LotSizingDataModel.Solution 2.0.1
Solution objects for production, setup, inventory and related decisions.
Loading...
Searching...
No Matches
ProductionDecision.cs
Go to the documentation of this file.
1using System;
2using System.ComponentModel;
3using System.Linq;
4using System.Xml.Serialization;
5using LotSizingDataModel.Core.Common;
6
8
9/// <summary>
10/// Stores the production decisions associated with one
11/// production routing over the complete planning horizon.
12/// </summary>
13/// <remarks>
14/// Period numbers are one-based.
15///
16/// The decision may originate from an exact optimization method,
17/// a heuristic, a metaheuristic, a manual plan or an imported file.
18/// </remarks>
19[Serializable]
20[XmlType(TypeName = "productionDecision")]
21public sealed class ProductionDecision :
22 ModelObject,
23 IPlanningHorizonAware
24{
25 private int _routingId;
26
27 private DoubleTimeSeries _quantities =
28 new();
29
30 private IntegerTimeSeries _setups =
31 new();
32
33 private IntegerTimeSeries _lotMultipleCounts =
34 new();
35
36 /// <summary>
37 /// Initializes an empty production decision.
38 /// </summary>
39 /// <remarks>
40 /// This constructor is required by
41 /// <see cref="XmlSerializer"/>.
42 /// </remarks>
44 {
45 SubscribeToSeries(_quantities);
46 SubscribeToSeries(_setups);
47 SubscribeToSeries(_lotMultipleCounts);
48 }
49
50 /// <summary>
51 /// Initializes a production decision for a routing
52 /// and a planning horizon.
53 /// </summary>
54 /// <param name="routingId">
55 /// Identifier of the production routing.
56 /// </param>
57 /// <param name="planningHorizon">
58 /// Strictly positive number of planning periods.
59 /// </param>
61 int routingId,
62 int planningHorizon)
63 : this()
64 {
65 if (routingId <= 0)
66 {
67 throw new ArgumentOutOfRangeException(
68 nameof(routingId),
69 routingId,
70 "The routing identifier must be strictly positive.");
71 }
72
73 if (planningHorizon <= 0)
74 {
75 throw new ArgumentOutOfRangeException(
76 nameof(planningHorizon),
77 planningHorizon,
78 "The planning horizon must be strictly positive.");
79 }
80
81 RoutingId = routingId;
82
83 ResizeTimeSeries(planningHorizon);
84 }
85
86 /// <summary>
87 /// Gets or sets the identifier of the production routing
88 /// associated with the decision.
89 /// </summary>
90 [XmlAttribute("routingId")]
91 public int RoutingId
92 {
93 get => _routingId;
94 set
95 {
96 /*
97 * Zero is tolerated for an empty object created
98 * by XmlSerializer. The solution validator will
99 * require a strictly positive identifier.
100 */
101 if (value < 0)
102 {
103 throw new ArgumentOutOfRangeException(
104 nameof(value),
105 value,
106 "The routing identifier cannot be negative.");
107 }
108
109 SetProperty(
110 ref _routingId,
111 value);
112 }
113 }
114
115 /// <summary>
116 /// Gets or sets the quantities produced through the routing.
117 /// </summary>
118 /// <remarks>
119 /// Values must be finite and non-negative.
120 /// </remarks>
121 [XmlElement("quantities")]
122 public DoubleTimeSeries Quantities
123 {
124 get => _quantities;
125 set
126 {
127 ArgumentNullException.ThrowIfNull(value);
128
129 if (ReferenceEquals(
130 _quantities,
131 value))
132 {
133 return;
134 }
135
136 UnsubscribeFromSeries(_quantities);
137
138 SetProperty(
139 ref _quantities,
140 value);
141
142 SubscribeToSeries(_quantities);
143
144 NotifyDerivedProperties();
145 }
146 }
147
148 /// <summary>
149 /// Gets or sets the binary routing-activation decisions.
150 /// </summary>
151 /// <remarks>
152 /// Each value must be zero or one.
153 /// </remarks>
154 [XmlElement("setups")]
155 public IntegerTimeSeries Setups
156 {
157 get => _setups;
158 set
159 {
160 ArgumentNullException.ThrowIfNull(value);
161
162 if (ReferenceEquals(
163 _setups,
164 value))
165 {
166 return;
167 }
168
169 UnsubscribeFromSeries(_setups);
170
171 SetProperty(
172 ref _setups,
173 value);
174
175 SubscribeToSeries(_setups);
176
177 NotifyDerivedProperties();
178 }
179 }
180
181 /// <summary>
182 /// Gets or sets the number of lot-size multiples
183 /// used during each planning period.
184 /// </summary>
185 /// <remarks>
186 /// Values must be non-negative integers.
187 ///
188 /// When no multiple-of-lot constraint applies to the routing,
189 /// the series may remain filled with zeros.
190 /// </remarks>
191 [XmlElement("lotMultipleCounts")]
192 public IntegerTimeSeries LotMultipleCounts
193 {
194 get => _lotMultipleCounts;
195 set
196 {
197 ArgumentNullException.ThrowIfNull(value);
198
199 if (ReferenceEquals(
200 _lotMultipleCounts,
201 value))
202 {
203 return;
204 }
205
206 UnsubscribeFromSeries(
207 _lotMultipleCounts);
208
209 SetProperty(
210 ref _lotMultipleCounts,
211 value);
212
213 SubscribeToSeries(
214 _lotMultipleCounts);
215
216 NotifyDerivedProperties();
217 }
218 }
219
220 /// <summary>
221 /// Gets the number of planning periods represented
222 /// by the production-quantity series.
223 /// </summary>
224 [XmlIgnore]
225 public int PlanningHorizon =>
226 Quantities.PeriodCount;
227
228 /// <summary>
229 /// Gets a value indicating whether all decision series
230 /// use the same planning horizon.
231 /// </summary>
232 [XmlIgnore]
234 Setups.PeriodCount ==
236 LotMultipleCounts.PeriodCount ==
238
239 /// <summary>
240 /// Gets a value indicating whether every production
241 /// quantity is finite and non-negative.
242 /// </summary>
243 [XmlIgnore]
244 public bool HasValidQuantities =>
245 Quantities.All(
246 quantity =>
247 double.IsFinite(quantity) &&
248 quantity >= 0.0);
249
250 /// <summary>
251 /// Gets a value indicating whether every setup value
252 /// is equal to zero or one.
253 /// </summary>
254 [XmlIgnore]
255 public bool HasValidSetupValues =>
256 Setups.All(
257 setup =>
258 setup is 0 or 1);
259
260 /// <summary>
261 /// Gets a value indicating whether every lot-multiple
262 /// count is non-negative.
263 /// </summary>
264 [XmlIgnore]
267 count => count >= 0);
268
269 /// <summary>
270 /// Gets a value indicating whether the production decision
271 /// is internally consistent.
272 /// </summary>
273 /// <remarks>
274 /// This property does not verify that the referenced routing
275 /// exists in a particular supply-chain instance.
276 /// </remarks>
277 [XmlIgnore]
278 public bool IsInternallyValid =>
279 RoutingId > 0 &&
280 PlanningHorizon > 0 &&
285
286 /// <summary>
287 /// Gets the production quantity for a planning period.
288 /// </summary>
289 /// <param name="period">
290 /// One-based planning period.
291 /// </param>
292 /// <returns>
293 /// Production quantity recorded for the period.
294 /// </returns>
295 public double GetQuantity(int period)
296 {
297 return Quantities[period];
298 }
299
300 /// <summary>
301 /// Sets the production quantity for a planning period.
302 /// </summary>
303 /// <param name="period">
304 /// One-based planning period.
305 /// </param>
306 /// <param name="quantity">
307 /// Finite and non-negative production quantity.
308 /// </param>
309 public void SetQuantity(
310 int period,
311 double quantity)
312 {
313 if (!double.IsFinite(quantity) ||
314 quantity < 0.0)
315 {
316 throw new ArgumentOutOfRangeException(
317 nameof(quantity),
318 quantity,
319 "The production quantity must be finite " +
320 "and non-negative.");
321 }
322
323 Quantities[period] = quantity;
324 }
325
326 /// <summary>
327 /// Determines whether production is activated during
328 /// a planning period.
329 /// </summary>
330 /// <param name="period">
331 /// One-based planning period.
332 /// </param>
333 /// <returns>
334 /// True when the setup value is one; otherwise, false.
335 /// </returns>
336 public bool IsSetupActivated(int period)
337 {
338 return Setups[period] == 1;
339 }
340
341 /// <summary>
342 /// Sets the production-activation decision for a period.
343 /// </summary>
344 /// <param name="period">
345 /// One-based planning period.
346 /// </param>
347 /// <param name="isActivated">
348 /// True to activate production; otherwise, false.
349 /// </param>
350 public void SetSetupActivated(
351 int period,
352 bool isActivated)
353 {
354 Setups[period] =
355 isActivated ? 1 : 0;
356 }
357
358 /// <summary>
359 /// Gets the number of lot-size multiples used during
360 /// a planning period.
361 /// </summary>
362 /// <param name="period">
363 /// One-based planning period.
364 /// </param>
365 /// <returns>
366 /// Non-negative number of lot-size multiples.
367 /// </returns>
368 public int GetLotMultipleCount(int period)
369 {
370 return LotMultipleCounts[period];
371 }
372
373 /// <summary>
374 /// Sets the number of lot-size multiples used during
375 /// a planning period.
376 /// </summary>
377 /// <param name="period">
378 /// One-based planning period.
379 /// </param>
380 /// <param name="count">
381 /// Non-negative integer count.
382 /// </param>
384 int period,
385 int count)
386 {
387 if (count < 0)
388 {
389 throw new ArgumentOutOfRangeException(
390 nameof(count),
391 count,
392 "The lot-multiple count cannot be negative.");
393 }
394
395 LotMultipleCounts[period] = count;
396 }
397
398 /// <summary>
399 /// Resizes every decision series to the specified
400 /// planning horizon.
401 /// </summary>
402 /// <param name="periodCount">
403 /// Non-negative number of planning periods.
404 /// </param>
405 /// <remarks>
406 /// Existing values are preserved whenever possible.
407 /// New periods are initialized with zero.
408 /// </remarks>
409 public void ResizeTimeSeries(int periodCount)
410 {
411 if (periodCount < 0)
412 {
413 throw new ArgumentOutOfRangeException(
414 nameof(periodCount),
415 periodCount,
416 "The period count cannot be negative.");
417 }
418
419 Quantities.Resize(
420 periodCount,
421 defaultValue: 0.0);
422
423 Setups.Resize(
424 periodCount,
425 defaultValue: 0);
426
427 LotMultipleCounts.Resize(
428 periodCount,
429 defaultValue: 0);
430
431 NotifyDerivedProperties();
432 }
433
434 /// <summary>
435 /// Resets every production decision value to zero.
436 /// </summary>
437 public void Clear()
438 {
439 Quantities.Fill(0.0);
440 Setups.Fill(0);
441 LotMultipleCounts.Fill(0);
442
443 NotifyDerivedProperties();
444 }
445
446 /// <inheritdoc/>
447 public override string ToString()
448 {
449 double totalQuantity =
450 Quantities.Sum();
451
452 int activatedPeriodCount =
453 Setups.Count(
454 setup => setup == 1);
455
456 return
457 $"Routing {RoutingId}: " +
458 $"total quantity {totalQuantity}; " +
459 $"activated periods {activatedPeriodCount}";
460 }
461
462 private void SubscribeToSeries(
463 ModelObject series)
464 {
465 series.PropertyChanged +=
466 OnSeriesPropertyChanged;
467 }
468
469 private void UnsubscribeFromSeries(
470 ModelObject series)
471 {
472 series.PropertyChanged -=
473 OnSeriesPropertyChanged;
474 }
475
476 private void OnSeriesPropertyChanged(
477 object? sender,
478 PropertyChangedEventArgs eventArgs)
479 {
480 NotifyDerivedProperties();
481 }
482
483 private void NotifyDerivedProperties()
484 {
485 OnPropertyChanged(
486 nameof(PlanningHorizon));
487
488 OnPropertyChanged(
490
491 OnPropertyChanged(
492 nameof(HasValidQuantities));
493
494 OnPropertyChanged(
495 nameof(HasValidSetupValues));
496
497 OnPropertyChanged(
499
500 OnPropertyChanged(
501 nameof(IsInternallyValid));
502 }
503}
int GetLotMultipleCount(int period)
Gets the number of lot-size multiples used during a planning period.
IntegerTimeSeries LotMultipleCounts
Gets or sets the number of lot-size multiples used during each planning period.
ProductionDecision(int routingId, int planningHorizon)
Initializes a production decision for a routing and a planning horizon.
bool HasValidQuantities
Gets a value indicating whether every production quantity is finite and non-negative.
int PlanningHorizon
Gets the number of planning periods represented by the production-quantity series.
void SetSetupActivated(int period, bool isActivated)
Sets the production-activation decision for a period.
bool HasValidLotMultipleCounts
Gets a value indicating whether every lot-multiple count is non-negative.
double GetQuantity(int period)
Gets the production quantity for a planning period.
IntegerTimeSeries Setups
Gets or sets the binary routing-activation decisions.
bool IsSetupActivated(int period)
Determines whether production is activated during a planning period.
void Clear()
Resets every production decision value to zero.
ProductionDecision()
Initializes an empty production decision.
bool HasValidSetupValues
Gets a value indicating whether every setup value is equal to zero or one.
void SetLotMultipleCount(int period, int count)
Sets the number of lot-size multiples used during a planning period.
int RoutingId
Gets or sets the identifier of the production routing associated with the decision.
void SetQuantity(int period, double quantity)
Sets the production quantity for a planning period.
bool IsInternallyValid
Gets a value indicating whether the production decision is internally consistent.
DoubleTimeSeries Quantities
Gets or sets the quantities produced through the routing.
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all decision series use the same planning horizon.