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