solverprocure.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: http://svn.code.sf.net/p/frepple/code/trunk/src/solver/solverprocure.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 double suggestQuantity(const BufferProcure* b, double f)
00035 {
00036   // Standard answer
00037   double order_qty = f;
00038 
00039   // Round to a multiple
00040   if (b->getSizeMultiple()>0.0)
00041   {
00042     int mult = static_cast<int>(order_qty / b->getSizeMultiple() + 0.99999999);
00043     order_qty = mult * b->getSizeMultiple();
00044   }
00045 
00046   // Respect minimum size
00047   if (order_qty < b->getSizeMinimum())
00048   {
00049     order_qty = b->getSizeMinimum();
00050     // round up to multiple
00051     if (b->getSizeMultiple()>0.0)
00052     {
00053       int mult = static_cast<int>(order_qty / b->getSizeMultiple() + 0.99999999);
00054       order_qty = mult * b->getSizeMultiple();
00055     }
00056     // if now bigger than max -> infeasible
00057     if (order_qty > b->getSizeMaximum())
00058       throw DataException("Inconsistent procurement parameters on buffer '"
00059           + b->getName() + "'");
00060   }
00061 
00062   // Respect maximum size
00063   if (order_qty > b->getSizeMaximum())
00064   {
00065     order_qty = b->getSizeMaximum();
00066     // round down
00067     if (b->getSizeMultiple()>0.0)
00068     {
00069       int mult = static_cast<int>(order_qty / b->getSizeMultiple());
00070       order_qty = mult * b->getSizeMultiple();
00071     }
00072     // if now smaller than min -> infeasible
00073     if (order_qty < b->getSizeMinimum())
00074       throw DataException("Inconsistent procurement parameters on buffer '"
00075           + b->getName() + "'");
00076   }
00077 
00078   // Reply
00079   return order_qty;
00080 }
00081 
00082 
00083 DECLARE_EXPORT void SolverMRP::solve(const BufferProcure* b, void* v)
00084 {
00085   SolverMRPdata* data = static_cast<SolverMRPdata*>(v);
00086 
00087   // TODO create a more performant procurement solver. Instead of creating a list of operationplans
00088   // moves and creations, we can create a custom command "updateProcurements". The commit of
00089   // this command will update the operationplans.
00090   // The solve method is only worried about getting a Yes/No reply. The reply is almost always yes,
00091   // except a) when the request is inside max(current + the lead time, latest procurement + min time
00092   // after locked procurement), or b) when the min time > 0 and max qty > 0
00093 
00094   // Call the user exit
00095   if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning));
00096 
00097   // Message
00098   if (data->getSolver()->getLogLevel()>1)
00099     logger << indent(b->getLevel()) << "  Procurement buffer '" << b->getName()
00100         << "' is asked: " << data->state->q_qty << "  " << data->state->q_date << endl;
00101 
00102   // Standard reply date
00103   data->state->a_date = Date::infiniteFuture;
00104 
00105   // Initialize an iterator over reusable existing procurements
00106   OperationPlan *last_operationplan = NULL;
00107   OperationPlan::iterator curProcure(b->getOperation());
00108   while (curProcure != OperationPlan::end() && curProcure->getLocked())
00109     ++curProcure;
00110   set<OperationPlan*> moved;
00111 
00112   // Find the latest locked procurement operation. It is used to know what
00113   // the earliest date is for a new procurement.
00114   Date earliest_next;
00115   for (OperationPlan::iterator procs(b->getOperation());
00116       procs != OperationPlan::end(); ++procs)
00117     if (procs->getLocked())
00118       earliest_next = procs->getDates().getEnd();
00119   Date latest_next = Date::infiniteFuture;
00120 
00121   // Find constraints on earliest and latest date for the next procurement
00122   if (earliest_next && b->getMaximumInterval())
00123     latest_next = earliest_next + b->getMaximumInterval();
00124   if (earliest_next && b->getMinimumInterval())
00125     earliest_next += b->getMinimumInterval();
00126   if (data->constrainedPlanning)
00127   {
00128     if (data->getSolver()->isLeadtimeConstrained()
00129         && earliest_next < Plan::instance().getCurrent() + b->getLeadtime())
00130       earliest_next = Plan::instance().getCurrent() + b->getLeadtime();
00131     if (data->getSolver()->isFenceConstrained()
00132         && earliest_next < Plan::instance().getCurrent() + b->getFence())
00133       earliest_next = Plan::instance().getCurrent() + b->getFence();
00134   }
00135 
00136   // Loop through all flowplans
00137   Date current_date;
00138   double produced = 0.0;
00139   double consumed = 0.0;
00140   double current_inventory = 0.0;
00141   const FlowPlan* current_flowplan = NULL;
00142   for (Buffer::flowplanlist::const_iterator cur=b->getFlowPlans().begin();
00143       latest_next != Date::infiniteFuture || cur != b->getFlowPlans().end(); )
00144   {
00145     if (cur==b->getFlowPlans().end() || latest_next < cur->getDate())
00146     {
00147       // Latest procument time is reached
00148       current_date = latest_next;
00149       current_flowplan = NULL;
00150     }
00151     else if (earliest_next && earliest_next < cur->getDate())
00152     {
00153       // Earliest procument time was reached
00154       current_date = earliest_next;
00155       current_flowplan = NULL;
00156     }
00157     else
00158     {
00159       // Date with flowplans found
00160       if (current_date && current_date >= cur->getDate())
00161       {
00162         // When procurements are being moved, it happens that we revisit the
00163         // same consuming flowplans twice. This check catches this case.
00164         cur++;
00165         continue;
00166       }
00167       current_date = cur->getDate();
00168       bool noConsumers = true;
00169       do
00170       {
00171         if (cur->getType() != 1)
00172         {
00173           cur++;
00174           continue;
00175         }
00176         current_flowplan = static_cast<const FlowPlan*>(&*(cur++));
00177         if (current_flowplan->getQuantity() < 0)
00178         {
00179           consumed -= current_flowplan->getQuantity();
00180           noConsumers = false;
00181         }
00182         else if (current_flowplan->getOperationPlan()->getLocked())
00183           produced += current_flowplan->getQuantity();
00184       }
00185       // Loop to pick up the last consuming flowplan on the given date
00186       while (cur != b->getFlowPlans().end() && cur->getDate() == current_date);
00187       // No further interest in dates with only producing flowplans.
00188       if (noConsumers) continue;
00189     }
00190 
00191     // Compute current inventory. The actual onhand in the buffer may be
00192     // different since we count only consumers and *locked* producers.
00193     current_inventory = produced - consumed;
00194 
00195     // Hard limit: respect minimum interval
00196     if (current_date < earliest_next)
00197     {
00198       if (current_inventory < -ROUNDING_ERROR
00199           && current_date >= data->state->q_date
00200           && b->getMinimumInterval()
00201           && data->state->a_date > earliest_next
00202           && data->getSolver()->isMaterialConstrained()
00203           && data->constrainedPlanning)
00204         // The inventory goes negative here and we can't procure more
00205         // material because of the minimum interval...
00206         data->state->a_date = earliest_next;
00207       continue;
00208     }
00209 
00210     // Now the normal reorder check
00211     if (current_inventory >= b->getMinimumInventory()
00212         && current_date < latest_next)
00213     {
00214       if (current_date == earliest_next) earliest_next = Date::infinitePast;
00215       continue;
00216     }
00217 
00218     // When we are within the minimum interval, we may need to increase the
00219     // size of the latest procurement.
00220     if (current_date == earliest_next
00221         && last_operationplan
00222         && current_inventory < b->getMinimumInventory())
00223     {
00224       double origqty = last_operationplan->getQuantity();
00225       last_operationplan->setQuantity(suggestQuantity(b,
00226           last_operationplan->getQuantity()
00227           + b->getMinimumInventory() - current_inventory));
00228       produced += last_operationplan->getQuantity() - origqty;
00229       current_inventory = produced - consumed;
00230       if (current_inventory < -ROUNDING_ERROR
00231           && data->state->a_date > earliest_next + b->getMinimumInterval()
00232           && earliest_next + b->getMinimumInterval() > data->state->q_date
00233           && data->getSolver()->isMaterialConstrained()
00234           && data->constrainedPlanning)
00235         // Resizing didn't work, and we still have shortage
00236         data->state->a_date = earliest_next + b->getMinimumInterval();
00237     }
00238 
00239     // At this point, we know we need to reorder...
00240     earliest_next = Date::infinitePast;
00241     double order_qty = suggestQuantity(b,
00242         b->getMaximumInventory() - current_inventory);
00243     if (order_qty > 0)
00244     {
00245       // Create a procurement or update an existing one
00246       if (curProcure == OperationPlan::end())
00247       {
00248         // No existing procurement can be reused. Create a new one.
00249         CommandCreateOperationPlan *a =
00250           new CommandCreateOperationPlan(b->getOperation(), order_qty,
00251               Date::infinitePast, current_date, data->state->curDemand);
00252         last_operationplan = a->getOperationPlan();
00253         a->getOperationPlan()->setMotive(data->state->motive);
00254         last_operationplan->insertInOperationplanList(); // TODO Not very nice: unregistered opplan in the list!
00255         produced += last_operationplan->getQuantity();
00256         data->add(a);
00257       }
00258       else if (curProcure->getDates().getEnd() == current_date
00259           && curProcure->getQuantity() == order_qty)
00260       {
00261         // We can reuse this existing procurement unchanged.
00262         produced += order_qty;
00263         last_operationplan = &*curProcure;
00264         moved.insert(last_operationplan);
00265         do
00266           ++curProcure;
00267         while (curProcure != OperationPlan::end()
00268             && curProcure->getLocked() && moved.find(&*curProcure)!=moved.end());
00269       }
00270       else
00271       {
00272         // Update an existing procurement to meet current needs
00273         CommandMoveOperationPlan *a =
00274           new CommandMoveOperationPlan(&*curProcure, Date::infinitePast, current_date, order_qty);
00275         last_operationplan = a->getOperationPlan();
00276         moved.insert(last_operationplan);
00277         data->add(a);
00278         produced += last_operationplan->getQuantity();
00279         do
00280           ++curProcure;
00281         while (curProcure != OperationPlan::end()
00282             && curProcure->getLocked() && moved.find(&*curProcure)!=moved.end());
00283       }
00284       if (b->getMinimumInterval())
00285         earliest_next = current_date + b->getMinimumInterval();
00286     }
00287     if (b->getMaximumInterval())
00288     {
00289       current_inventory = produced - consumed;
00290       if (current_inventory >= b->getMaximumInventory()
00291           && cur == b->getFlowPlans().end())
00292         // Nothing happens any more further in the future.
00293         // Abort procuring based on the max inteval
00294         latest_next = Date::infiniteFuture;
00295       else
00296         latest_next = current_date + b->getMaximumInterval();
00297     }
00298   }
00299 
00300   // Get rid of extra procurements that have become redundant
00301   while (curProcure != OperationPlan::end())
00302   {
00303     OperationPlan *opplan = &*(curProcure++);
00304     if (!opplan->getLocked() && moved.find(opplan)!=moved.end())
00305       data->add(new CommandDeleteOperationPlan(opplan));
00306   }
00307 
00308   // Create the answer
00309   if (data->constrainedPlanning && (data->getSolver()->isFenceConstrained()
00310       || data->getSolver()->isLeadtimeConstrained()
00311       || data->getSolver()->isMaterialConstrained()))
00312   {
00313     // Check if the inventory drops below zero somewhere
00314     double shortage = 0;
00315     Date startdate;
00316     for (Buffer::flowplanlist::const_iterator cur = b->getFlowPlans().begin();
00317         cur != b->getFlowPlans().end(); ++cur)
00318       if (cur->getDate() >= data->state->q_date
00319           && cur->getOnhand() < -ROUNDING_ERROR
00320           && cur->getOnhand() < shortage)
00321       {
00322         shortage = cur->getOnhand();
00323         if (-shortage >= data->state->q_qty) break;
00324         if (startdate == Date::infinitePast) startdate = cur->getDate();
00325       }
00326     if (shortage < 0)
00327     {
00328       // Answer a shorted quantity
00329       data->state->a_qty = data->state->q_qty + shortage;
00330       // Log a constraint
00331       if (data->logConstraints)
00332         data->planningDemand->getConstraints().push(
00333           ProblemMaterialShortage::metadata, b, startdate, Date::infiniteFuture, // @todo calculate a better end date
00334           -shortage);
00335       // Nothing to promise...
00336       if (data->state->a_qty < 0) data->state->a_qty = 0;
00337       // Check the reply date
00338       if (data->constrainedPlanning)
00339       {
00340         if (data->getSolver()->isFenceConstrained()
00341             && data->state->q_date < Plan::instance().getCurrent() + b->getFence()
00342             && data->state->a_date > Plan::instance().getCurrent() + b->getFence())
00343           data->state->a_date = Plan::instance().getCurrent() + b->getFence();
00344         if (data->getSolver()->isLeadtimeConstrained()
00345             && data->state->q_date < Plan::instance().getCurrent() + b->getLeadtime()
00346             && data->state->a_date > Plan::instance().getCurrent() + b->getLeadtime())
00347           data->state->a_date = Plan::instance().getCurrent() + b->getLeadtime();
00348       }
00349     }
00350     else
00351       // Answer the full quantity
00352       data->state->a_qty = data->state->q_qty;
00353   }
00354   else
00355     // Answer the full quantity
00356     data->state->a_qty = data->state->q_qty;
00357 
00358   // Increment the cost
00359   if (b->getItem() && data->state->a_qty > 0.0)
00360     data->state->a_cost += data->state->a_qty * b->getItem()->getPrice();
00361 
00362   // Message
00363   if (data->getSolver()->getLogLevel()>1)
00364     logger << indent(b->getLevel()) << "  Procurement buffer '" << b
00365         << "' answers: " << data->state->a_qty << "  " << data->state->a_date
00366         << "  " << data->state->a_cost << "  " << data->state->a_penalty << endl;
00367 }
00368 
00369 
00370 }

Documentation generated for frePPLe by  doxygen