solverdemand.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/solverdemand.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 
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   if (!deliveryoper)
00079     throw DataException("Demand '" + l->getName() + "' can't be planned");
00080 
00081   // Planning loop
00082   do
00083   {
00084     // Message
00085     if (loglevel>0)
00086       logger << "Demand '" << l << "' asks: "
00087       << plan_qty << "  " << plan_date << endl;
00088 
00089     // Store the last command in the list, in order to undo the following
00090     // commands if required.
00091     Command* topcommand = data->getLastCommand();
00092 
00093     // Plan the demand by asking the delivery operation to plan
00094     data->state->curBuffer = NULL;
00095     data->state->q_qty = plan_qty;
00096     data->state->q_date = plan_date;
00097     data->state->curDemand = const_cast<Demand*>(l);
00098     deliveryoper->solve(*this,v);
00099 
00100     // Message
00101     if (loglevel>0)
00102       logger << "Demand '" << l << "' gets answer: "
00103       << data->state->a_qty << "  " << data->state->a_date << "  "
00104       << data->state->a_cost << "  " << data->state->a_penalty << endl;
00105 
00106     // Update the date to plan in the next loop
00107     Date copy_plan_date = plan_date;
00108 
00109     // Compare the planned quantity with the minimum allowed shipment quantity
00110     // We don't accept the answer in case:
00111     // 1) Nothing is planned
00112     // 2) The planned quantity is less than the minimum shipment quantity
00113     // 3) The remaining quantity after accepting this answer is less than
00114     //    the minimum quantity.
00115     if (data->state->a_qty < ROUNDING_ERROR
00116       || data->state->a_qty + ROUNDING_ERROR < l->getMinShipment()
00117       || (plan_qty - data->state->a_qty < l->getMinShipment()
00118           && plan_qty - data->state->a_qty > ROUNDING_ERROR))
00119     {
00120       if (plan_qty - data->state->a_qty < l->getMinShipment()
00121         && data->state->a_qty + ROUNDING_ERROR >= l->getMinShipment()
00122         && data->state->a_qty > best_a_qty )
00123       {
00124         // The remaining quantity after accepting this answer is less than
00125         // the minimum quantity. Therefore, we delay accepting it now, but
00126         // still keep track of this best offer.
00127         best_a_qty = data->state->a_qty;
00128         best_q_qty = plan_qty;
00129         best_q_date = plan_date;
00130       }
00131 
00132       // Delete operationplans - Undo all changes
00133       data->undo(topcommand);
00134 
00135       // Set the ask date for the next pass through the loop
00136       if (data->state->a_date <= copy_plan_date)
00137       {
00138         // Oops, we didn't get a proper answer we can use for the next loop.
00139         // Print a warning and simply try one day later.
00140         if (loglevel>0)
00141           logger << "Warning: Demand '" << l << "': Lazy retry" << endl;
00142         plan_date = copy_plan_date + data->sol->getLazyDelay();
00143       }
00144       else
00145         // Use the next-date answer from the solver
00146         plan_date = data->state->a_date;
00147     }
00148     else
00149     {
00150       // Accepting this answer
00151       if (data->state->a_qty + ROUNDING_ERROR < plan_qty)
00152       {
00153         // The demand was only partially planned. We need to do a new
00154         // 'coordinated' planning run.
00155 
00156         // Delete operationplans created in the 'testing round'
00157         data->undo(topcommand);
00158 
00159         // Create the correct operationplans
00160         if (loglevel>=2) 
00161           logger << "Demand '" << l << "' plans coordination." << endl;
00162         data->getSolver()->setLogLevel(0);
00163         double tmpresult = data->state->a_qty;
00164         try
00165         {
00166           for(double remainder = data->state->a_qty;
00167             remainder > ROUNDING_ERROR; remainder -= data->state->a_qty)
00168           {
00169             data->state->q_qty = remainder;
00170             data->state->q_date = copy_plan_date;
00171             data->state->curDemand = const_cast<Demand*>(l);
00172             data->state->curBuffer = NULL;
00173             deliveryoper->solve(*this,v);
00174             if (data->state->a_qty < ROUNDING_ERROR)
00175             {
00176               logger << "Warning: Demand '" << l << "': Failing coordination" << endl;
00177               break;
00178             }
00179           }
00180         }
00181         catch (...)
00182         {
00183           data->getSolver()->setLogLevel(loglevel);
00184           throw;
00185         }
00186         data->getSolver()->setLogLevel(loglevel);
00187         data->state->a_qty = tmpresult;
00188       }
00189 
00190       // Register the new operationplans. We need to make sure that the
00191       // correct execute method is called!
00192       if (data->getSolver()->getAutocommit())
00193         data->CommandList::execute();
00194 
00195       // Update the quantity to plan in the next loop
00196       plan_qty -= data->state->a_qty;
00197       best_a_qty = 0.0;  // Reset 'best-answer' remember
00198     }
00199 
00200   }
00201   // Repeat while there is still a quantity left to plan and we aren't
00202   // exceeding the maximum delivery delay.
00203   while (plan_qty > ROUNDING_ERROR
00204     && ((data->getSolver()->getPlanType() != 2 && plan_date < l->getDue() + l->getMaxLateness()) 
00205         || (data->getSolver()->getPlanType() == 2 && !data->constrainedPlanning && plan_date < l->getDue() + l->getMaxLateness()) 
00206         || (data->getSolver()->getPlanType() == 2 && data->constrainedPlanning && plan_date == l->getDue())
00207     ));
00208 
00209   // Accept the best possible answer.
00210   // We may have skipped it in the previous loop, awaiting a still better answer
00211   if (best_a_qty > 0.0 && data->constrainedPlanning)
00212   {
00213     if (loglevel>=2) logger << "Demand '" << l << "' accepts a best answer." << endl;
00214     data->getSolver()->setLogLevel(0);
00215     try
00216     {
00217       for(double remainder = best_q_qty;
00218         remainder > ROUNDING_ERROR; remainder -= data->state->a_qty)
00219       {
00220         data->state->q_qty = remainder;
00221         data->state->q_date = best_q_date;
00222         data->state->curDemand = const_cast<Demand*>(l);
00223         data->state->curBuffer = NULL;
00224         deliveryoper->solve(*this,v);
00225         if (data->state->a_qty < ROUNDING_ERROR)
00226         {
00227           logger << "Warning: Demand '" << l << "': Failing accepting best answer" << endl;
00228           break;
00229         }
00230       }
00231       if (data->getSolver()->getAutocommit())
00232         data->CommandList::execute();
00233     }
00234     catch (...)
00235     {
00236       data->getSolver()->setLogLevel(loglevel);
00237       throw;
00238     }
00239     data->getSolver()->setLogLevel(loglevel);
00240   }
00241 }
00242 
00243 }