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