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
