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

Documentation generated for frePPLe by  doxygen