LotSizingDataModel.Solution 2.0.1
Solution objects for production, setup, inventory and related decisions.
Loading...
Searching...
No Matches
TransportDecision.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 transport decisions associated with one item,
12/// one transport resource and one directed warehouse lane
13/// over the complete planning horizon.
14/// </summary>
15/// <remarks>
16/// Period numbers are one-based.
17///
18/// This class stores item-specific transport decisions.
19/// Global transport-resource activation and global additional
20/// capacity are stored in a separate capacity decision.
21/// </remarks>
22[Serializable]
23[XmlType(TypeName = "transportDecision")]
24public sealed class TransportDecision :
25 ModelObject,
26 IPlanningHorizonAware
27{
28 private int _itemId;
29 private int _transportResourceId;
30
31 private WarehouseReference _origin =
32 new();
33
34 private WarehouseReference _destination =
35 new();
36
37 private DoubleTimeSeries _transportedQuantities =
38 new();
39
40 private IntegerTimeSeries _setups =
41 new();
42
43 private DoubleTimeSeries _additionalCapacityUsed =
44 new();
45
46 /// <summary>
47 /// Initializes an empty transport decision.
48 /// </summary>
49 /// <remarks>
50 /// This constructor is required by
51 /// <see cref="XmlSerializer"/>.
52 /// </remarks>
54 {
55 SubscribeToObject(_origin);
56 SubscribeToObject(_destination);
57 SubscribeToObject(_transportedQuantities);
58 SubscribeToObject(_setups);
59 SubscribeToObject(_additionalCapacityUsed);
60 }
61
62 /// <summary>
63 /// Initializes a transport decision for an item,
64 /// a transport resource and a directed warehouse lane.
65 /// </summary>
66 /// <param name="itemId">
67 /// Identifier of the transported item.
68 /// </param>
69 /// <param name="transportResourceId">
70 /// Identifier of the transport resource.
71 /// </param>
72 /// <param name="origin">
73 /// Origin warehouse reference.
74 /// </param>
75 /// <param name="destination">
76 /// Destination warehouse reference.
77 /// </param>
78 /// <param name="planningHorizon">
79 /// Strictly positive number of planning periods.
80 /// </param>
82 int itemId,
83 int transportResourceId,
84 WarehouseReference origin,
85 WarehouseReference destination,
86 int planningHorizon)
87 : this()
88 {
89 if (itemId <= 0)
90 {
91 throw new ArgumentOutOfRangeException(
92 nameof(itemId),
93 itemId,
94 "The item identifier must be strictly positive.");
95 }
96
97 if (transportResourceId <= 0)
98 {
99 throw new ArgumentOutOfRangeException(
100 nameof(transportResourceId),
101 transportResourceId,
102 "The transport-resource identifier must be " +
103 "strictly positive.");
104 }
105
106 ArgumentNullException.ThrowIfNull(origin);
107 ArgumentNullException.ThrowIfNull(destination);
108
109 if (SameWarehouse(
110 origin,
111 destination))
112 {
113 throw new ArgumentException(
114 "The origin and destination warehouses " +
115 "must be different.",
116 nameof(destination));
117 }
118
119 if (planningHorizon <= 0)
120 {
121 throw new ArgumentOutOfRangeException(
122 nameof(planningHorizon),
123 planningHorizon,
124 "The planning horizon must be strictly positive.");
125 }
126
127 ItemId = itemId;
128
130 transportResourceId;
131
132 Origin = origin;
133 Destination = destination;
134
135 ResizeTimeSeries(planningHorizon);
136 }
137
138 /// <summary>
139 /// Gets or sets the identifier of the transported item.
140 /// </summary>
141 [XmlAttribute("itemId")]
142 public int ItemId
143 {
144 get => _itemId;
145 set
146 {
147 /*
148 * Zero is tolerated for an empty object created
149 * by XmlSerializer. The solution validator will
150 * require a strictly positive identifier.
151 */
152 if (value < 0)
153 {
154 throw new ArgumentOutOfRangeException(
155 nameof(value),
156 value,
157 "The item identifier cannot be negative.");
158 }
159
160 SetProperty(
161 ref _itemId,
162 value);
163 }
164 }
165
166 /// <summary>
167 /// Gets or sets the identifier of the transport resource
168 /// used by the decision.
169 /// </summary>
170 [XmlAttribute("transportResourceId")]
172 {
173 get => _transportResourceId;
174 set
175 {
176 /*
177 * Zero is tolerated during XML deserialization.
178 */
179 if (value < 0)
180 {
181 throw new ArgumentOutOfRangeException(
182 nameof(value),
183 value,
184 "The transport-resource identifier " +
185 "cannot be negative.");
186 }
187
188 SetProperty(
189 ref _transportResourceId,
190 value);
191 }
192 }
193
194 /// <summary>
195 /// Gets or sets the origin warehouse reference.
196 /// </summary>
197 [XmlElement("origin")]
198 public WarehouseReference Origin
199 {
200 get => _origin;
201 set
202 {
203 ArgumentNullException.ThrowIfNull(value);
204
205 if (ReferenceEquals(
206 _origin,
207 value))
208 {
209 return;
210 }
211
212 UnsubscribeFromObject(_origin);
213
214 SetProperty(
215 ref _origin,
216 value);
217
218 SubscribeToObject(_origin);
219
220 NotifyDerivedProperties();
221 }
222 }
223
224 /// <summary>
225 /// Gets or sets the destination warehouse reference.
226 /// </summary>
227 [XmlElement("destination")]
228 public WarehouseReference Destination
229 {
230 get => _destination;
231 set
232 {
233 ArgumentNullException.ThrowIfNull(value);
234
235 if (ReferenceEquals(
236 _destination,
237 value))
238 {
239 return;
240 }
241
242 UnsubscribeFromObject(_destination);
243
244 SetProperty(
245 ref _destination,
246 value);
247
248 SubscribeToObject(_destination);
249
250 NotifyDerivedProperties();
251 }
252 }
253
254 /// <summary>
255 /// Gets or sets the quantities transported during
256 /// each planning period.
257 /// </summary>
258 /// <remarks>
259 /// Values must be finite and non-negative.
260 /// </remarks>
261 [XmlElement("transportedQuantities")]
262 public DoubleTimeSeries TransportedQuantities
263 {
264 get => _transportedQuantities;
265 set
266 {
267 ArgumentNullException.ThrowIfNull(value);
268
269 if (ReferenceEquals(
270 _transportedQuantities,
271 value))
272 {
273 return;
274 }
275
276 UnsubscribeFromObject(
277 _transportedQuantities);
278
279 SetProperty(
280 ref _transportedQuantities,
281 value);
282
283 SubscribeToObject(
284 _transportedQuantities);
285
286 NotifyDerivedProperties();
287 }
288 }
289
290 /// <summary>
291 /// Gets or sets the binary item-specific transport
292 /// activation decisions.
293 /// </summary>
294 /// <remarks>
295 /// Each value must be zero or one.
296 /// </remarks>
297 [XmlElement("setups")]
298 public IntegerTimeSeries Setups
299 {
300 get => _setups;
301 set
302 {
303 ArgumentNullException.ThrowIfNull(value);
304
305 if (ReferenceEquals(
306 _setups,
307 value))
308 {
309 return;
310 }
311
312 UnsubscribeFromObject(_setups);
313
314 SetProperty(
315 ref _setups,
316 value);
317
318 SubscribeToObject(_setups);
319
320 NotifyDerivedProperties();
321 }
322 }
323
324 /// <summary>
325 /// Gets or sets the item-specific additional transport
326 /// capacity used during each planning period.
327 /// </summary>
328 /// <remarks>
329 /// Values must be finite and non-negative.
330 ///
331 /// Global additional capacity used by the transport resource
332 /// is not stored in this series.
333 /// </remarks>
334 [XmlElement("additionalCapacityUsed")]
335 public DoubleTimeSeries AdditionalCapacityUsed
336 {
337 get => _additionalCapacityUsed;
338 set
339 {
340 ArgumentNullException.ThrowIfNull(value);
341
342 if (ReferenceEquals(
343 _additionalCapacityUsed,
344 value))
345 {
346 return;
347 }
348
349 UnsubscribeFromObject(
350 _additionalCapacityUsed);
351
352 SetProperty(
353 ref _additionalCapacityUsed,
354 value);
355
356 SubscribeToObject(
357 _additionalCapacityUsed);
358
359 NotifyDerivedProperties();
360 }
361 }
362
363 /// <summary>
364 /// Gets the number of planning periods represented
365 /// by the transported-quantity series.
366 /// </summary>
367 [XmlIgnore]
368 public int PlanningHorizon =>
369 TransportedQuantities.PeriodCount;
370
371 /// <summary>
372 /// Gets a value indicating whether all decision series
373 /// use the same planning horizon.
374 /// </summary>
375 [XmlIgnore]
377 Setups.PeriodCount ==
379 AdditionalCapacityUsed.PeriodCount ==
381
382 /// <summary>
383 /// Gets a value indicating whether every transported
384 /// quantity is finite and non-negative.
385 /// </summary>
386 [XmlIgnore]
389 quantity =>
390 double.IsFinite(quantity) &&
391 quantity >= 0.0);
392
393 /// <summary>
394 /// Gets a value indicating whether every setup value
395 /// is equal to zero or one.
396 /// </summary>
397 [XmlIgnore]
398 public bool HasValidSetupValues =>
399 Setups.All(
400 setup =>
401 setup is 0 or 1);
402
403 /// <summary>
404 /// Gets a value indicating whether every additional-capacity
405 /// value is finite and non-negative.
406 /// </summary>
407 [XmlIgnore]
410 capacity =>
411 double.IsFinite(capacity) &&
412 capacity >= 0.0);
413
414 /// <summary>
415 /// Gets a value indicating whether the origin and
416 /// destination identify different warehouses.
417 /// </summary>
418 [XmlIgnore]
419 public bool HasValidLane =>
420 Origin.ReferenceId > 0 &&
421 Destination.ReferenceId > 0 &&
422 !SameWarehouse(
423 Origin,
425
426 /// <summary>
427 /// Gets a value indicating whether the transport decision
428 /// is internally consistent.
429 /// </summary>
430 /// <remarks>
431 /// This property does not verify that the item, transport
432 /// resource or transport lane exists in a particular
433 /// supply-chain instance.
434 /// </remarks>
435 [XmlIgnore]
436 public bool IsInternallyValid =>
437 ItemId > 0 &&
439 PlanningHorizon > 0 &&
440 HasValidLane &&
445
446 /// <summary>
447 /// Gets the transported quantity for a planning period.
448 /// </summary>
449 /// <param name="period">
450 /// One-based planning period.
451 /// </param>
452 /// <returns>
453 /// Transported quantity recorded for the period.
454 /// </returns>
455 public double GetTransportedQuantity(int period)
456 {
457 return TransportedQuantities[period];
458 }
459
460 /// <summary>
461 /// Sets the transported quantity for a planning period.
462 /// </summary>
463 /// <param name="period">
464 /// One-based planning period.
465 /// </param>
466 /// <param name="quantity">
467 /// Finite and non-negative transported quantity.
468 /// </param>
470 int period,
471 double quantity)
472 {
473 ValidateNonNegativeFiniteValue(
474 quantity,
475 nameof(quantity));
476
477 TransportedQuantities[period] =
478 quantity;
479 }
480
481 /// <summary>
482 /// Determines whether item-specific transport is activated
483 /// during a planning period.
484 /// </summary>
485 /// <param name="period">
486 /// One-based planning period.
487 /// </param>
488 /// <returns>
489 /// True when the setup value is one; otherwise, false.
490 /// </returns>
491 public bool IsSetupActivated(int period)
492 {
493 return Setups[period] == 1;
494 }
495
496 /// <summary>
497 /// Sets the item-specific transport activation
498 /// for a planning period.
499 /// </summary>
500 /// <param name="period">
501 /// One-based planning period.
502 /// </param>
503 /// <param name="isActivated">
504 /// True to activate transport; otherwise, false.
505 /// </param>
506 public void SetSetupActivated(
507 int period,
508 bool isActivated)
509 {
510 Setups[period] =
511 isActivated ? 1 : 0;
512 }
513
514 /// <summary>
515 /// Gets the item-specific additional transport capacity
516 /// used during a planning period.
517 /// </summary>
518 /// <param name="period">
519 /// One-based planning period.
520 /// </param>
521 /// <returns>
522 /// Non-negative additional-capacity quantity.
523 /// </returns>
525 int period)
526 {
527 return AdditionalCapacityUsed[period];
528 }
529
530 /// <summary>
531 /// Sets the item-specific additional transport capacity
532 /// used during a planning period.
533 /// </summary>
534 /// <param name="period">
535 /// One-based planning period.
536 /// </param>
537 /// <param name="capacity">
538 /// Finite and non-negative additional-capacity quantity.
539 /// </param>
541 int period,
542 double capacity)
543 {
544 ValidateNonNegativeFiniteValue(
545 capacity,
546 nameof(capacity));
547
548 AdditionalCapacityUsed[period] =
549 capacity;
550 }
551
552 /// <summary>
553 /// Determines whether this decision identifies the
554 /// specified item, resource and directed warehouse lane.
555 /// </summary>
556 /// <param name="itemId">
557 /// Item identifier to compare.
558 /// </param>
559 /// <param name="transportResourceId">
560 /// Transport-resource identifier to compare.
561 /// </param>
562 /// <param name="origin">
563 /// Origin warehouse to compare.
564 /// </param>
565 /// <param name="destination">
566 /// Destination warehouse to compare.
567 /// </param>
568 /// <returns>
569 /// True when all key elements match; otherwise, false.
570 /// </returns>
571 public bool Matches(
572 int itemId,
573 int transportResourceId,
574 WarehouseReference origin,
575 WarehouseReference destination)
576 {
577 ArgumentNullException.ThrowIfNull(origin);
578 ArgumentNullException.ThrowIfNull(destination);
579
580 return ItemId == itemId &&
582 transportResourceId &&
583 SameWarehouse(
584 Origin,
585 origin) &&
586 SameWarehouse(
588 destination);
589 }
590
591 /// <summary>
592 /// Resizes every decision series to the specified
593 /// planning horizon.
594 /// </summary>
595 /// <param name="periodCount">
596 /// Non-negative number of planning periods.
597 /// </param>
598 /// <remarks>
599 /// Existing values are preserved whenever possible.
600 /// New periods are initialized with zero.
601 /// </remarks>
602 public void ResizeTimeSeries(int periodCount)
603 {
604 if (periodCount < 0)
605 {
606 throw new ArgumentOutOfRangeException(
607 nameof(periodCount),
608 periodCount,
609 "The period count cannot be negative.");
610 }
611
613 periodCount,
614 defaultValue: 0.0);
615
616 Setups.Resize(
617 periodCount,
618 defaultValue: 0);
619
621 periodCount,
622 defaultValue: 0.0);
623
624 NotifyDerivedProperties();
625 }
626
627 /// <summary>
628 /// Resets every transport decision value to zero.
629 /// </summary>
630 public void Clear()
631 {
632 TransportedQuantities.Fill(0.0);
633 Setups.Fill(0);
634 AdditionalCapacityUsed.Fill(0.0);
635
636 NotifyDerivedProperties();
637 }
638
639 /// <inheritdoc/>
640 public override string ToString()
641 {
642 double totalQuantity =
644
645 return
646 $"Item {ItemId}, transport resource " +
647 $"{TransportResourceId}, " +
648 $"{FormatWarehouse(Origin)} -> " +
649 $"{FormatWarehouse(Destination)}: " +
650 $"total quantity {totalQuantity}";
651 }
652
653 private void SubscribeToObject(
654 ModelObject modelObject)
655 {
656 modelObject.PropertyChanged +=
657 OnNestedPropertyChanged;
658 }
659
660 private void UnsubscribeFromObject(
661 ModelObject modelObject)
662 {
663 modelObject.PropertyChanged -=
664 OnNestedPropertyChanged;
665 }
666
667 private void OnNestedPropertyChanged(
668 object? sender,
669 PropertyChangedEventArgs eventArgs)
670 {
671 NotifyDerivedProperties();
672 }
673
674 private void NotifyDerivedProperties()
675 {
676 OnPropertyChanged(
677 nameof(PlanningHorizon));
678
679 OnPropertyChanged(
681
682 OnPropertyChanged(
684
685 OnPropertyChanged(
686 nameof(HasValidSetupValues));
687
688 OnPropertyChanged(
690
691 OnPropertyChanged(
692 nameof(HasValidLane));
693
694 OnPropertyChanged(
695 nameof(IsInternallyValid));
696 }
697
698 private static bool SameWarehouse(
699 WarehouseReference first,
700 WarehouseReference second)
701 {
702 return first.Kind ==
703 second.Kind &&
704 first.ReferenceId ==
705 second.ReferenceId;
706 }
707
708 private static string FormatWarehouse(
709 WarehouseReference warehouse)
710 {
711 return
712 $"{warehouse.Kind}:{warehouse.ReferenceId}";
713 }
714
715 private static void ValidateNonNegativeFiniteValue(
716 double value,
717 string parameterName)
718 {
719 if (!double.IsFinite(value) ||
720 value < 0.0)
721 {
722 throw new ArgumentOutOfRangeException(
723 parameterName,
724 value,
725 "The value must be finite and non-negative.");
726 }
727 }
728}
void SetSetupActivated(int period, bool isActivated)
Sets the item-specific transport activation for a planning period.
WarehouseReference Destination
Gets or sets the destination warehouse reference.
bool IsSetupActivated(int period)
Determines whether item-specific transport is activated during a planning period.
void SetTransportedQuantity(int period, double quantity)
Sets the transported quantity for a planning period.
bool IsInternallyValid
Gets a value indicating whether the transport decision is internally consistent.
TransportDecision(int itemId, int transportResourceId, WarehouseReference origin, WarehouseReference destination, int planningHorizon)
Initializes a transport decision for an item, a transport resource and a directed warehouse lane.
TransportDecision()
Initializes an empty transport decision.
int TransportResourceId
Gets or sets the identifier of the transport resource used by the decision.
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
double GetAdditionalCapacityUsed(int period)
Gets the item-specific additional transport capacity used during a planning period.
double GetTransportedQuantity(int period)
Gets the transported quantity for a planning period.
DoubleTimeSeries AdditionalCapacityUsed
Gets or sets the item-specific additional transport capacity used during each planning period.
int PlanningHorizon
Gets the number of planning periods represented by the transported-quantity series.
DoubleTimeSeries TransportedQuantities
Gets or sets the quantities transported during each planning period.
void SetAdditionalCapacityUsed(int period, double capacity)
Sets the item-specific additional transport capacity used during a planning period.
void Clear()
Resets every transport decision value to zero.
bool HasValidAdditionalCapacityValues
Gets a value indicating whether every additional-capacity value is finite and non-negative.
bool HasConsistentPlanningHorizon
Gets a value indicating whether all decision series use the same planning horizon.
bool HasValidSetupValues
Gets a value indicating whether every setup value is equal to zero or one.
bool HasValidLane
Gets a value indicating whether the origin and destination identify different warehouses.
bool Matches(int itemId, int transportResourceId, WarehouseReference origin, WarehouseReference destination)
Determines whether this decision identifies the specified item, resource and directed warehouse lane.
WarehouseReference Origin
Gets or sets the origin warehouse reference.
IntegerTimeSeries Setups
Gets or sets the binary item-specific transport activation decisions.
bool HasValidTransportedQuantities
Gets a value indicating whether every transported quantity is finite and non-negative.
int ItemId
Gets or sets the identifier of the transported item.