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