solverprocure.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/solverprocure.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 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   // Call the user exit
00089   if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning));
00090 
00091   // Message
00092   if (data->getSolver()->getLogLevel()>1)
00093     logger << indent(b->getLevel()) << "  Procurement buffer '" << b->getName()
00094     << "' is asked: " << data->state->q_qty << "  " << data->state->q_date << endl;
00095 
00096   // Standard reply date
00097   data->state->a_date = Date::infiniteFuture;
00098 
00099   // Initialize an iterator over reusable existing procurements
00100   OperationPlan *last_operationplan = NULL;
00101   OperationPlan::iterator curProcure(b->getOperation());
00102   while (curProcure != OperationPlan::end() && curProcure->getLocked())
00103       ++curProcure;
00104   set<OperationPlan*> moved;
00105 
00106   // Find the latest locked procurement operation. It is used to know what
00107   // the earliest date is for a new procurement.
00108   Date earliest_next;
00109   for (OperationPlan::iterator procs(b->getOperation());
00110     procs != OperationPlan::end(); ++procs)
00111       if (procs->getLocked())
00112         earliest_next = procs->getDates().getEnd();
00113   Date latest_next = Date::infiniteFuture;
00114 
00115   // Find constraints on earliest and latest date for the next procurement
00116   if (earliest_next && b->getMaximumInterval())
00117     latest_next = earliest_next + b->getMaximumInterval();
00118   if (earliest_next && b->getMinimumInterval())
00119     earliest_next += b->getMinimumInterval();
00120   if (data->constrainedPlanning)
00121   {
00122     if (data->getSolver()->isLeadtimeConstrained()
00123       && earliest_next < Plan::instance().getCurrent() + b->getLeadtime())
00124       earliest_next = Plan::instance().getCurrent() + b->getLeadtime();
00125     if (data->getSolver()->isFenceConstrained()
00126       && earliest_next < Plan::instance().getCurrent() + b->getFence())
00127       earliest_next = Plan::instance().getCurrent() + b->getFence();
00128   }
00129 
00130   // Loop through all flowplans
00131   Date current_date;
00132   double produced = 0.0;
00133   double consumed = 0.0;
00134   double current_inventory = 0.0;
00135   const FlowPlan* current_flowplan = NULL;
00136   for (Buffer::flowplanlist::const_iterator cur=b->getFlowPlans().begin();
00137     latest_next != Date::infiniteFuture || cur != b->getFlowPlans().end(); )
00138   {
00139     if (cur==b->getFlowPlans().end() || latest_next < cur->getDate())
00140     {
00141       // Latest procument time is reached
00142       current_date = latest_next;
00143       current_flowplan = NULL;
00144     }
00145     else if (earliest_next && earliest_next < cur->getDate())
00146     {
00147       // Earliest procument time was reached
00148       current_date = earliest_next;
00149       current_flowplan = NULL;
00150     }
00151     else
00152     {
00153       // Date with flowplans found
00154       if (current_date && current_date >= cur->getDate())
00155       {
00156         // When procurements are being moved, it happens that we revisit the
00157         // same consuming flowplans twice. This check catches this case.
00158         cur++;
00159         continue;
00160       }
00161       current_date = cur->getDate();
00162       bool noConsumers = true;
00163       do
00164       {
00165         if (cur->getType() != 1)
00166         {
00167           cur++;
00168           continue;
00169         }
00170         current_flowplan = static_cast<const FlowPlan*>(&*(cur++));
00171         if (current_flowplan->getQuantity() < 0)
00172         {
00173           consumed -= current_flowplan->getQuantity();
00174           noConsumers = false;
00175         }
00176         else if (current_flowplan->getOperationPlan()->getLocked())
00177           produced += current_flowplan->getQuantity();
00178       }
00179       // Loop to pick up the last consuming flowplan on the given date
00180       while (cur != b->getFlowPlans().end() && cur->getDate() == current_date);
00181       // No further interest in dates with only producing flowplans.
00182       if (noConsumers) continue;
00183     }
00184 
00185     // Compute current inventory. The actual onhand in the buffer may be
00186     // different since we count only consumers and *locked* producers.
00187     current_inventory = produced - consumed;
00188 
00189     // Hard limit: respect minimum interval
00190     if (current_date < earliest_next)
00191     {
00192       if (current_inventory < -ROUNDING_ERROR
00193         && current_date >= data->state->q_date
00194         && b->getMinimumInterval()
00195         && data->state->a_date > earliest_next
00196         && data->getSolver()->isMaterialConstrained()
00197         && data->constrainedPlanning)
00198           // The inventory goes negative here and we can't procure more
00199           // material because of the minimum interval...
00200           data->state->a_date = earliest_next;
00201       continue;
00202     }
00203 
00204     // Now the normal reorder check
00205     if (current_inventory >= b->getMinimumInventory()
00206       && current_date < latest_next)
00207     {
00208       if (current_date == earliest_next) earliest_next = Date::infinitePast;
00209       continue;
00210     }
00211 
00212     // When we are within the minimum interval, we may need to increase the
00213     // size of the latest procurement.
00214     if (current_date == earliest_next
00215       && last_operationplan
00216       && current_inventory < b->getMinimumInventory())
00217     {
00218       double origqty = last_operationplan->getQuantity();
00219       last_operationplan->setQuantity(suggestQuantity(b,
00220         last_operationplan->getQuantity()
00221           + b->getMinimumInventory() - current_inventory));
00222       produced += last_operationplan->getQuantity() - origqty;
00223       current_inventory = produced - consumed;
00224       if (current_inventory < -ROUNDING_ERROR
00225         && data->state->a_date > earliest_next + b->getMinimumInterval()
00226         && earliest_next + b->getMinimumInterval() > data->state->q_date
00227         && data->getSolver()->isMaterialConstrained()
00228         && data->constrainedPlanning)
00229           // Resizing didn't work, and we still have shortage
00230           data->state->a_date = earliest_next + b->getMinimumInterval();
00231     }
00232 
00233     // At this point, we know we need to reorder...
00234     earliest_next = Date::infinitePast;
00235     double order_qty = suggestQuantity(b,
00236       b->getMaximumInventory() - current_inventory);
00237     if (order_qty > 0)
00238     {
00239       // Create a procurement or update an existing one
00240       if (curProcure == OperationPlan::end())
00241       {
00242         // No existing procurement can be reused. Create a new one.
00243         CommandCreateOperationPlan *a =
00244           new CommandCreateOperationPlan(b->getOperation(), order_qty,
00245             Date::infinitePast, current_date, data->state->curDemand);
00246         last_operationplan = a->getOperationPlan();
00247         last_operationplan->insertInOperationplanList();
00248         produced += last_operationplan->getQuantity();
00249         data->add(a);
00250       }
00251       else if (curProcure->getDates().getEnd() == current_date
00252         && curProcure->getQuantity() == order_qty)
00253       {
00254         // We can reuse this existing procurement unchanged.
00255         produced += order_qty;
00256         last_operationplan = &*curProcure;
00257         moved.insert(last_operationplan);
00258         do
00259           ++curProcure;
00260         while (curProcure != OperationPlan::end()
00261           && curProcure->getLocked() && moved.find(&*curProcure)!=moved.end());
00262       }
00263       else
00264       {
00265         // Update an existing procurement to meet current needs
00266         CommandMoveOperationPlan *a =
00267           new CommandMoveOperationPlan(&*curProcure, Date::infinitePast, current_date, order_qty);
00268         last_operationplan = a->getOperationPlan();
00269         moved.insert(last_operationplan);
00270         data->add(a);
00271         produced += last_operationplan->getQuantity();
00272         do
00273           ++curProcure;
00274         while (curProcure != OperationPlan::end()
00275           && curProcure->getLocked() && moved.find(&*curProcure)!=moved.end());
00276       }
00277       if (b->getMinimumInterval())
00278         earliest_next = current_date + b->getMinimumInterval();
00279     }
00280     if (b->getMaximumInterval())
00281     {
00282       current_inventory = produced - consumed;
00283       if (current_inventory >= b->getMaximumInventory()
00284         && cur == b->getFlowPlans().end())
00285         // Nothing happens any more further in the future.
00286         // Abort procuring based on the max inteval
00287         latest_next = Date::infiniteFuture;
00288       else
00289         latest_next = current_date + b->getMaximumInterval();
00290     }
00291   }
00292 
00293   // Get rid of extra procurements that have become redundant
00294   while (curProcure != OperationPlan::end())
00295   {
00296     OperationPlan *opplan = &*(curProcure++);
00297     if (!opplan->getLocked() && moved.find(opplan)!=moved.end())
00298       data->add(new CommandDeleteOperationPlan(opplan));
00299   }
00300 
00301   // Create the answer
00302   if (data->constrainedPlanning && (data->getSolver()->isFenceConstrained()
00303     || data->getSolver()->isLeadtimeConstrained()
00304     || data->getSolver()->isMaterialConstrained()))
00305   {
00306     // Check if the inventory drops below zero somewhere
00307     double shortage = 0;
00308     for (Buffer::flowplanlist::const_iterator cur=b->getFlowPlans().begin();
00309       cur != b->getFlowPlans().end(); ++cur)
00310       if (cur->getDate() >= data->state->q_date
00311         && cur->getOnhand() < -ROUNDING_ERROR
00312         && cur->getOnhand() < shortage)
00313       {
00314         shortage = cur->getOnhand();
00315         if (-shortage >= data->state->q_qty) break;
00316       }
00317     if (shortage < 0)
00318     {
00319       // Answer a shorted quantity
00320       data->state->a_qty = data->state->q_qty + shortage;
00321       // Nothing to promise...
00322       if (data->state->a_qty < 0) data->state->a_qty = 0;
00323       // Check the reply date
00324       if (data->constrainedPlanning)
00325       {
00326         if (data->getSolver()->isFenceConstrained()
00327           && data->state->q_date < Plan::instance().getCurrent() + b->getFence()
00328           && data->state->a_date > Plan::instance().getCurrent() + b->getFence())
00329           data->state->a_date = Plan::instance().getCurrent() + b->getFence();
00330         if (data->getSolver()->isLeadtimeConstrained()
00331           && data->state->q_date < Plan::instance().getCurrent() + b->getLeadtime()
00332           && data->state->a_date > Plan::instance().getCurrent() + b->getLeadtime())
00333           data->state->a_date = Plan::instance().getCurrent() + b->getLeadtime();
00334       }
00335     }
00336     else
00337       // Answer the full quantity
00338       data->state->a_qty = data->state->q_qty;
00339   }
00340   else
00341     // Answer the full quantity
00342     data->state->a_qty = data->state->q_qty;
00343 
00344   // Increment the cost
00345   if (b->getItem() && data->state->a_qty > 0.0)
00346     data->state->a_cost += data->state->a_qty * b->getItem()->getPrice();
00347 
00348   // Message
00349   if (data->getSolver()->getLogLevel()>1)
00350     logger << indent(b->getLevel()) << "  Procurement buffer '" << b
00351     << "' answers: " << data->state->a_qty << "  " << data->state->a_date
00352     << "  " << data->state->a_cost << "  " << data->state->a_penalty << endl;
00353 }
00354 
00355 
00356 }
Generated by  doxygen 1.6.2-20100208