solverprocure.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/trunk/src/solver/solverprocure.cpp $
00003   version : $LastChangedRevision: 1505 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2011-08-26 18:55:08 +0200 (Fri, 26 Aug 2011) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007-2011 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 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 double suggestQuantity(const BufferProcure* b, double f)
00036 {
00037   // Standard answer
00038   double order_qty = f;
00039 
00040   // Round to a multiple
00041   if (b->getSizeMultiple()>0.0)
00042   {
00043     int mult = static_cast<int>(order_qty / b->getSizeMultiple() + 0.99999999);
00044     order_qty = mult * b->getSizeMultiple();
00045   }
00046 
00047   // Respect minimum size
00048   if (order_qty < b->getSizeMinimum())
00049   {
00050     order_qty = b->getSizeMinimum();
00051     // round up to multiple
00052     if (b->getSizeMultiple()>0.0)
00053     {
00054       int mult = static_cast<int>(order_qty / b->getSizeMultiple() + 0.99999999);
00055       order_qty = mult * b->getSizeMultiple();
00056     }
00057     // if now bigger than max -> infeasible
00058     if (order_qty > b->getSizeMaximum())
00059       throw DataException("Inconsistent procurement parameters on buffer '"
00060         + b->getName() + "'");
00061   }
00062 
00063   // Respect maximum size
00064   if (order_qty > b->getSizeMaximum())
00065   {
00066     order_qty = b->getSizeMaximum();
00067     // round down
00068     if (b->getSizeMultiple()>0.0)
00069     {
00070       int mult = static_cast<int>(order_qty / b->getSizeMultiple());
00071       order_qty = mult * b->getSizeMultiple();
00072     }
00073     // if now smaller than min -> infeasible
00074     if (order_qty < b->getSizeMinimum())
00075       throw DataException("Inconsistent procurement parameters on buffer '"
00076         + b->getName() + "'");
00077   }
00078 
00079   // Reply
00080   return order_qty;
00081 }
00082 
00083 
00084 DECLARE_EXPORT void SolverMRP::solve(const BufferProcure* b, void* v)
00085 {
00086   SolverMRPdata* data = static_cast<SolverMRPdata*>(v);
00087 
00088   // TODO create a more performant procurement solver. Instead of creating a list of operationplans
00089   // moves and creations, we can create a custom command "updateProcurements". The commit of
00090   // this command will update the operationplans.
00091   // The solve method is only worried about getting a Yes/No reply. The reply is almost always yes,
00092   // except a) when the request is inside max(current + the lead time, latest procurement + min time
00093   // after locked procurement), or b) when the min time > 0 and max qty > 0
00094 
00095   // Call the user exit
00096   if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning));
00097 
00098   // Message
00099   if (data->getSolver()->getLogLevel()>1)
00100     logger << indent(b->getLevel()) << "  Procurement buffer '" << b->getName()
00101     << "' is asked: " << data->state->q_qty << "  " << data->state->q_date << endl;
00102 
00103   // Standard reply date
00104   data->state->a_date = Date::infiniteFuture;
00105 
00106   // Initialize an iterator over reusable existing procurements
00107   OperationPlan *last_operationplan = NULL;
00108   OperationPlan::iterator curProcure(b->getOperation());
00109   while (curProcure != OperationPlan::end() && curProcure->getLocked())
00110       ++curProcure;
00111   set<OperationPlan*> moved;
00112 
00113   // Find the latest locked procurement operation. It is used to know what
00114   // the earliest date is for a new procurement.
00115   Date earliest_next;
00116   for (OperationPlan::iterator procs(b->getOperation());
00117     procs != OperationPlan::end(); ++procs)
00118       if (procs->getLocked())
00119         earliest_next = procs->getDates().getEnd();
00120   Date latest_next = Date::infiniteFuture;
00121 
00122   // Find constraints on earliest and latest date for the next procurement
00123   if (earliest_next && b->getMaximumInterval())
00124     latest_next = earliest_next + b->getMaximumInterval();
00125   if (earliest_next && b->getMinimumInterval())
00126     earliest_next += b->getMinimumInterval();
00127   if (data->constrainedPlanning)
00128   {
00129     if (data->getSolver()->isLeadtimeConstrained()
00130       && earliest_next < Plan::instance().getCurrent() + b->getLeadtime())
00131       earliest_next = Plan::instance().getCurrent() + b->getLeadtime();
00132     if (data->getSolver()->isFenceConstrained()
00133       && earliest_next < Plan::instance().getCurrent() + b->getFence())
00134       earliest_next = Plan::instance().getCurrent() + b->getFence();
00135   }
00136 
00137   // Loop through all flowplans
00138   Date current_date;
00139   double produced = 0.0;
00140   double consumed = 0.0;
00141   double current_inventory = 0.0;
00142   const FlowPlan* current_flowplan = NULL;
00143   for (Buffer::flowplanlist::const_iterator cur=b->getFlowPlans().begin();
00144     latest_next != Date::infiniteFuture || cur != b->getFlowPlans().end(); )
00145   {
00146     if (cur==b->getFlowPlans().end() || latest_next < cur->getDate())
00147     {
00148       // Latest procument time is reached
00149       current_date = latest_next;
00150       current_flowplan = NULL;
00151     }
00152     else if (earliest_next && earliest_next < cur->getDate())
00153     {
00154       // Earliest procument time was reached
00155       current_date = earliest_next;
00156       current_flowplan = NULL;
00157     }
00158     else
00159     {
00160       // Date with flowplans found
00161       if (current_date && current_date >= cur->getDate())
00162       {
00163         // When procurements are being moved, it happens that we revisit the
00164         // same consuming flowplans twice. This check catches this case.
00165         cur++;
00166         continue;
00167       }
00168       current_date = cur->getDate();
00169       bool noConsumers = true;
00170       do
00171       {
00172         if (cur->getType() != 1)
00173         {
00174           cur++;
00175           continue;
00176         }
00177         current_flowplan = static_cast<const FlowPlan*>(&*(cur++));
00178         if (current_flowplan->getQuantity() < 0)
00179         {
00180           consumed -= current_flowplan->getQuantity();
00181           noConsumers = false;
00182         }
00183         else if (current_flowplan->getOperationPlan()->getLocked())
00184           produced += current_flowplan->getQuantity();
00185       }
00186       // Loop to pick up the last consuming flowplan on the given date
00187       while (cur != b->getFlowPlans().end() && cur->getDate() == current_date);
00188       // No further interest in dates with only producing flowplans.
00189       if (noConsumers) continue;
00190     }
00191 
00192     // Compute current inventory. The actual onhand in the buffer may be
00193     // different since we count only consumers and *locked* producers.
00194     current_inventory = produced - consumed;
00195 
00196     // Hard limit: respect minimum interval
00197     if (current_date < earliest_next)
00198     {
00199       if (current_inventory < -ROUNDING_ERROR
00200         && current_date >= data->state->q_date
00201         && b->getMinimumInterval()
00202         && data->state->a_date > earliest_next
00203         && data->getSolver()->isMaterialConstrained()
00204         && data->constrainedPlanning)
00205           // The inventory goes negative here and we can't procure more
00206           // material because of the minimum interval...
00207           data->state->a_date = earliest_next;
00208       continue;
00209     }
00210 
00211     // Now the normal reorder check
00212     if (current_inventory >= b->getMinimumInventory()
00213       && current_date < latest_next)
00214     {
00215       if (current_date == earliest_next) earliest_next = Date::infinitePast;
00216       continue;
00217     }
00218 
00219     // When we are within the minimum interval, we may need to increase the
00220     // size of the latest procurement.
00221     if (current_date == earliest_next
00222       && last_operationplan
00223       && current_inventory < b->getMinimumInventory())
00224     {
00225       double origqty = last_operationplan->getQuantity();
00226       last_operationplan->setQuantity(suggestQuantity(b,
00227         last_operationplan->getQuantity()
00228           + b->getMinimumInventory() - current_inventory));
00229       produced += last_operationplan->getQuantity() - origqty;
00230       current_inventory = produced - consumed;
00231       if (current_inventory < -ROUNDING_ERROR
00232         && data->state->a_date > earliest_next + b->getMinimumInterval()
00233         && earliest_next + b->getMinimumInterval() > data->state->q_date
00234         && data->getSolver()->isMaterialConstrained()
00235         && data->constrainedPlanning)
00236           // Resizing didn't work, and we still have shortage
00237           data->state->a_date = earliest_next + b->getMinimumInterval();
00238     }
00239 
00240     // At this point, we know we need to reorder...
00241     earliest_next = Date::infinitePast;
00242     double order_qty = suggestQuantity(b,
00243       b->getMaximumInventory() - current_inventory);
00244     if (order_qty > 0)
00245     {
00246       // Create a procurement or update an existing one
00247       if (curProcure == OperationPlan::end())
00248       {
00249         // No existing procurement can be reused. Create a new one.
00250         CommandCreateOperationPlan *a =
00251           new CommandCreateOperationPlan(b->getOperation(), order_qty,
00252             Date::infinitePast, current_date, data->state->curDemand);
00253         last_operationplan = a->getOperationPlan();
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