LotSizingDataModel.Solution 2.0.1
Solution objects for production, setup, inventory and related decisions.
Loading...
Searching...
No Matches
DistributionDecision.cs
Go to the documentation of this file.
1using System;
2using System.ComponentModel;
3using System.Linq;
4using System.Xml.Serialization;
5using LotSizingDataModel.Core.Common;
6using LotSizingDataModel.Core.PhysicalModel;
7
9
10/// <summary>
11/// Stores the distribution decisions associated with one
12/// distribution center, one item and one warehouse over the
13/// complete planning horizon.
14/// </summary>
15/// <remarks>
16/// Period numbers are one-based.
17///
18/// Demand values, selling prices, shortage costs and backlog
19/// costs belong to the supply-chain instance and are not
20/// duplicated in this solution object.
21/// </remarks>
22[Serializable]
23[XmlType(TypeName = "distributionDecision")]
24public sealed class DistributionDecision :
25 ModelObject,
26 IPlanningHorizonAware
27{
28 private int _distributionCenterId;
29 private int _itemId;
30
31 private WarehouseReference _warehouse =
32 new();
33
34 private DoubleTimeSeries _deliveredQuantities =
35 new();
36
37 private DoubleTimeSeries _backlogLevels =
38 new();
39
40 private DoubleTimeSeries _shortageQuantities =
41 new();
42
43 /// <summary>
44 /// Initializes an empty distribution decision.
45 /// </summary>
46 /// <remarks>
47 /// This constructor is required by
48 /// <see cref="XmlSerializer"/>.
49 /// </remarks>
51 {
52 SubscribeToObject(_warehouse);
53 SubscribeToObject(_deliveredQuantities);
54 SubscribeToObject(_backlogLevels);
55 SubscribeToObject(_shortageQuantities);
56 }
57
58 /// <summary>
59 /// Initializes a distribution decision for a distribution
60 /// center, an item, a warehouse and a planning horizon.
61 /// </summary>
62 /// <param name="distributionCenterId">
63 /// Identifier of the distribution center.
64 /// </param>
65 /// <param name="itemId">
66 /// Identifier of the distributed item.
67 /// </param>
68 /// <param name="warehouse">
69 /// Warehouse from which the item is delivered.
70 /// </param>
71 /// <param name="planningHorizon">
72 /// Strictly positive number of planning periods.
73 /// </param>
75 int distributionCenterId,
76 int itemId,
77 WarehouseReference warehouse,
78 int planningHorizon)
79 : this()
80 {
81 if (distributionCenterId <= 0)
82 {
83 throw new ArgumentOutOfRangeException(
84 nameof(distributionCenterId),
85 distributionCenterId,
86 "The distribution-center identifier must be " +
87 "strictly positive.");
88 }
89
90 if (itemId <= 0)
91 {
92 throw new ArgumentOutOfRangeException(
93 nameof(itemId),
94 itemId,
95 "The item identifier must be strictly positive.");
96 }
97
98 ArgumentNullException.ThrowIfNull(warehouse);
99
100 if (planningHorizon <= 0)
101 {
102 throw new ArgumentOutOfRangeException(
103 nameof(planningHorizon),
104 planningHorizon,
105 "The planning horizon must be strictly positive.");
106 }
107
109 distributionCenterId;
110
111 ItemId = itemId;
112 Warehouse = warehouse;
113
114 ResizeTimeSeries(planningHorizon);
115 }
116
117 /// <summary>
118 /// Gets or sets the identifier of the distribution center.
119 /// </summary>
120 [XmlAttribute("distributionCenterId")]
122 {
123 get => _distributionCenterId;
124 set
125 {
126 /*
127 * Zero is tolerated for an empty object created
128 * by XmlSerializer. The solution validator will
129 * require a strictly positive identifier.
130 */
131 if (value < 0)
132 {
133 throw new ArgumentOutOfRangeException(
134 nameof(value),
135 value,
136 "The distribution-center identifier " +
137 "cannot be negative.");
138 }
139
140 SetProperty(
141 ref _distributionCenterId,
142 value);
143 }
144 }
145
146 /// <summary>
147 /// Gets or sets the identifier of the distributed item.
148 /// </summary>
149 [XmlAttribute("itemId")]
150 public int ItemId
151 {
152 get => _itemId;
153 set
154 {
155 /*
156 * Zero is tolerated during XML deserialization.
157 */
158 if (value < 0)
159 {
160 throw new ArgumentOutOfRangeException(
161 nameof(value),
162 value,
163 "The item identifier cannot be negative.");
164 }
165
166 SetProperty(
167 ref _itemId,
168 value);
169 }
170 }
171
172 /// <summary>
173 /// Gets or sets the warehouse from which the item
174 /// is delivered.
175 /// </summary>
176 [XmlElement("warehouse")]
177 public WarehouseReference Warehouse
178 {
179 get => _warehouse;
180 set
181 {
182 ArgumentNullException.ThrowIfNull(value);
183
184 if (ReferenceEquals(
185 _warehouse,
186 value))
187 {
188 return;
189 }
190
191 UnsubscribeFromObject(_warehouse);
192
193 SetProperty(
194 ref _warehouse,
195 value);
196
197 SubscribeToObject(_warehouse);
198
199 NotifyDerivedProperties();
200 }
201 }
202
203 /// <summary>
204 /// Gets or sets the quantities delivered to customers
205 /// during each planning period.
206 /// </summary>
207 /// <remarks>
208 /// Values must be finite and non-negative.
209 /// </remarks>
210 [XmlElement("deliveredQuantities")]
211 public DoubleTimeSeries DeliveredQuantities
212 {
213 get => _deliveredQuantities;
214 set
215 {
216 ArgumentNullException.ThrowIfNull(value);
217
218 if (ReferenceEquals(
219 _deliveredQuantities,
220 value))
221 {
222 return;
223 }
224
225 UnsubscribeFromObject(
226 _deliveredQuantities);
227
228 SetProperty(
229 ref _deliveredQuantities,
230 value);
231
232 SubscribeToObject(
233 _deliveredQuantities);
234
235 NotifyDerivedProperties();
236 }
237 }
238
239 /// <summary>
240 /// Gets or sets the outstanding demand at the end
241 /// of each planning period.
242 /// </summary>
243 /// <remarks>
244 /// Values must be finite and non-negative.
245 ///
246 /// A backlog is assumed to remain eligible for fulfillment
247 /// during a later planning period.
248 /// </remarks>
249 [XmlElement("backlogLevels")]
250 public DoubleTimeSeries BacklogLevels
251 {
252 get => _backlogLevels;
253 set
254 {
255 ArgumentNullException.ThrowIfNull(value);
256
257 if (ReferenceEquals(
258 _backlogLevels,
259 value))
260 {
261 return;
262 }
263
264 UnsubscribeFromObject(
265 _backlogLevels);
266
267 SetProperty(
268 ref _backlogLevels,
269 value);
270
271 SubscribeToObject(
272 _backlogLevels);
273
274 NotifyDerivedProperties();
275 }
276 }
277
278 /// <summary>
279 /// Gets or sets the quantities of demand that are
280 /// definitively not fulfilled during each planning period.
281 /// </summary>
282 /// <remarks>
283 /// Values must be finite and non-negative.
284 ///
285 /// Unlike a backlog, a shortage quantity is not carried
286 /// forward to a later planning period. When lost sales are
287 /// not permitted by the model, this series remains zero.
288 /// </remarks>
289 [XmlElement("shortageQuantities")]
290 public DoubleTimeSeries ShortageQuantities
291 {
292 get => _shortageQuantities;
293 set
294 {
295 ArgumentNullException.ThrowIfNull(value);
296
297 if (ReferenceEquals(
298 _shortageQuantities,
299 value))
300 {
301 return;
302 }
303
304 UnsubscribeFromObject(
305 _shortageQuantities);
306
307 SetProperty(
308 ref _shortageQuantities,
309 value);
310
311 SubscribeToObject(
312 _shortageQuantities);
313
314 NotifyDerivedProperties();
315 }
316 }
317
318 /// <summary>
319 /// Gets the number of planning periods represented
320 /// by the delivered-quantity series.
321 /// </summary>
322 [XmlIgnore]
323 public int PlanningHorizon =>
324 DeliveredQuantities.PeriodCount;
325
326 /// <summary>
327 /// Gets a value indicating whether all decision series
328 /// use the same planning horizon.
329 /// </summary>
330 [XmlIgnore]
332 BacklogLevels.PeriodCount ==
334 ShortageQuantities.PeriodCount ==
336
337 /// <summary>
338 /// Gets a value indicating whether every delivered
339 /// quantity is finite and non-negative.
340 /// </summary>
341 [XmlIgnore]
344 quantity =>
345 double.IsFinite(quantity) &&
346 quantity >= 0.0);
347
348 /// <summary>
349 /// Gets a value indicating whether every backlog level
350 /// is finite and non-negative.
351 /// </summary>
352 [XmlIgnore]
354 BacklogLevels.All(
355 backlog =>
356 double.IsFinite(backlog) &&
357 backlog >= 0.0);
358
359 /// <summary>
360 /// Gets a value indicating whether every shortage
361 /// quantity is finite and non-negative.
362 /// </summary>
363 [XmlIgnore]
366 shortage =>
367 double.IsFinite(shortage) &&
368 shortage >= 0.0);
369
370 /// <summary>
371 /// Gets a value indicating whether the warehouse
372 /// reference is initialized.
373 /// </summary>
374 [XmlIgnore]
375 public bool HasValidWarehouse =>
376 Warehouse.ReferenceId > 0;
377
378 /// <summary>
379 /// Gets a value indicating whether the distribution
380 /// decision is internally consistent.
381 /// </summary>
382 /// <remarks>
383 /// This property does not verify that the distribution
384 /// center, item and warehouse exist in a particular
385 /// supply-chain instance.
386 /// </remarks>
387 [XmlIgnore]
388 public bool IsInternallyValid =>
390 ItemId > 0 &&
391 PlanningHorizon > 0 &&
397
398 /// <summary>
399 /// Gets the quantity delivered during a planning period.
400 /// </summary>
401 /// <param name="period">
402 /// One-based planning period.
403 /// </param>
404 /// <returns>
405 /// Delivered quantity recorded for the period.
406 /// </returns>
407 public double GetDeliveredQuantity(int period)
408 {
409 return DeliveredQuantities[period];
410 }
411
412 /// <summary>
413 /// Sets the quantity delivered during a planning period.
414 /// </summary>
415 /// <param name="period">
416 /// One-based planning period.
417 /// </param>
418 /// <param name="quantity">
419 /// Finite and non-negative delivered quantity.
420 /// </param>
422 int period,
423 double quantity)
424 {
425 ValidateNonNegativeFiniteValue(
426 quantity,
427 nameof(quantity));
428
429 DeliveredQuantities[period] =
430 quantity;
431 }
432
433 /// <summary>
434 /// Gets the backlog level at the end of a planning period.
435 /// </summary>
436 /// <param name="period">
437 /// One-based planning period.
438 /// </param>
439 /// <returns>
440 /// Non-negative backlog level.
441 /// </returns>
442 public double GetBacklogLevel(int period)
443 {
444 return BacklogLevels[period];
445 }
446
447 /// <summary>
448 /// Sets the backlog level at the end of a planning period.
449 /// </summary>
450 /// <param name="period">
451 /// One-based planning period.
452 /// </param>
453 /// <param name="backlog">
454 /// Finite and non-negative backlog level.
455 /// </param>
456 public void SetBacklogLevel(
457 int period,
458 double backlog)
459 {
460 ValidateNonNegativeFiniteValue(
461 backlog,
462 nameof(backlog));
463
464 BacklogLevels[period] =
465 backlog;
466 }
467
468 /// <summary>
469 /// Gets the shortage quantity for a planning period.
470 /// </summary>
471 /// <param name="period">
472 /// One-based planning period.
473 /// </param>
474 /// <returns>
475 /// Non-negative shortage quantity.
476 /// </returns>
477 public double GetShortageQuantity(int period)
478 {
479 return ShortageQuantities[period];
480 }
481
482 /// <summary>
483 /// Sets the shortage quantity for a planning period.
484 /// </summary>
485 /// <param name="period">
486 /// One-based planning period.
487 /// </param>
488 /// <param name="shortage">
489 /// Finite and non-negative shortage quantity.
490 /// </param>
492 int period,
493 double shortage)
494 {
495 ValidateNonNegativeFiniteValue(
496 shortage,
497 nameof(shortage));
498
499 ShortageQuantities[period] =
500 shortage;
501 }
502
503 /// <summary>
504 /// Determines whether this decision identifies the
505 /// specified distribution center, item and warehouse.
506 /// </summary>
507 /// <param name="distributionCenterId">
508 /// Distribution-center identifier to compare.
509 /// </param>
510 /// <param name="itemId">
511 /// Item identifier to compare.
512 /// </param>
513 /// <param name="warehouse">
514 /// Warehouse reference to compare.
515 /// </param>
516 /// <returns>
517 /// True when all key elements match; otherwise, false.
518 /// </returns>
519 public bool Matches(
520 int distributionCenterId,
521 int itemId,
522 WarehouseReference warehouse)
523 {
524 ArgumentNullException.ThrowIfNull(warehouse);
525
526 return DistributionCenterId ==
527 distributionCenterId &&
528 ItemId == itemId &&
529 SameWarehouse(
530 Warehouse,
531 warehouse);
532 }
533
534 /// <summary>
535 /// Resizes every decision series to the specified
536 /// planning horizon.
537 /// </summary>
538 /// <param name="periodCount">
539 /// Non-negative number of planning periods.
540 /// </param>
541 /// <remarks>
542 /// Existing values are preserved whenever possible.
543 /// New periods are initialized with zero.
544 /// </remarks>
545 public void ResizeTimeSeries(int periodCount)
546 {
547 if (periodCount < 0)
548 {
549 throw new ArgumentOutOfRangeException(
550 nameof(periodCount),
551 periodCount,
552 "The period count cannot be negative.");
553 }
554
555 DeliveredQuantities.Resize(
556 periodCount,
557 defaultValue: 0.0);
558
559 BacklogLevels.Resize(
560 periodCount,
561 defaultValue: 0.0);
562
563 ShortageQuantities.Resize(
564 periodCount,
565 defaultValue: 0.0);
566
567 NotifyDerivedProperties();
568 }
569
570 /// <summary>
571 /// Resets every distribution decision value to zero.
572 /// </summary>
573 public void Clear()
574 {
575 DeliveredQuantities.Fill(0.0);
576 BacklogLevels.Fill(0.0);
577 ShortageQuantities.Fill(0.0);
578
579 NotifyDerivedProperties();
580 }
581
582 /// <inheritdoc/>
583 public override string ToString()
584 {
585 double totalDeliveredQuantity =
587
588 double finalBacklog =
591 : 0.0;
592
593 double totalShortage =
594 ShortageQuantities.Sum();
595
596 return
597 $"Distribution center {DistributionCenterId}, " +
598 $"item {ItemId}, warehouse " +
599 $"{FormatWarehouse(Warehouse)}: " +
600 $"delivered {totalDeliveredQuantity}; " +
601 $"final backlog {finalBacklog}; " +
602 $"shortage {totalShortage}";
603 }
604
605 private void SubscribeToObject(
606 ModelObject modelObject)
607 {
608 modelObject.PropertyChanged +=
609 OnNestedPropertyChanged;
610 }
611
612 private void UnsubscribeFromObject(
613 ModelObject modelObject)
614 {
615 modelObject.PropertyChanged -=
616 OnNestedPropertyChanged;
617 }
618
619 private void OnNestedPropertyChanged(
620 object? sender,
621 PropertyChangedEventArgs eventArgs)
622 {
623 NotifyDerivedProperties();
624 }
625
626 private void NotifyDerivedProperties()
627 {
628 OnPropertyChanged(
629 nameof(PlanningHorizon));
630
631 OnPropertyChanged(
633
634 OnPropertyChanged(
636
637 OnPropertyChanged(
638 nameof(HasValidBacklogLevels));
639
640 OnPropertyChanged(
642
643 OnPropertyChanged(
644 nameof(HasValidWarehouse));
645
646 OnPropertyChanged(
647 nameof(IsInternallyValid));
648 }
649
650 private static bool SameWarehouse(
651 WarehouseReference first,
652 WarehouseReference second)
653 {
654 return first.Kind ==
655 second.Kind &&
656 first.ReferenceId ==
657 second.ReferenceId;
658 }
659
660 private static string FormatWarehouse(
661 WarehouseReference warehouse)
662 {
663 return
664 $"{warehouse.Kind}:{warehouse.ReferenceId}";
665 }
666
667 private static void ValidateNonNegativeFiniteValue(
668 double value,
669 string parameterName)
670 {
671 if (!double.IsFinite(value) ||
672 value < 0.0)
673 {
674 throw new ArgumentOutOfRangeException(
675 parameterName,
676 value,
677 "The value must be finite and non-negative.");
678 }
679 }
680}
DoubleTimeSeries ShortageQuantities
Gets or sets the quantities of demand that are definitively not fulfilled during each planning period...
DistributionDecision(int distributionCenterId, int itemId, WarehouseReference warehouse, int planningHorizon)
Initializes a distribution decision for a distribution center, an item, a warehouse and a planning ho...
void SetBacklogLevel(int period, double backlog)
Sets the backlog level at the end of a planning period.
int PlanningHorizon
Gets the number of planning periods represented by the delivered-quantity series.
bool HasValidWarehouse
Gets a value indicating whether the warehouse reference is initialized.
DoubleTimeSeries DeliveredQuantities
Gets or sets the quantities delivered to customers during each planning period.
double GetBacklogLevel(int period)
Gets the backlog level at the end of a planning period.
DistributionDecision()
Initializes an empty distribution decision.
double GetDeliveredQuantity(int period)
Gets the quantity delivered during a planning period.
WarehouseReference Warehouse
Gets or sets the warehouse from which the item is delivered.
bool HasValidDeliveredQuantities
Gets a value indicating whether every delivered quantity is finite and non-negative.
DoubleTimeSeries BacklogLevels
Gets or sets the outstanding demand at the end of each planning period.
void SetDeliveredQuantity(int period, double quantity)
Sets the quantity delivered during a planning period.
int ItemId
Gets or sets the identifier of the distributed item.
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
bool HasValidBacklogLevels
Gets a value indicating whether every backlog level is finite and non-negative.
void Clear()
Resets every distribution decision value to zero.
int DistributionCenterId
Gets or sets the identifier of the distribution center.
double GetShortageQuantity(int period)
Gets the shortage quantity for a planning period.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all decision series use the same planning horizon.
bool IsInternallyValid
Gets a value indicating whether the distribution decision is internally consistent.
bool Matches(int distributionCenterId, int itemId, WarehouseReference warehouse)
Determines whether this decision identifies the specified distribution center, item and warehouse.
bool HasValidShortageQuantities
Gets a value indicating whether every shortage quantity is finite and non-negative.
void SetShortageQuantity(int period, double shortage)
Sets the shortage quantity for a planning period.