solverbuffer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2012 by Johan De Taeye, frePPLe bvba *
4  * *
5  * This library is free software; you can redistribute it and/or modify it *
6  * under the terms of the GNU Affero General Public License as published *
7  * by the Free Software Foundation; either version 3 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU Affero General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Affero General Public *
16  * License along with this program. *
17  * If not, see <http://www.gnu.org/licenses/>. *
18  * *
19  ***************************************************************************/
20 
21 #define FREPPLE_CORE
22 #include "frepple/solver.h"
23 
24 namespace frepple
25 {
26 
27 
28 /** @todo The flow quantity is handled at the wrong place. It needs to be
29  * handled by the operation, since flows can exist on multiple suboperations
30  * with different quantities. The buffer solve can't handle this, because
31  * it only calls the solve() for the producing operation...
32  * Are there some situations where the operation solver doesn't know enough
33  * on the buffer behavior???
34  */
35 DECLARE_EXPORT void SolverMRP::solve(const Buffer* b, void* v)
36 {
37  SolverMRPdata* data = static_cast<SolverMRPdata*>(v);
38  Date requested_date(data->state->q_date);
39  double requested_qty(data->state->q_qty);
40  bool tried_requested_date(false);
41 
42  // Call the user exit
43  if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning));
44 
45  // Message
46  if (data->getSolver()->getLogLevel()>1)
47  logger << indent(b->getLevel()) << " Buffer '" << b->getName()
48  << "' is asked: " << data->state->q_qty << " " << data->state->q_date << endl;
49 
50  // Store the last command in the list, in order to undo the following
51  // commands if required.
52  CommandManager::Bookmark* topcommand = data->setBookmark();
53 
54  // Make sure the new operationplans don't inherit an owner.
55  // When an operation calls the solve method of suboperations, this field is
56  // used to pass the information about the owner operationplan down. When
57  // solving for buffers we must make sure NOT to pass owner information.
58  // At the end of solving for a buffer we need to restore the original
59  // settings...
60  OperationPlan *prev_owner_opplan = data->state->curOwnerOpplan;
61  data->state->curOwnerOpplan = NULL;
62 
63  // Evaluate the buffer profile and solve shortages by asking more material.
64  // The loop goes from the requested date till the very end. Whenever the
65  // event date changes, we evaluate if a shortage exists.
66  Date currentDate;
67  const TimeLine<FlowPlan>::Event *prev = NULL;
68  double shortage(0.0);
69  Date extraSupplyDate(Date::infiniteFuture);
70  Date extraInventoryDate(Date::infiniteFuture);
71  double cumproduced = b->getFlowPlans().rbegin()->getCumulativeProduced();
72  double current_minimum(0.0);
73  double unconfirmed_supply(0.0);
74  for (Buffer::flowplanlist::const_iterator cur=b->getFlowPlans().begin();
75  ; ++cur)
76  {
77  const FlowPlan* fplan = dynamic_cast<const FlowPlan*>(&*cur);
78  if (fplan && !fplan->getOperationPlan()->getIdentifier()
79  && fplan->getQuantity()>0
81  unconfirmed_supply += fplan->getQuantity();
82 
83  // Iterator has now changed to a new date or we have arrived at the end.
84  // If multiple flows are at the same moment in time, we are not interested
85  // in the inventory changes. It gets interesting only when a certain
86  // inventory level remains unchanged for a certain time.
87  if ((cur == b->getFlowPlans().end() || cur->getDate()>currentDate) && prev)
88  {
89  // Some variables
90  Date theDate = prev->getDate();
91  double theOnHand = prev->getOnhand();
92  double theDelta = theOnHand - current_minimum + shortage;
93 
94  // Evaluate the situation at the last flowplan before the date change.
95  // Is there a shortage at that date?
96  if (theDelta < -ROUNDING_ERROR)
97  {
98  // Can we get extra supply to solve the problem, or part of it?
99  // If the shortage already starts before the requested date, it
100  // was not created by the newly added flowplan, but existed before.
101  // We don't consider this as a shortage for the current flowplan,
102  // and we want our flowplan to try to repair the previous problems
103  // if it can...
104  bool loop = true;
105  while (b->getProducingOperation() && theDate >= requested_date && loop)
106  {
107  // Create supply
108  data->state->curBuffer = const_cast<Buffer*>(b);
109  data->state->q_qty = -theDelta;
110  data->state->q_date = prev->getDate();
111 
112  // Check whether this date doesn't match with the requested date.
113  // See a bit further why this is required.
114  if (data->state->q_date == requested_date) tried_requested_date = true;
115 
116  // Note that the supply created with the next line changes the
117  // onhand value at all later dates!
118  b->getProducingOperation()->solve(*this,v);
119 
120  // Evaluate the reply date. The variable extraSupplyDate will store
121  // the date when the producing operation tells us it can get extra
122  // supply.
123  if (data->state->a_date < extraSupplyDate
124  && data->state->a_date > requested_date)
125  extraSupplyDate = data->state->a_date;
126 
127  // If we got some extra supply, we retry to get some more supply.
128  // Only when no extra material is obtained, we give up.
129  if (data->state->a_qty > ROUNDING_ERROR
130  && data->state->a_qty < -theDelta - ROUNDING_ERROR)
131  theDelta += data->state->a_qty;
132  else
133  loop = false;
134  }
135 
136  // Not enough supply was received to repair the complete problem
137  if (prev->getOnhand() + shortage < -ROUNDING_ERROR)
138  {
139  // Keep track of the shorted quantity.
140  // Only consider shortages later than the requested date.
141  if (theDate >= requested_date)
142  shortage = -prev->getOnhand();
143 
144  // Reset the date from which excess material is in the buffer. This
145  // excess material can be used to compute the date when the buffer
146  // can be asked again for additional supply.
147  extraInventoryDate = Date::infiniteFuture;
148  }
149  }
150  else if (theDelta > unconfirmed_supply + ROUNDING_ERROR)
151  // There is excess material at this date (coming from planned/frozen
152  // material arrivals, surplus material created by lotsized operations,
153  // etc...)
154  // The unconfirmed_supply element is required to exclude any of the
155  // excess inventory we may have caused ourselves. Such situations are
156  // possible when there are loops in the supply chain.
157  if (theDate > requested_date
158  && extraInventoryDate == Date::infiniteFuture)
159  extraInventoryDate = theDate;
160  }
161 
162  // We have reached the end of the flowplans. Breaking out of the loop
163  // needs to be done here because in the next statements we are accessing
164  // *cur, which isn't valid at the end of the list
165  if (cur == b->getFlowPlans().end()) break;
166 
167  // The minimum or the maximum have changed
168  // Note that these limits can be updated only after the processing of the
169  // date change in the statement above. Otherwise the code above would
170  // already use the new value before the intended date.
171  if (cur->getType() == 3) current_minimum = cur->getMin();
172 
173  // Update the pointer to the previous flowplan.
174  prev = &*cur;
175  currentDate = cur->getDate();
176  }
177 
178  // Note: the variable extraInventoryDate now stores the date from which
179  // excess material is available in the buffer. The excess
180  // We don't need to care how much material is lying there.
181 
182  // Check for supply at the requested date
183  // Isn't this included in the normal loop? In some cases it is indeed, but
184  // sometimes it isn't because in the normal loop there may still have been
185  // onhand available and the shortage only shows at a later date than the
186  // requested date.
187  // E.g. Initial situation: After extra consumer at time y:
188  // -------+ --+
189  // | |
190  // +------ +---+
191  // |
192  // 0 -------y------ 0 --y---x-----
193  // |
194  // +-----
195  // The first loop only checks for supply at times x and later. If it is not
196  // feasible, we now check for supply at time y. It will create some extra
197  // inventory, but at least the demand is met.
198  // @todo The buffer solver could move backward in time from x till time y,
199  // and try multiple dates. This would minimize the excess inventory created.
200  while (shortage > ROUNDING_ERROR
201  && b->getProducingOperation() && !tried_requested_date)
202  {
203  // Create supply at the requested date
204  data->state->curBuffer = const_cast<Buffer*>(b);
205  data->state->q_qty = shortage;
206  data->state->q_date = requested_date;
207  // Note that the supply created with the next line changes the onhand value
208  // at all later dates!
209  // Note that asking at the requested date doesn't keep the material on
210  // stock to a minimum.
211  b->getProducingOperation()->solve(*this,v);
212  // Evaluate the reply
213  if (data->state->a_date < extraSupplyDate
214  && data->state->a_date > requested_date)
215  extraSupplyDate = data->state->a_date;
216  if (data->state->a_qty > ROUNDING_ERROR)
217  shortage -= data->state->a_qty;
218  else
219  tried_requested_date = true;
220  }
221 
222  // Final evaluation of the replenishment
223  if (data->constrainedPlanning && data->getSolver()->isConstrained())
224  {
225  // Use the constrained planning result
226  data->state->a_qty = requested_qty - shortage;
227  if (data->state->a_qty < ROUNDING_ERROR)
228  {
229  data->rollback(topcommand);
230  data->state->a_qty = 0.0;
231  }
232  data->state->a_date = (extraInventoryDate < extraSupplyDate) ?
233  extraInventoryDate :
234  extraSupplyDate;
235  // Monitor as a constraint if there is no producing operation.
236  // Note that if there is a producing operation the constraint is flagged
237  // on the operation instead of on this buffer.
238  if (!b->getProducingOperation() && data->logConstraints && shortage > ROUNDING_ERROR)
239  data->planningDemand->getConstraints().push(ProblemMaterialShortage::metadata,
240  b, requested_date, Date::infiniteFuture, shortage);
241  }
242  else
243  {
244  // Enough inventory or supply available, or not material constrained.
245  // In case of a plan that is not material constrained, the buffer tries to
246  // solve for shortages as good as possible. Only in the end we 'lie' about
247  // the result to the calling function. Material shortages will then remain
248  // in the buffer.
249  data->state->a_qty = requested_qty;
250  data->state->a_date = Date::infiniteFuture;
251  }
252 
253  // Restore the owning operationplan.
254  data->state->curOwnerOpplan = prev_owner_opplan;
255 
256  // Reply quantity must be greater than 0
257  assert( data->state->a_qty >= 0 );
258 
259  // Increment the cost
260  // Only the quantity consumed directly from the buffer is counted.
261  // The cost of the material supply taken from producing operations is
262  // computed seperately and not considered here.
263  if (b->getItem() && data->state->a_qty > 0)
264  {
265  cumproduced = b->getFlowPlans().rbegin()->getCumulativeProduced() - cumproduced;
266  if (data->state->a_qty > cumproduced)
267  data->state->a_cost += (data->state->a_qty - cumproduced) * b->getItem()->getPrice();
268  }
269 
270  // Message
271  if (data->getSolver()->getLogLevel()>1)
272  logger << indent(b->getLevel()) << " Buffer '" << b->getName()
273  << "' answers: " << data->state->a_qty << " " << data->state->a_date << " "
274  << data->state->a_cost << " " << data->state->a_penalty << endl;
275 }
276 
277 
279 {
280  SolverMRPdata* data = static_cast<SolverMRPdata*>(v);
281 
282  // Call the user exit
283  if (userexit_buffer) userexit_buffer.call(b, PythonObject(data->constrainedPlanning));
284 
285  // Message
286  if (data->getSolver()->getLogLevel()>1)
287  logger << indent(b->getLevel()) << " Infinite buffer '" << b << "' is asked: "
288  << data->state->q_qty << " " << data->state->q_date << endl;
289 
290  // Reply whatever is requested, regardless of date, quantity or supply.
291  // The demand is not propagated upstream either.
292  data->state->a_qty = data->state->q_qty;
293  data->state->a_date = data->state->q_date;
294  if (b->getItem())
295  data->state->a_cost += data->state->q_qty * b->getItem()->getPrice();
296 
297  // Message
298  if (data->getSolver()->getLogLevel()>1)
299  logger << indent(b->getLevel()) << " Infinite buffer '" << b << "' answers: "
300  << data->state->a_qty << " " << data->state->a_date << " "
301  << data->state->a_cost << " " << data->state->a_penalty << endl;
302 }
303 
304 
305 }