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