solverbuffer.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/tags/0.8.0/src/solver/solverbuffer.cpp $
00003   version : $LastChangedRevision: 1187 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2010-02-21 12:45:06 +0100 (Sun, 21 Feb 2010) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007 by Johan De Taeye                                    *
00010  *                                                                         *
00011  * This library is free software; you can redistribute it and/or modify it *
00012  * under the terms of the GNU Lesser General Public License as published   *
00013  * by the Free Software Foundation; either version 2.1 of the License, or  *
00014  * (at your option) any later version.                                     *
00015  *                                                                         *
00016  * This library is distributed in the hope that it will be useful,         *
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
00019  * General Public License for more details.                                *
00020  *                                                                         *
00021  * You should have received a copy of the GNU Lesser General Public        *
00022  * License along with this library; if not, write to the Free Software     *
00023  * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 *
00024  * USA                                                                     *
00025  *                                                                         *
00026  ***************************************************************************/
00027 
00028 #define FREPPLE_CORE
00029 #include "frepple/solver.h"
00030 
00031 namespace frepple
00032 {
00033 
00034 
00035 /** @todo The flow quantity is handled at the wrong place. It needs to be
00036   * handled by the operation, since flows can exist on multiple suboperations
00037   * with different quantities. The buffer solve can't handle this, because
00038   * it only calls the solve() for the producing operation...
00039   * Are there some situations where the operation solver doesn't know enough
00040   * on the buffer behavior???
00041   */
00042 DECLARE_EXPORT void SolverMRP::solve(const Buffer* b, void* v)
00043 {
00044   SolverMRPdata* data = static_cast<SolverMRPdata*>(v);
00045   Date requested_date(data->state->q_date);
00046   double requested_qty(data->state->q_qty);
00047   bool tried_requested_date(false);
00048 
00049   // Call the user exit
00050   if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning));
00051 
00052   // Message
00053   if (data->getSolver()->getLogLevel()>1)
00054     logger << indent(b->getLevel()) << "  Buffer '" << b->getName()
00055       << "' is asked: " << data->state->q_qty << "  " << data->state->q_date << endl;
00056 
00057   // Store the last command in the list, in order to undo the following
00058   // commands if required.
00059   Command* topcommand = data->getLastCommand();
00060 
00061   // Make sure the new operationplans don't inherit an owner.
00062   // When an operation calls the solve method of suboperations, this field is
00063   // used to pass the information about the owner operationplan down. When
00064   // solving for buffers we must make sure NOT to pass owner information.
00065   // At the end of solving for a buffer we need to restore the original
00066   // settings...
00067   OperationPlan *prev_owner_opplan = data->state->curOwnerOpplan;
00068   data->state->curOwnerOpplan = NULL;
00069 
00070   // Evaluate the buffer profile and solve shortages by asking more material.
00071   // The loop goes from the requested date till the very end. Whenever the
00072   // event date changes, we evaluate if a shortage exists.
00073   Date currentDate;
00074   const TimeLine<FlowPlan>::Event *prev = NULL;
00075   double shortage(0.0);
00076   Date extraSupplyDate(Date::infiniteFuture);
00077   Date extraInventoryDate(Date::infiniteFuture);
00078   double cumproduced = b->getFlowPlans().rbegin()->getCumulativeProduced();
00079   double current_minimum(0.0);
00080   double unconfirmed_supply(0.0);
00081   for (Buffer::flowplanlist::const_iterator cur=b->getFlowPlans().begin();
00082       ; ++cur)
00083   {
00084     const FlowPlan* fplan = dynamic_cast<const FlowPlan*>(&*cur);
00085     if (fplan && !fplan->getOperationPlan()->getIdentifier() 
00086       && fplan->getQuantity()>0 
00087       && fplan->getOperationPlan()->getOperation() != b->getProducingOperation())
00088       unconfirmed_supply += fplan->getQuantity();
00089 
00090     // Iterator has now changed to a new date or we have arrived at the end.
00091     // If multiple flows are at the same moment in time, we are not interested
00092     // in the inventory changes. It gets interesting only when a certain
00093     // inventory level remains unchanged for a certain time.
00094     if ((cur == b->getFlowPlans().end() || cur->getDate()>currentDate) && prev)
00095     {
00096       // Some variables
00097       Date theDate = prev->getDate();
00098       double theOnHand = prev->getOnhand();
00099       double theDelta = theOnHand - current_minimum + shortage;
00100 
00101       // Evaluate the situation at the last flowplan before the date change.
00102       // Is there a shortage at that date?
00103       if (theDelta < -ROUNDING_ERROR)
00104       {
00105         // Can we get extra supply to solve the problem, or part of it?
00106         // If the shortage already starts before the requested date, it
00107         // was not created by the newly added flowplan, but existed before.
00108         // We don't consider this as a shortage for the current flowplan,
00109         // and we want our flowplan to try to repair the previous problems
00110         // if it can...
00111         bool loop = true;
00112         while (b->getProducingOperation() && theDate >= requested_date && loop)
00113         {
00114           // Create supply
00115           data->state->curBuffer = const_cast<Buffer*>(b);
00116           data->state->q_qty = -theDelta;
00117           data->state->q_date = prev->getDate();
00118 
00119           // Check whether this date doesn't match with the requested date.
00120           // See a bit further why this is required.
00121           if (data->state->q_date == requested_date) tried_requested_date = true;
00122 
00123           // Note that the supply created with the next line changes the
00124           // onhand value at all later dates!
00125           b->getProducingOperation()->solve(*this,v);
00126 
00127           // Evaluate the reply date. The variable extraSupplyDate will store
00128           // the date when the producing operation tells us it can get extra
00129           // supply.
00130           if (data->state->a_date < extraSupplyDate
00131             && data->state->a_date > requested_date)
00132             extraSupplyDate = data->state->a_date;
00133 
00134           // If we got some extra supply, we retry to get some more supply.
00135           // Only when no extra material is obtained, we give up.
00136           if (data->state->a_qty > ROUNDING_ERROR
00137             && data->state->a_qty < -theDelta - ROUNDING_ERROR)
00138             theDelta += data->state->a_qty;
00139           else
00140             loop = false;
00141         }
00142 
00143         // Not enough supply was received to repair the complete problem
00144         if (prev->getOnhand() + shortage < -ROUNDING_ERROR)
00145         {
00146           // Keep track of the shorted quantity.
00147           // Only consider shortages later than the requested date.
00148           if (theDate >= requested_date)
00149             shortage = -prev->getOnhand();
00150 
00151           // Reset the date from which excess material is in the buffer. This
00152           // excess material can be used to compute the date when the buffer
00153           // can be asked again for additional supply.
00154           extraInventoryDate = Date::infiniteFuture;
00155         }
00156       }
00157       else if (theDelta > unconfirmed_supply + ROUNDING_ERROR)
00158         // There is excess material at this date (coming from planned/frozen
00159         // material arrivals, surplus material created by lotsized operations,
00160         // etc...)
00161         // The unconfirmed_supply element is required to exclude any of the
00162         // excess inventory we may have caused ourselves. Such situations are 
00163         // possible when there are loops in the supply chain.
00164         if (theDate > requested_date
00165             && extraInventoryDate == Date::infiniteFuture)
00166           extraInventoryDate = theDate;
00167     }
00168 
00169     // We have reached the end of the flowplans. Breaking out of the loop
00170     // needs to be done here because in the next statements we are accessing
00171     // *cur, which isn't valid at the end of the list
00172     if (cur == b->getFlowPlans().end()) break;
00173 
00174     // The minimum or the maximum have changed
00175     // Note that these limits can be updated only after the processing of the
00176     // date change in the statement above. Otherwise the code above would
00177     // already use the new value before the intended date.
00178     if (cur->getType() == 3) current_minimum = cur->getMin();
00179 
00180     // Update the pointer to the previous flowplan.
00181     prev = &*cur;
00182     currentDate = cur->getDate();
00183   }
00184 
00185   // Note: the variable extraInventoryDate now stores the date from which
00186   // excess material is available in the buffer. The excess
00187   // We don't need to care how much material is lying there.
00188 
00189   // Check for supply at the requested date
00190   // Isn't this included in the normal loop? In some cases it is indeed, but
00191   // sometimes it isn't because in the normal loop there may still have been
00192   // onhand available and the shortage only shows at a later date than the
00193   // requested date.
00194   // E.g. Initial situation:              After extra consumer at time y:
00195   //      -------+                                --+
00196   //             |                                  |
00197   //             +------                            +---+
00198   //                                                    |
00199   //    0 -------y------                        0 --y---x-----
00200   //                                                    |
00201   //                                                    +-----
00202   // The first loop only checks for supply at times x and later. If it is not
00203   // feasible, we now check for supply at time y. It will create some extra
00204   // inventory, but at least the demand is met.
00205   // @todo The buffer solver could move backward in time from x till time y,
00206   // and try multiple dates. This would minimize the excess inventory created.
00207   while (shortage > ROUNDING_ERROR
00208       && b->getProducingOperation() && !tried_requested_date)
00209   {
00210     // Create supply at the requested date
00211     data->state->curBuffer = const_cast<Buffer*>(b);
00212     data->state->q_qty = shortage;
00213     data->state->q_date = requested_date;
00214     // Note that the supply created with the next line changes the onhand value
00215     // at all later dates!
00216     // Note that asking at the requested date doesn't keep the material on
00217     // stock to a minimum.
00218     b->getProducingOperation()->solve(*this,v);
00219     // Evaluate the reply
00220     if (data->state->a_date < extraSupplyDate
00221       && data->state->a_date > requested_date)
00222       extraSupplyDate = data->state->a_date;
00223     if (data->state->a_qty > ROUNDING_ERROR)
00224       shortage -= data->state->a_qty;
00225     else
00226       tried_requested_date = true;
00227   }
00228 
00229   // Final evaluation of the replenishment
00230   if (data->constrainedPlanning && data->getSolver()->isConstrained())
00231   {
00232     // Use the constrained planning result
00233     data->state->a_qty = requested_qty - shortage;
00234     if (data->state->a_qty < ROUNDING_ERROR)
00235     {
00236       data->undo(topcommand);
00237       data->state->a_qty = 0.0;
00238     }
00239     data->state->a_date = (extraInventoryDate < extraSupplyDate) ?
00240         extraInventoryDate :
00241         extraSupplyDate;
00242   }
00243   else
00244   {
00245     // Enough inventory or supply available, or not material constrained.
00246     // In case of a plan that is not material constrained, the buffer tries to
00247     // solve for shortages as good as possible. Only in the end we 'lie' about
00248     // the result to the calling function. Material shortages will then remain
00249     // in the buffer.
00250     data->state->a_qty = requested_qty;
00251     data->state->a_date = Date::infiniteFuture;
00252   }
00253 
00254   // Restore the owning operationplan.
00255   data->state->curOwnerOpplan = prev_owner_opplan;
00256 
00257   // Reply quantity must be greater than 0
00258   assert( data->state->a_qty >= 0 );
00259 
00260   // Increment the cost
00261   // Only the quantity consumed directly from the buffer is counted.
00262   // The cost of the material supply taken from producing operations is
00263   // computed seperately and not considered here.
00264   if (b->getItem() && data->state->a_qty > 0)
00265   {
00266     cumproduced = b->getFlowPlans().rbegin()->getCumulativeProduced() - cumproduced;
00267     if (data->state->a_qty > cumproduced)
00268       data->state->a_cost += (data->state->a_qty - cumproduced) * b->getItem()->getPrice();
00269   }
00270 
00271   // Message
00272   if (data->getSolver()->getLogLevel()>1)
00273     logger << indent(b->getLevel()) << "  Buffer '" << b->getName()
00274     << "' answers: " << data->state->a_qty << "  " << data->state->a_date << "  "
00275     << data->state->a_cost << "  " << data->state->a_penalty << endl;
00276 }
00277 
00278 
00279 DECLARE_EXPORT void SolverMRP::solve(const BufferInfinite* b, void* v)
00280 {
00281   SolverMRPdata* data = static_cast<SolverMRPdata*>(v);
00282 
00283   // Call the user exit
00284   if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning));
00285 
00286   // Message
00287   if (data->getSolver()->getLogLevel()>1)
00288     logger << indent(b->getLevel()) << "  Infinite buffer '" << b << "' is asked: "
00289     << data->state->q_qty << "  " << data->state->q_date << endl;
00290 
00291   // Reply whatever is requested, regardless of date, quantity or supply.
00292   // The demand is not propagated upstream either.
00293   data->state->a_qty = data->state->q_qty;
00294   data->state->a_date = data->state->q_date;
00295   if (b->getItem())
00296     data->state->a_cost += data->state->q_qty * b->getItem()->getPrice();
00297 
00298   // Message
00299   if (data->getSolver()->getLogLevel()>1)
00300     logger << indent(b->getLevel()) << "  Infinite buffer '" << b << "' answers: "
00301     << data->state->a_qty << "  " << data->state->a_date << "  "
00302     << data->state->a_cost << "  " << data->state->a_penalty << endl;
00303 }
00304 
00305 
00306 }

Generated on 21 Mar 2010 for frePPLe by  doxygen 1.6.1