solverbuffer.cpp
Go to the documentation of this file.
00001 /*************************************************************************** 00002 file : $URL: http://svn.code.sf.net/p/frepple/code/trunk/src/solver/solverbuffer.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 /** @todo The flow quantity is handled at the wrong place. It needs to be 00035 * handled by the operation, since flows can exist on multiple suboperations 00036 * with different quantities. The buffer solve can't handle this, because 00037 * it only calls the solve() for the producing operation... 00038 * Are there some situations where the operation solver doesn't know enough 00039 * on the buffer behavior??? 00040 */ 00041 DECLARE_EXPORT void SolverMRP::solve(const Buffer* b, void* v) 00042 { 00043 SolverMRPdata* data = static_cast<SolverMRPdata*>(v); 00044 Date requested_date(data->state->q_date); 00045 double requested_qty(data->state->q_qty); 00046 bool tried_requested_date(false); 00047 00048 // Call the user exit 00049 if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning)); 00050 00051 // Message 00052 if (data->getSolver()->getLogLevel()>1) 00053 logger << indent(b->getLevel()) << " Buffer '" << b->getName() 00054 << "' is asked: " << data->state->q_qty << " " << data->state->q_date << endl; 00055 00056 // Store the last command in the list, in order to undo the following 00057 // commands if required. 00058 CommandManager::Bookmark* topcommand = data->setBookmark(); 00059 00060 // Make sure the new operationplans don't inherit an owner. 00061 // When an operation calls the solve method of suboperations, this field is 00062 // used to pass the information about the owner operationplan down. When 00063 // solving for buffers we must make sure NOT to pass owner information. 00064 // At the end of solving for a buffer we need to restore the original 00065 // settings... 00066 OperationPlan *prev_owner_opplan = data->state->curOwnerOpplan; 00067 data->state->curOwnerOpplan = NULL; 00068 00069 // Evaluate the buffer profile and solve shortages by asking more material. 00070 // The loop goes from the requested date till the very end. Whenever the 00071 // event date changes, we evaluate if a shortage exists. 00072 Date currentDate; 00073 const TimeLine<FlowPlan>::Event *prev = NULL; 00074 double shortage(0.0); 00075 Date extraSupplyDate(Date::infiniteFuture); 00076 Date extraInventoryDate(Date::infiniteFuture); 00077 double cumproduced = b->getFlowPlans().rbegin()->getCumulativeProduced(); 00078 double current_minimum(0.0); 00079 double unconfirmed_supply(0.0); 00080 for (Buffer::flowplanlist::const_iterator cur=b->getFlowPlans().begin(); 00081 ; ++cur) 00082 { 00083 const FlowPlan* fplan = dynamic_cast<const FlowPlan*>(&*cur); 00084 if (fplan && !fplan->getOperationPlan()->getIdentifier() 00085 && fplan->getQuantity()>0 00086 && fplan->getOperationPlan()->getOperation() != b->getProducingOperation()) 00087 unconfirmed_supply += fplan->getQuantity(); 00088 00089 // Iterator has now changed to a new date or we have arrived at the end. 00090 // If multiple flows are at the same moment in time, we are not interested 00091 // in the inventory changes. It gets interesting only when a certain 00092 // inventory level remains unchanged for a certain time. 00093 if ((cur == b->getFlowPlans().end() || cur->getDate()>currentDate) && prev) 00094 { 00095 // Some variables 00096 Date theDate = prev->getDate(); 00097 double theOnHand = prev->getOnhand(); 00098 double theDelta = theOnHand - current_minimum + shortage; 00099 00100 // Evaluate the situation at the last flowplan before the date change. 00101 // Is there a shortage at that date? 00102 if (theDelta < -ROUNDING_ERROR) 00103 { 00104 // Can we get extra supply to solve the problem, or part of it? 00105 // If the shortage already starts before the requested date, it 00106 // was not created by the newly added flowplan, but existed before. 00107 // We don't consider this as a shortage for the current flowplan, 00108 // and we want our flowplan to try to repair the previous problems 00109 // if it can... 00110 bool loop = true; 00111 while (b->getProducingOperation() && theDate >= requested_date && loop) 00112 { 00113 // Create supply 00114 data->state->curBuffer = const_cast<Buffer*>(b); 00115 data->state->q_qty = -theDelta; 00116 data->state->q_date = prev->getDate(); 00117 00118 // Check whether this date doesn't match with the requested date. 00119 // See a bit further why this is required. 00120 if (data->state->q_date == requested_date) tried_requested_date = true; 00121 00122 // Note that the supply created with the next line changes the 00123 // onhand value at all later dates! 00124 b->getProducingOperation()->solve(*this,v); 00125 00126 // Evaluate the reply date. The variable extraSupplyDate will store 00127 // the date when the producing operation tells us it can get extra 00128 // supply. 00129 if (data->state->a_date < extraSupplyDate 00130 && data->state->a_date > requested_date) 00131 extraSupplyDate = data->state->a_date; 00132 00133 // If we got some extra supply, we retry to get some more supply. 00134 // Only when no extra material is obtained, we give up. 00135 if (data->state->a_qty > ROUNDING_ERROR 00136 && data->state->a_qty < -theDelta - ROUNDING_ERROR) 00137 theDelta += data->state->a_qty; 00138 else 00139 loop = false; 00140 } 00141 00142 // Not enough supply was received to repair the complete problem 00143 if (prev->getOnhand() + shortage < -ROUNDING_ERROR) 00144 { 00145 // Keep track of the shorted quantity. 00146 // Only consider shortages later than the requested date. 00147 if (theDate >= requested_date) 00148 shortage = -prev->getOnhand(); 00149 00150 // Reset the date from which excess material is in the buffer. This 00151 // excess material can be used to compute the date when the buffer 00152 // can be asked again for additional supply. 00153 extraInventoryDate = Date::infiniteFuture; 00154 } 00155 } 00156 else if (theDelta > unconfirmed_supply + ROUNDING_ERROR) 00157 // There is excess material at this date (coming from planned/frozen 00158 // material arrivals, surplus material created by lotsized operations, 00159 // etc...) 00160 // The unconfirmed_supply element is required to exclude any of the 00161 // excess inventory we may have caused ourselves. Such situations are 00162 // possible when there are loops in the supply chain. 00163 if (theDate > requested_date 00164 && extraInventoryDate == Date::infiniteFuture) 00165 extraInventoryDate = theDate; 00166 } 00167 00168 // We have reached the end of the flowplans. Breaking out of the loop 00169 // needs to be done here because in the next statements we are accessing 00170 // *cur, which isn't valid at the end of the list 00171 if (cur == b->getFlowPlans().end()) break; 00172 00173 // The minimum or the maximum have changed 00174 // Note that these limits can be updated only after the processing of the 00175 // date change in the statement above. Otherwise the code above would 00176 // already use the new value before the intended date. 00177 if (cur->getType() == 3) current_minimum = cur->getMin(); 00178 00179 // Update the pointer to the previous flowplan. 00180 prev = &*cur; 00181 currentDate = cur->getDate(); 00182 } 00183 00184 // Note: the variable extraInventoryDate now stores the date from which 00185 // excess material is available in the buffer. The excess 00186 // We don't need to care how much material is lying there. 00187 00188 // Check for supply at the requested date 00189 // Isn't this included in the normal loop? In some cases it is indeed, but 00190 // sometimes it isn't because in the normal loop there may still have been 00191 // onhand available and the shortage only shows at a later date than the 00192 // requested date. 00193 // E.g. Initial situation: After extra consumer at time y: 00194 // -------+ --+ 00195 // | | 00196 // +------ +---+ 00197 // | 00198 // 0 -------y------ 0 --y---x----- 00199 // | 00200 // +----- 00201 // The first loop only checks for supply at times x and later. If it is not 00202 // feasible, we now check for supply at time y. It will create some extra 00203 // inventory, but at least the demand is met. 00204 // @todo The buffer solver could move backward in time from x till time y, 00205 // and try multiple dates. This would minimize the excess inventory created. 00206 while (shortage > ROUNDING_ERROR 00207 && b->getProducingOperation() && !tried_requested_date) 00208 { 00209 // Create supply at the requested date 00210 data->state->curBuffer = const_cast<Buffer*>(b); 00211 data->state->q_qty = shortage; 00212 data->state->q_date = requested_date; 00213 // Note that the supply created with the next line changes the onhand value 00214 // at all later dates! 00215 // Note that asking at the requested date doesn't keep the material on 00216 // stock to a minimum. 00217 b->getProducingOperation()->solve(*this,v); 00218 // Evaluate the reply 00219 if (data->state->a_date < extraSupplyDate 00220 && data->state->a_date > requested_date) 00221 extraSupplyDate = data->state->a_date; 00222 if (data->state->a_qty > ROUNDING_ERROR) 00223 shortage -= data->state->a_qty; 00224 else 00225 tried_requested_date = true; 00226 } 00227 00228 // Final evaluation of the replenishment 00229 if (data->constrainedPlanning && data->getSolver()->isConstrained()) 00230 { 00231 // Use the constrained planning result 00232 data->state->a_qty = requested_qty - shortage; 00233 if (data->state->a_qty < ROUNDING_ERROR) 00234 { 00235 data->rollback(topcommand); 00236 data->state->a_qty = 0.0; 00237 } 00238 data->state->a_date = (extraInventoryDate < extraSupplyDate) ? 00239 extraInventoryDate : 00240 extraSupplyDate; 00241 // Monitor as a constraint if there is no producing operation. 00242 // Note that if there is a producing operation the constraint is flagged 00243 // on the operation instead of on this buffer. 00244 if (!b->getProducingOperation() && data->logConstraints && shortage > ROUNDING_ERROR) 00245 data->planningDemand->getConstraints().push(ProblemMaterialShortage::metadata, 00246 b, requested_date, Date::infiniteFuture, shortage); 00247 } 00248 else 00249 { 00250 // Enough inventory or supply available, or not material constrained. 00251 // In case of a plan that is not material constrained, the buffer tries to 00252 // solve for shortages as good as possible. Only in the end we 'lie' about 00253 // the result to the calling function. Material shortages will then remain 00254 // in the buffer. 00255 data->state->a_qty = requested_qty; 00256 data->state->a_date = Date::infiniteFuture; 00257 } 00258 00259 // Restore the owning operationplan. 00260 data->state->curOwnerOpplan = prev_owner_opplan; 00261 00262 // Reply quantity must be greater than 0 00263 assert( data->state->a_qty >= 0 ); 00264 00265 // Increment the cost 00266 // Only the quantity consumed directly from the buffer is counted. 00267 // The cost of the material supply taken from producing operations is 00268 // computed seperately and not considered here. 00269 if (b->getItem() && data->state->a_qty > 0) 00270 { 00271 cumproduced = b->getFlowPlans().rbegin()->getCumulativeProduced() - cumproduced; 00272 if (data->state->a_qty > cumproduced) 00273 data->state->a_cost += (data->state->a_qty - cumproduced) * b->getItem()->getPrice(); 00274 } 00275 00276 // Message 00277 if (data->getSolver()->getLogLevel()>1) 00278 logger << indent(b->getLevel()) << " Buffer '" << b->getName() 00279 << "' answers: " << data->state->a_qty << " " << data->state->a_date << " " 00280 << data->state->a_cost << " " << data->state->a_penalty << endl; 00281 } 00282 00283 00284 DECLARE_EXPORT void SolverMRP::solve(const BufferInfinite* b, void* v) 00285 { 00286 SolverMRPdata* data = static_cast<SolverMRPdata*>(v); 00287 00288 // Call the user exit 00289 if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning)); 00290 00291 // Message 00292 if (data->getSolver()->getLogLevel()>1) 00293 logger << indent(b->getLevel()) << " Infinite buffer '" << b << "' is asked: " 00294 << data->state->q_qty << " " << data->state->q_date << endl; 00295 00296 // Reply whatever is requested, regardless of date, quantity or supply. 00297 // The demand is not propagated upstream either. 00298 data->state->a_qty = data->state->q_qty; 00299 data->state->a_date = data->state->q_date; 00300 if (b->getItem()) 00301 data->state->a_cost += data->state->q_qty * b->getItem()->getPrice(); 00302 00303 // Message 00304 if (data->getSolver()->getLogLevel()>1) 00305 logger << indent(b->getLevel()) << " Infinite buffer '" << b << "' answers: " 00306 << data->state->a_qty << " " << data->state->a_date << " " 00307 << data->state->a_cost << " " << data->state->a_penalty << endl; 00308 } 00309 00310 00311 }