solverprocure.cpp
Go to the documentation of this file.
00001 /*************************************************************************** 00002 file : $URL: http://svn.code.sf.net/p/frepple/code/trunk/src/solver/solverprocure.cpp $ 00003 version : $LastChangedRevision: 1713 $ $LastChangedBy: jdetaeye $ 00004 date : $LastChangedDate: 2012-07-18 11:46:01 +0200 (Wed, 18 Jul 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 Affero General Public License as published * 00013 * by the Free Software Foundation; either version 3 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 * 00019 * GNU Affero General Public License for more details. * 00020 * * 00021 * You should have received a copy of the GNU Affero General Public * 00022 * License along with this program. * 00023 * If not, see <http://www.gnu.org/licenses/>. * 00024 * * 00025 ***************************************************************************/ 00026 00027 #define FREPPLE_CORE 00028 #include "frepple/solver.h" 00029 00030 namespace frepple 00031 { 00032 00033 00034 double suggestQuantity(const BufferProcure* b, double f) 00035 { 00036 // Standard answer 00037 double order_qty = f; 00038 00039 // Round to a multiple 00040 if (b->getSizeMultiple()>0.0) 00041 { 00042 int mult = static_cast<int>(order_qty / b->getSizeMultiple() + 0.99999999); 00043 order_qty = mult * b->getSizeMultiple(); 00044 } 00045 00046 // Respect minimum size 00047 if (order_qty < b->getSizeMinimum()) 00048 { 00049 order_qty = b->getSizeMinimum(); 00050 // round up to multiple 00051 if (b->getSizeMultiple()>0.0) 00052 { 00053 int mult = static_cast<int>(order_qty / b->getSizeMultiple() + 0.99999999); 00054 order_qty = mult * b->getSizeMultiple(); 00055 } 00056 // if now bigger than max -> infeasible 00057 if (order_qty > b->getSizeMaximum()) 00058 throw DataException("Inconsistent procurement parameters on buffer '" 00059 + b->getName() + "'"); 00060 } 00061 00062 // Respect maximum size 00063 if (order_qty > b->getSizeMaximum()) 00064 { 00065 order_qty = b->getSizeMaximum(); 00066 // round down 00067 if (b->getSizeMultiple()>0.0) 00068 { 00069 int mult = static_cast<int>(order_qty / b->getSizeMultiple()); 00070 order_qty = mult * b->getSizeMultiple(); 00071 } 00072 // if now smaller than min -> infeasible 00073 if (order_qty < b->getSizeMinimum()) 00074 throw DataException("Inconsistent procurement parameters on buffer '" 00075 + b->getName() + "'"); 00076 } 00077 00078 // Reply 00079 return order_qty; 00080 } 00081 00082 00083 DECLARE_EXPORT void SolverMRP::solve(const BufferProcure* b, void* v) 00084 { 00085 SolverMRPdata* data = static_cast<SolverMRPdata*>(v); 00086 00087 // TODO create a more performant procurement solver. Instead of creating a list of operationplans 00088 // moves and creations, we can create a custom command "updateProcurements". The commit of 00089 // this command will update the operationplans. 00090 // The solve method is only worried about getting a Yes/No reply. The reply is almost always yes, 00091 // except a) when the request is inside max(current + the lead time, latest procurement + min time 00092 // after locked procurement), or b) when the min time > 0 and max qty > 0 00093 00094 // Call the user exit 00095 if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning)); 00096 00097 // Message 00098 if (data->getSolver()->getLogLevel()>1) 00099 logger << indent(b->getLevel()) << " Procurement buffer '" << b->getName() 00100 << "' is asked: " << data->state->q_qty << " " << data->state->q_date << endl; 00101 00102 // Standard reply date 00103 data->state->a_date = Date::infiniteFuture; 00104 00105 // Initialize an iterator over reusable existing procurements 00106 OperationPlan *last_operationplan = NULL; 00107 OperationPlan::iterator curProcure(b->getOperation()); 00108 while (curProcure != OperationPlan::end() && curProcure->getLocked()) 00109 ++curProcure; 00110 set<OperationPlan*> moved; 00111 00112 // Find the latest locked procurement operation. It is used to know what 00113 // the earliest date is for a new procurement. 00114 Date earliest_next; 00115 for (OperationPlan::iterator procs(b->getOperation()); 00116 procs != OperationPlan::end(); ++procs) 00117 if (procs->getLocked()) 00118 earliest_next = procs->getDates().getEnd(); 00119 Date latest_next = Date::infiniteFuture; 00120 00121 // Find constraints on earliest and latest date for the next procurement 00122 if (earliest_next && b->getMaximumInterval()) 00123 latest_next = earliest_next + b->getMaximumInterval(); 00124 if (earliest_next && b->getMinimumInterval()) 00125 earliest_next += b->getMinimumInterval(); 00126 if (data->constrainedPlanning) 00127 { 00128 if (data->getSolver()->isLeadtimeConstrained() 00129 && earliest_next < Plan::instance().getCurrent() + b->getLeadtime()) 00130 earliest_next = Plan::instance().getCurrent() + b->getLeadtime(); 00131 if (data->getSolver()->isFenceConstrained() 00132 && earliest_next < Plan::instance().getCurrent() + b->getFence()) 00133 earliest_next = Plan::instance().getCurrent() + b->getFence(); 00134 } 00135 00136 // Loop through all flowplans 00137 Date current_date; 00138 double produced = 0.0; 00139 double consumed = 0.0; 00140 double current_inventory = 0.0; 00141 const FlowPlan* current_flowplan = NULL; 00142 for (Buffer::flowplanlist::const_iterator cur=b->getFlowPlans().begin(); 00143 latest_next != Date::infiniteFuture || cur != b->getFlowPlans().end(); ) 00144 { 00145 if (cur==b->getFlowPlans().end() || latest_next < cur->getDate()) 00146 { 00147 // Latest procument time is reached 00148 current_date = latest_next; 00149 current_flowplan = NULL; 00150 } 00151 else if (earliest_next && earliest_next < cur->getDate()) 00152 { 00153 // Earliest procument time was reached 00154 current_date = earliest_next; 00155 current_flowplan = NULL; 00156 } 00157 else 00158 { 00159 // Date with flowplans found 00160 if (current_date && current_date >= cur->getDate()) 00161 { 00162 // When procurements are being moved, it happens that we revisit the 00163 // same consuming flowplans twice. This check catches this case. 00164 cur++; 00165 continue; 00166 } 00167 current_date = cur->getDate(); 00168 bool noConsumers = true; 00169 do 00170 { 00171 if (cur->getType() != 1) 00172 { 00173 cur++; 00174 continue; 00175 } 00176 current_flowplan = static_cast<const FlowPlan*>(&*(cur++)); 00177 if (current_flowplan->getQuantity() < 0) 00178 { 00179 consumed -= current_flowplan->getQuantity(); 00180 noConsumers = false; 00181 } 00182 else if (current_flowplan->getOperationPlan()->getLocked()) 00183 produced += current_flowplan->getQuantity(); 00184 } 00185 // Loop to pick up the last consuming flowplan on the given date 00186 while (cur != b->getFlowPlans().end() && cur->getDate() == current_date); 00187 // No further interest in dates with only producing flowplans. 00188 if (noConsumers) continue; 00189 } 00190 00191 // Compute current inventory. The actual onhand in the buffer may be 00192 // different since we count only consumers and *locked* producers. 00193 current_inventory = produced - consumed; 00194 00195 // Hard limit: respect minimum interval 00196 if (current_date < earliest_next) 00197 { 00198 if (current_inventory < -ROUNDING_ERROR 00199 && current_date >= data->state->q_date 00200 && b->getMinimumInterval() 00201 && data->state->a_date > earliest_next 00202 && data->getSolver()->isMaterialConstrained() 00203 && data->constrainedPlanning) 00204 // The inventory goes negative here and we can't procure more 00205 // material because of the minimum interval... 00206 data->state->a_date = earliest_next; 00207 continue; 00208 } 00209 00210 // Now the normal reorder check 00211 if (current_inventory >= b->getMinimumInventory() 00212 && current_date < latest_next) 00213 { 00214 if (current_date == earliest_next) earliest_next = Date::infinitePast; 00215 continue; 00216 } 00217 00218 // When we are within the minimum interval, we may need to increase the 00219 // size of the latest procurement. 00220 if (current_date == earliest_next 00221 && last_operationplan 00222 && current_inventory < b->getMinimumInventory()) 00223 { 00224 double origqty = last_operationplan->getQuantity(); 00225 last_operationplan->setQuantity(suggestQuantity(b, 00226 last_operationplan->getQuantity() 00227 + b->getMinimumInventory() - current_inventory)); 00228 produced += last_operationplan->getQuantity() - origqty; 00229 current_inventory = produced - consumed; 00230 if (current_inventory < -ROUNDING_ERROR 00231 && data->state->a_date > earliest_next + b->getMinimumInterval() 00232 && earliest_next + b->getMinimumInterval() > data->state->q_date 00233 && data->getSolver()->isMaterialConstrained() 00234 && data->constrainedPlanning) 00235 // Resizing didn't work, and we still have shortage 00236 data->state->a_date = earliest_next + b->getMinimumInterval(); 00237 } 00238 00239 // At this point, we know we need to reorder... 00240 earliest_next = Date::infinitePast; 00241 double order_qty = suggestQuantity(b, 00242 b->getMaximumInventory() - current_inventory); 00243 if (order_qty > 0) 00244 { 00245 // Create a procurement or update an existing one 00246 if (curProcure == OperationPlan::end()) 00247 { 00248 // No existing procurement can be reused. Create a new one. 00249 CommandCreateOperationPlan *a = 00250 new CommandCreateOperationPlan(b->getOperation(), order_qty, 00251 Date::infinitePast, current_date, data->state->curDemand); 00252 last_operationplan = a->getOperationPlan(); 00253 a->getOperationPlan()->setMotive(data->state->motive); 00254 last_operationplan->insertInOperationplanList(); // TODO Not very nice: unregistered opplan in the list! 00255 produced += last_operationplan->getQuantity(); 00256 data->add(a); 00257 } 00258 else if (curProcure->getDates().getEnd() == current_date 00259 && curProcure->getQuantity() == order_qty) 00260 { 00261 // We can reuse this existing procurement unchanged. 00262 produced += order_qty; 00263 last_operationplan = &*curProcure; 00264 moved.insert(last_operationplan); 00265 do 00266 ++curProcure; 00267 while (curProcure != OperationPlan::end() 00268 && curProcure->getLocked() && moved.find(&*curProcure)!=moved.end()); 00269 } 00270 else 00271 { 00272 // Update an existing procurement to meet current needs 00273 CommandMoveOperationPlan *a = 00274 new CommandMoveOperationPlan(&*curProcure, Date::infinitePast, current_date, order_qty); 00275 last_operationplan = a->getOperationPlan(); 00276 moved.insert(last_operationplan); 00277 data->add(a); 00278 produced += last_operationplan->getQuantity(); 00279 do 00280 ++curProcure; 00281 while (curProcure != OperationPlan::end() 00282 && curProcure->getLocked() && moved.find(&*curProcure)!=moved.end()); 00283 } 00284 if (b->getMinimumInterval()) 00285 earliest_next = current_date + b->getMinimumInterval(); 00286 } 00287 if (b->getMaximumInterval()) 00288 { 00289 current_inventory = produced - consumed; 00290 if (current_inventory >= b->getMaximumInventory() 00291 && cur == b->getFlowPlans().end()) 00292 // Nothing happens any more further in the future. 00293 // Abort procuring based on the max inteval 00294 latest_next = Date::infiniteFuture; 00295 else 00296 latest_next = current_date + b->getMaximumInterval(); 00297 } 00298 } 00299 00300 // Get rid of extra procurements that have become redundant 00301 while (curProcure != OperationPlan::end()) 00302 { 00303 OperationPlan *opplan = &*(curProcure++); 00304 if (!opplan->getLocked() && moved.find(opplan)!=moved.end()) 00305 data->add(new CommandDeleteOperationPlan(opplan)); 00306 } 00307 00308 // Create the answer 00309 if (data->constrainedPlanning && (data->getSolver()->isFenceConstrained() 00310 || data->getSolver()->isLeadtimeConstrained() 00311 || data->getSolver()->isMaterialConstrained())) 00312 { 00313 // Check if the inventory drops below zero somewhere 00314 double shortage = 0; 00315 Date startdate; 00316 for (Buffer::flowplanlist::const_iterator cur = b->getFlowPlans().begin(); 00317 cur != b->getFlowPlans().end(); ++cur) 00318 if (cur->getDate() >= data->state->q_date 00319 && cur->getOnhand() < -ROUNDING_ERROR 00320 && cur->getOnhand() < shortage) 00321 { 00322 shortage = cur->getOnhand(); 00323 if (-shortage >= data->state->q_qty) break; 00324 if (startdate == Date::infinitePast) startdate = cur->getDate(); 00325 } 00326 if (shortage < 0) 00327 { 00328 // Answer a shorted quantity 00329 data->state->a_qty = data->state->q_qty + shortage; 00330 // Log a constraint 00331 if (data->logConstraints) 00332 data->planningDemand->getConstraints().push( 00333 ProblemMaterialShortage::metadata, b, startdate, Date::infiniteFuture, // @todo calculate a better end date 00334 -shortage); 00335 // Nothing to promise... 00336 if (data->state->a_qty < 0) data->state->a_qty = 0; 00337 // Check the reply date 00338 if (data->constrainedPlanning) 00339 { 00340 if (data->getSolver()->isFenceConstrained() 00341 && data->state->q_date < Plan::instance().getCurrent() + b->getFence() 00342 && data->state->a_date > Plan::instance().getCurrent() + b->getFence()) 00343 data->state->a_date = Plan::instance().getCurrent() + b->getFence(); 00344 if (data->getSolver()->isLeadtimeConstrained() 00345 && data->state->q_date < Plan::instance().getCurrent() + b->getLeadtime() 00346 && data->state->a_date > Plan::instance().getCurrent() + b->getLeadtime()) 00347 data->state->a_date = Plan::instance().getCurrent() + b->getLeadtime(); 00348 } 00349 } 00350 else 00351 // Answer the full quantity 00352 data->state->a_qty = data->state->q_qty; 00353 } 00354 else 00355 // Answer the full quantity 00356 data->state->a_qty = data->state->q_qty; 00357 00358 // Increment the cost 00359 if (b->getItem() && data->state->a_qty > 0.0) 00360 data->state->a_cost += data->state->a_qty * b->getItem()->getPrice(); 00361 00362 // Message 00363 if (data->getSolver()->getLogLevel()>1) 00364 logger << indent(b->getLevel()) << " Procurement buffer '" << b 00365 << "' answers: " << data->state->a_qty << " " << data->state->a_date 00366 << " " << data->state->a_cost << " " << data->state->a_penalty << endl; 00367 } 00368 00369 00370 }