solverdemand.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/trunk/src/solver/solverdemand.cpp $
00003   version : $LastChangedRevision: 1317 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2010-07-20 22:03:36 +0200 (Tue, 20 Jul 2010) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007-2010 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 
00032 namespace frepple
00033 {
00034 
00035 
00036 DECLARE_EXPORT void SolverMRP::solve(const Demand* l, void* v)
00037 {
00038   // Call the user exit
00039   SolverMRPdata* data = static_cast<SolverMRPdata*>(v);
00040   if (userexit_demand) userexit_demand.call(l, PythonObject(data->constrainedPlanning));
00041   unsigned int loglevel = data->getSolver()->getLogLevel();
00042 
00043   // Note: This solver method does not push/pop states on the stack.
00044   // We continue to work on the top element of the stack.
00045 
00046   // Message
00047   if (data->getSolver()->getLogLevel()>0)
00048   {
00049     logger << "Planning demand '" << l->getName() << "' (" << l->getPriority()
00050     << ", " << l->getDue() << ", " << l->getQuantity() << ")";
00051     if (!data->constrainedPlanning || !data->getSolver()->isConstrained()) 
00052       logger << " in unconstrained mode";
00053     logger << endl;
00054   }
00055 
00056   // Unattach previous delivery operationplans.
00057   // Locked operationplans will NOT be deleted, and a part of the demand can
00058   // still remain planned.
00059   const_cast<Demand*>(l)->deleteOperationPlans(false, data);
00060 
00061   // Determine the quantity to be planned and the date for the planning loop
00062   double plan_qty = l->getQuantity() - l->getPlannedQuantity();
00063   Date plan_date = l->getDue();
00064 
00065   // Nothing to be planned any more (e.g. all deliveries are locked...)
00066   if (plan_qty < ROUNDING_ERROR)
00067   {
00068     if (loglevel>0) logger << "  Nothing to be planned." << endl;
00069     return;
00070   }
00071 
00072   // Temporary values to store the 'best-reply' so far
00073   double best_q_qty = 0.0, best_a_qty = 0.0;
00074   Date best_q_date;
00075 
00076   // Which operation to use?
00077   Operation* deliveryoper = l->getDeliveryOperation();
00078   string problemtext = string("Demand '") + l->getName() + "' has no delivery operation";
00079   if (!deliveryoper)
00080   {
00081     // Create a problem
00082     new ProblemInvalidData(const_cast<Demand*>(l), problemtext, "demand", 
00083       l->getDue(), l->getDue(), l->getQuantity());
00084     // Abort planning of this demand
00085     throw DataException("Demand '" + l->getName() + "' can't be planned");
00086   }
00087   else
00088   {
00089     // Remove problem that may already exist
00090     for (Problem::const_iterator j = Problem::begin(const_cast<Demand*>(l), false); 
00091       j!=Problem::end(); ++j)
00092       if (&(j->getType()) == ProblemInvalidData::metadata 
00093           && j->getDescription() == problemtext)
00094       {
00095         delete &*j;
00096         break;
00097       }
00098   }
00099 
00100   // Planning loop
00101   do
00102   {
00103     // Message
00104     if (loglevel>0)
00105       logger << "Demand '" << l << "' asks: "
00106       << plan_qty << "  " << plan_date << endl;
00107 
00108     // Store the last command in the list, in order to undo the following
00109     // commands if required.
00110     Command* topcommand = data->getLastCommand();
00111 
00112     // Plan the demand by asking the delivery operation to plan
00113     data->state->curBuffer = NULL;
00114     data->state->q_qty = plan_qty;
00115     data->state->q_date = plan_date;
00116     data->state->curDemand = const_cast<Demand*>(l);
00117     deliveryoper->solve(*this,v);
00118 
00119     // Message
00120     if (loglevel>0)
00121       logger << "Demand '" << l << "' gets answer: "
00122       << data->state->a_qty << "  " << data->state->a_date << "  "
00123       << data->state->a_cost << "  " << data->state->a_penalty << endl;
00124 
00125     // Update the date to plan in the next loop
00126     Date copy_plan_date = plan_date;
00127 
00128     // Compare the planned quantity with the minimum allowed shipment quantity
00129     // We don't accept the answer in case:
00130     // 1) Nothing is planned
00131     // 2) The planned quantity is less than the minimum shipment quantity
00132     // 3) The remaining quantity after accepting this answer is less than
00133     //    the minimum quantity.
00134     if (data->state->a_qty < ROUNDING_ERROR
00135       || data->state->a_qty + ROUNDING_ERROR < l->getMinShipment()
00136       || (plan_qty - data->state->a_qty < l->getMinShipment()
00137           && plan_qty - data->state->a_qty > ROUNDING_ERROR))
00138     {
00139       if (plan_qty - data->state->a_qty < l->getMinShipment()
00140         && data->state->a_qty + ROUNDING_ERROR >= l->getMinShipment()
00141         && data->state->a_qty > best_a_qty )
00142       {
00143         // The remaining quantity after accepting this answer is less than
00144         // the minimum quantity. Therefore, we delay accepting it now, but
00145         // still keep track of this best offer.
00146         best_a_qty = data->state->a_qty;
00147         best_q_qty = plan_qty;
00148         best_q_date = plan_date;
00149       }
00150 
00151       // Delete operationplans - Undo all changes
00152       data->undo(topcommand);
00153 
00154       // Set the ask date for the next pass through the loop
00155       if (data->state->a_date <= copy_plan_date)
00156       {
00157         // Oops, we didn't get a proper answer we can use for the next loop.
00158         // Print a warning and simply try one day later.
00159         if (loglevel>0)
00160           logger << "Warning: Demand '" << l << "': Lazy retry" << endl;
00161         plan_date = copy_plan_date + data->sol->getLazyDelay();
00162       }
00163       else
00164         // Use the next-date answer from the solver
00165         plan_date = data->state->a_date;
00166     }
00167     else
00168     {
00169       // Accepting this answer
00170       if (data->state->a_qty + ROUNDING_ERROR < plan_qty)
00171       {
00172         // The demand was only partially planned. We need to do a new
00173         // 'coordinated' planning run.
00174 
00175         // Delete operationplans created in the 'testing round'
00176         data->undo(topcommand);
00177 
00178         // Create the correct operationplans
00179         if (loglevel>=2) 
00180           logger << "Demand '" << l << "' plans coordination." << endl;
00181         data->getSolver()->setLogLevel(0);
00182         double tmpresult = data->state->a_qty;
00183         try
00184         {
00185           for(double remainder = data->state->a_qty;
00186             remainder > ROUNDING_ERROR; remainder -= data->state->a_qty)
00187           {
00188             data->state->q_qty = remainder;
00189             data->state->q_date = copy_plan_date;
00190             data->state->curDemand = const_cast<Demand*>(l);
00191             data->state->curBuffer = NULL;
00192             deliveryoper->solve(*this,v);
00193             if (data->state->a_qty < ROUNDING_ERROR)
00194             {
00195               logger << "Warning: Demand '" << l << "': Failing coordination" << endl;
00196               break;
00197             }
00198           }
00199         }
00200         catch (...)
00201         {
00202           data->getSolver()->setLogLevel(loglevel);
00203           throw;
00204         }
00205         data->getSolver()->setLogLevel(loglevel);
00206         data->state->a_qty = tmpresult;
00207       }
00208 
00209       // Register the new operationplans. We need to make sure that the
00210       // correct execute method is called!
00211       if (data->getSolver()->getAutocommit())
00212       {
00213         data->getSolver()->scanExcess(data->getFirstCommand());
00214         data->CommandList::execute();
00215       }
00216 
00217 
00218       // Update the quantity to plan in the next loop
00219       plan_qty -= data->state->a_qty;
00220       best_a_qty = 0.0;  // Reset 'best-answer' remember
00221     }
00222 
00223   }
00224   // Repeat while there is still a quantity left to plan and we aren't
00225   // exceeding the maximum delivery delay.
00226   while (plan_qty > ROUNDING_ERROR
00227     && ((data->getSolver()->getPlanType() != 2 && plan_date < l->getDue() + l->getMaxLateness()) 
00228         || (data->getSolver()->getPlanType() == 2 && !data->constrainedPlanning && plan_date < l->getDue() + l->getMaxLateness()) 
00229         || (data->getSolver()->getPlanType() == 2 && data->constrainedPlanning && plan_date == l->getDue())
00230     ));
00231 
00232   // Accept the best possible answer.
00233   // We may have skipped it in the previous loop, awaiting a still better answer
00234   if (best_a_qty > 0.0 && data->constrainedPlanning)
00235   {
00236     if (loglevel>=2) logger << "Demand '" << l << "' accepts a best answer." << endl;
00237     data->getSolver()->setLogLevel(0);
00238     try
00239     {
00240       for(double remainder = best_q_qty;
00241         remainder > ROUNDING_ERROR; remainder -= data->state->a_qty)
00242       {
00243         data->state->q_qty = remainder;
00244         data->state->q_date = best_q_date;
00245         data->state->curDemand = const_cast<Demand*>(l);
00246         data->state->curBuffer = NULL;
00247         deliveryoper->solve(*this,v);
00248         if (data->state->a_qty < ROUNDING_ERROR)
00249         {
00250           logger << "Warning: Demand '" << l << "': Failing accepting best answer" << endl;
00251           break;
00252         }
00253       }
00254       if (data->getSolver()->getAutocommit())
00255       {
00256         data->getSolver()->scanExcess(data->getFirstCommand());
00257         data->CommandList::execute();
00258       }
00259     }
00260     catch (...)
00261     {
00262       data->getSolver()->setLogLevel(loglevel);
00263       throw;
00264     }
00265     data->getSolver()->setLogLevel(loglevel);
00266   }
00267 }
00268 
00269 
00270 DECLARE_EXPORT void SolverMRP::scanExcess(Command* cmd)
00271 {
00272   // Loop over all newly created operationplans found in the command stack
00273   for(; cmd; cmd = cmd->getNext())
00274   {
00275     CommandCreateOperationPlan* createcmd = dynamic_cast<CommandCreateOperationPlan*>(cmd);
00276     if (!createcmd)
00277     {           
00278       // If the command is a list: recursively call this function
00279       if (dynamic_cast<CommandList*>(cmd))
00280         scanExcess(dynamic_cast<CommandList*>(cmd)->getFirstCommand());
00281       //else: Not a command creating an operationplan: move on
00282     }
00283     else
00284     {
00285       // Detect excess operationplans and undo them
00286       if (createcmd->getOperationPlan() && createcmd->getOperationPlan()->isExcess())
00287       {
00288         if (getLogLevel())
00289           logger << "Denying creation of redundant operationplan " 
00290             << createcmd->getOperationPlan()->getOperation() << "  " 
00291             << createcmd->getOperationPlan()->getDates() << "  " 
00292             << createcmd->getOperationPlan()->getQuantity() << endl;
00293         createcmd->undo();
00294       }
00295     }
00296   }
00297 }
00298 
00299 }

Documentation generated for frePPLe by  doxygen