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 }