solverprocure.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/tags/0.9.1/src/solver/solverprocure.cpp $
00003   version : $LastChangedRevision: 1656 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2012-03-27 19:05:34 +0200 (Tue, 27 Mar 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 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         a->getOperationPlan()->setMotive(data->state->motive);
00255         last_operationplan->insertInOperationplanList(); // TODO Not very nice: unregistered opplan in the list!
00256         produced += last_operationplan->getQuantity();
00257         data->add(a);
00258       }
00259       else if (curProcure->getDates().getEnd() == current_date
00260           && curProcure->getQuantity() == order_qty)
00261       {
00262         // We can reuse this existing procurement unchanged.
00263         produced += order_qty;
00264         last_operationplan = &*curProcure;
00265         moved.insert(last_operationplan);
00266         do
00267           ++curProcure;
00268         while (curProcure != OperationPlan::end()
00269             && curProcure->getLocked() && moved.find(&*curProcure)!=moved.end());
00270       }
00271       else
00272       {
00273         // Update an existing procurement to meet current needs
00274         CommandMoveOperationPlan *a =
00275           new CommandMoveOperationPlan(&*curProcure, Date::infinitePast, current_date, order_qty);
00276         last_operationplan = a->getOperationPlan();
00277         moved.insert(last_operationplan);
00278         data->add(a);
00279         produced += last_operationplan->getQuantity();
00280         do
00281           ++curProcure;
00282         while (curProcure != OperationPlan::end()
00283             && curProcure->getLocked() && moved.find(&*curProcure)!=moved.end());
00284       }
00285       if (b->getMinimumInterval())
00286         earliest_next = current_date + b->getMinimumInterval();
00287     }
00288     if (b->getMaximumInterval())
00289     {
00290       current_inventory = produced - consumed;
00291       if (current_inventory >= b->getMaximumInventory()
00292           && cur == b->getFlowPlans().end())
00293         // Nothing happens any more further in the future.
00294         // Abort procuring based on the max inteval
00295         latest_next = Date::infiniteFuture;
00296       else
00297         latest_next = current_date + b->getMaximumInterval();
00298     }
00299   }
00300 
00301   // Get rid of extra procurements that have become redundant
00302   while (curProcure != OperationPlan::end())
00303   {
00304     OperationPlan *opplan = &*(curProcure++);
00305     if (!opplan->getLocked() && moved.find(opplan)!=moved.end())
00306       data->add(new CommandDeleteOperationPlan(opplan));
00307   }
00308 
00309   // Create the answer
00310   if (data->constrainedPlanning && (data->getSolver()->isFenceConstrained()
00311       || data->getSolver()->isLeadtimeConstrained()
00312       || data->getSolver()->isMaterialConstrained()))
00313   {
00314     // Check if the inventory drops below zero somewhere
00315     double shortage = 0;
00316     Date startdate;
00317     for (Buffer::flowplanlist::const_iterator cur = b->getFlowPlans().begin();
00318         cur != b->getFlowPlans().end(); ++cur)
00319       if (cur->getDate() >= data->state->q_date
00320           && cur->getOnhand() < -ROUNDING_ERROR
00321           && cur->getOnhand() < shortage)
00322       {
00323         shortage = cur->getOnhand();
00324         if (-shortage >= data->state->q_qty) break;
00325         if (startdate == Date::infinitePast) startdate = cur->getDate();
00326       }
00327     if (shortage < 0)
00328     {
00329       // Answer a shorted quantity
00330       data->state->a_qty = data->state->q_qty + shortage;
00331       // Log a constraint
00332       if (data->logConstraints)
00333         data->planningDemand->getConstraints().push(
00334           ProblemMaterialShortage::metadata, b, startdate, Date::infiniteFuture, // @todo calculate a better end date
00335           -shortage);
00336       // Nothing to promise...
00337       if (data->state->a_qty < 0) data->state->a_qty = 0;
00338       // Check the reply date
00339       if (data->constrainedPlanning)
00340       {
00341         if (data->getSolver()->isFenceConstrained()
00342             && data->state->q_date < Plan::instance().getCurrent() + b->getFence()
00343             && data->state->a_date > Plan::instance().getCurrent() + b->getFence())
00344           data->state->a_date = Plan::instance().getCurrent() + b->getFence();
00345         if (data->getSolver()->isLeadtimeConstrained()
00346             && data->state->q_date < Plan::instance().getCurrent() + b->getLeadtime()
00347             && data->state->a_date > Plan::instance().getCurrent() + b->getLeadtime())
00348           data->state->a_date = Plan::instance().getCurrent() + b->getLeadtime();
00349       }
00350     }
00351     else
00352       // Answer the full quantity
00353       data->state->a_qty = data->state->q_qty;
00354   }
00355   else
00356     // Answer the full quantity
00357     data->state->a_qty = data->state->q_qty;
00358 
00359   // Increment the cost
00360   if (b->getItem() && data->state->a_qty > 0.0)
00361     data->state->a_cost += data->state->a_qty * b->getItem()->getPrice();
00362 
00363   // Message
00364   if (data->getSolver()->getLogLevel()>1)
00365     logger << indent(b->getLevel()) << "  Procurement buffer '" << b
00366         << "' answers: " << data->state->a_qty << "  " << data->state->a_date
00367         << "  " << data->state->a_cost << "  " << data->state->a_penalty << endl;
00368 }
00369 
00370 
00371 }

Documentation generated for frePPLe by  doxygen