LotSizingDataModel.Solution 2.0.1
Solution objects for production, setup, inventory and related decisions.
Loading...
Searching...
No Matches
LotSizingSolution.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.Linq;
5using System.Xml.Serialization;
6using LotSizingDataModel.Core;
7using LotSizingDataModel.Core.Common;
8using LotSizingDataModel.Core.PhysicalModel;
14
16
17/// <summary>
18/// Represents one candidate solution for a lot-sizing
19/// supply-chain instance.
20/// </summary>
21/// <remarks>
22/// The solution stores decision values independently of the
23/// method used to generate them.
24///
25/// A solution may originate from an exact solver, a heuristic,
26/// a metaheuristic, a matheuristic, a manual construction
27/// or an external file.
28/// </remarks>
29[Serializable]
30[XmlRoot("lotSizingSolution")]
31[XmlType(TypeName = "lotSizingSolution")]
32public sealed partial class LotSizingSolution :
33 ModelObject,
34 IPlanningHorizonAware
35{
36 private Guid _id =
37 Guid.NewGuid();
38
39 private string _name =
40 string.Empty;
41
42 private string _description =
43 string.Empty;
44
45 private string _instanceIdentifier =
46 string.Empty;
47
48 private string _instanceFingerprint =
49 string.Empty;
50
51 private int _planningHorizon;
52
53 private SolutionCompleteness _completeness =
55
56 private SolutionGenerationMetadata _generationMetadata =
57 new();
58
59 private SolutionEvaluation _evaluation =
60 new();
61
62 /// <summary>
63 /// Initializes an empty lot-sizing solution.
64 /// </summary>
65 /// <remarks>
66 /// This constructor is required by
67 /// <see cref="XmlSerializer"/>.
68 /// </remarks>
70 {
71 SubscribeToObject(_generationMetadata);
72 SubscribeToObject(_evaluation);
73 }
74
75 /// <summary>
76 /// Initializes a lot-sizing solution for a planning horizon.
77 /// </summary>
78 /// <param name="planningHorizon">
79 /// Strictly positive number of planning periods.
80 /// </param>
82 int planningHorizon)
83 : this()
84 {
85 if (planningHorizon <= 0)
86 {
87 throw new ArgumentOutOfRangeException(
88 nameof(planningHorizon),
89 planningHorizon,
90 "The planning horizon must be strictly positive.");
91 }
92
93 ResizeTimeSeries(planningHorizon);
94 }
95
96 /// <summary>
97 /// Initializes a named lot-sizing solution for an instance.
98 /// </summary>
99 /// <param name="name">
100 /// Human-readable solution name.
101 /// </param>
102 /// <param name="instanceIdentifier">
103 /// Identifier of the associated supply-chain instance.
104 /// </param>
105 /// <param name="planningHorizon">
106 /// Strictly positive number of planning periods.
107 /// </param>
109 string name,
110 string instanceIdentifier,
111 int planningHorizon)
112 : this(planningHorizon)
113 {
114 Name = name;
115 InstanceIdentifier = instanceIdentifier;
116 }
117
118 /// <summary>
119 /// Creates a zero-initialized solution whose decision
120 /// structure matches the specified supply-chain instance.
121 /// </summary>
122 /// <param name="supplyChain">
123 /// Supply-chain instance used to create the solution.
124 /// </param>
125 /// <param name="instanceIdentifier">
126 /// Identifier of the associated supply-chain instance.
127 /// </param>
128 /// <param name="name">
129 /// Optional human-readable solution name.
130 /// </param>
131 /// <param name="instanceFingerprint">
132 /// Optional fingerprint of the associated instance.
133 /// </param>
134 /// <param name="validateSupplyChain">
135 /// True to validate the supply-chain instance before
136 /// creating the solution; otherwise, false.
137 /// </param>
138 /// <returns>
139 /// A structurally complete, zero-initialized solution.
140 /// </returns>
142 SupplyChain supplyChain,
143 string instanceIdentifier = "",
144 string name = "",
145 string instanceFingerprint = "",
146 bool validateSupplyChain = true)
147 {
149 supplyChain,
150 instanceIdentifier,
151 name,
152 instanceFingerprint,
153 validateSupplyChain);
154 }
155
156
157 /// <summary>
158 /// Gets or sets the unique identifier of the solution.
159 /// </summary>
160 [XmlAttribute("id")]
161 public Guid Id
162 {
163 get => _id;
164 set => SetProperty(
165 ref _id,
166 value);
167 }
168
169 /// <summary>
170 /// Gets or sets the human-readable name of the solution.
171 /// </summary>
172 [XmlAttribute("name")]
173 public string Name
174 {
175 get => _name;
176 set => SetProperty(
177 ref _name,
178 value?.Trim() ?? string.Empty);
179 }
180
181 /// <summary>
182 /// Gets or sets an optional human-readable description
183 /// of the solution.
184 /// </summary>
185 [XmlElement("description")]
186 public string Description
187 {
188 get => _description;
189 set => SetProperty(
190 ref _description,
191 value ?? string.Empty);
192 }
193
194 /// <summary>
195 /// Gets or sets the identifier of the supply-chain
196 /// instance associated with the solution.
197 /// </summary>
198 /// <remarks>
199 /// This identifier may correspond to a file name,
200 /// benchmark name, database key or model identifier.
201 /// </remarks>
202 [XmlAttribute("instanceIdentifier")]
203 public string InstanceIdentifier
204 {
205 get => _instanceIdentifier;
206 set => SetProperty(
207 ref _instanceIdentifier,
208 value?.Trim() ?? string.Empty);
209 }
210
211 /// <summary>
212 /// Gets or sets an optional fingerprint of the associated
213 /// supply-chain instance.
214 /// </summary>
215 /// <remarks>
216 /// A fingerprint may be used to detect whether the instance
217 /// has changed since the solution was generated.
218 ///
219 /// The fingerprint-generation mechanism is intentionally
220 /// not imposed by this class.
221 /// </remarks>
222 [XmlAttribute("instanceFingerprint")]
224 {
225 get => _instanceFingerprint;
226 set => SetProperty(
227 ref _instanceFingerprint,
228 value?.Trim() ?? string.Empty);
229 }
230
231 /// <summary>
232 /// Gets or sets the number of planning periods represented
233 /// by the solution.
234 /// </summary>
235 /// <remarks>
236 /// Changing this value resizes every decision time series.
237 /// Existing values are preserved whenever possible.
238 /// </remarks>
239 [XmlAttribute("planningHorizon")]
241 {
242 get => _planningHorizon;
243 set => ResizeTimeSeries(value);
244 }
245
246 /// <summary>
247 /// Gets or sets whether the solution contains all
248 /// decisions expected for the associated instance.
249 /// </summary>
250 [XmlAttribute("completeness")]
252 {
253 get => _completeness;
254 set => SetProperty(
255 ref _completeness,
256 value);
257 }
258
259 /// <summary>
260 /// Gets or sets the metadata describing how
261 /// the solution was generated.
262 /// </summary>
263 [XmlElement("generationMetadata")]
265 {
266 get => _generationMetadata;
267 set
268 {
269 ArgumentNullException.ThrowIfNull(value);
270
271 if (ReferenceEquals(
272 _generationMetadata,
273 value))
274 {
275 return;
276 }
277
278 UnsubscribeFromObject(
279 _generationMetadata);
280
281 SetProperty(
282 ref _generationMetadata,
283 value);
284
285 SubscribeToObject(
286 _generationMetadata);
287 }
288 }
289
290 /// <summary>
291 /// Gets or sets the independent evaluation
292 /// of the solution.
293 /// </summary>
294 [XmlElement("evaluation")]
296 {
297 get => _evaluation;
298 set
299 {
300 ArgumentNullException.ThrowIfNull(value);
301
302 if (ReferenceEquals(
303 _evaluation,
304 value))
305 {
306 return;
307 }
308
309 UnsubscribeFromObject(_evaluation);
310
311 SetProperty(
312 ref _evaluation,
313 value);
314
315 SubscribeToObject(_evaluation);
316 }
317 }
318
319 /// <summary>
320 /// Gets the production decisions contained in the solution.
321 /// </summary>
322 [XmlArray("productionDecisions")]
323 [XmlArrayItem("productionDecision")]
324 public List<ProductionDecision> ProductionDecisions
325 {
326 get;
327 } = new();
328
329 /// <summary>
330 /// Gets the inventory decisions contained in the solution.
331 /// </summary>
332 [XmlArray("inventoryDecisions")]
333 [XmlArrayItem("inventoryDecision")]
334 public List<InventoryDecision> InventoryDecisions
335 {
336 get;
337 } = new();
338
339 /// <summary>
340 /// Gets the transport decisions contained in the solution.
341 /// </summary>
342 [XmlArray("transportDecisions")]
343 [XmlArrayItem("transportDecision")]
344 public List<TransportDecision> TransportDecisions
345 {
346 get;
347 } = new();
348
349 /// <summary>
350 /// Gets the purchase decisions contained in the solution.
351 /// </summary>
352 [XmlArray("purchaseDecisions")]
353 [XmlArrayItem("purchaseDecision")]
354 public List<PurchaseDecision> PurchaseDecisions
355 {
356 get;
357 } = new();
358
359 /// <summary>
360 /// Gets the distribution decisions contained in the solution.
361 /// </summary>
362 [XmlArray("distributionDecisions")]
363 [XmlArrayItem("distributionDecision")]
364 public List<DistributionDecision> DistributionDecisions
365 {
366 get;
367 } = new();
368
369 /// <summary>
370 /// Gets the global work-center capacity decisions
371 /// contained in the solution.
372 /// </summary>
373 [XmlArray("workCenterCapacityDecisions")]
374 [XmlArrayItem("workCenterCapacityDecision")]
375 public List<WorkCenterCapacityDecision>
377 {
378 get;
379 } = new();
380
381 /// <summary>
382 /// Gets the global warehouse-capacity decisions
383 /// contained in the solution.
384 /// </summary>
385 [XmlArray("warehouseCapacityDecisions")]
386 [XmlArrayItem("warehouseCapacityDecision")]
387 public List<WarehouseCapacityDecision>
389 {
390 get;
391 } = new();
392
393 /// <summary>
394 /// Gets the global transport-resource capacity decisions
395 /// contained in the solution.
396 /// </summary>
397 [XmlArray("transportResourceCapacityDecisions")]
398 [XmlArrayItem("transportResourceCapacityDecision")]
399 public List<TransportResourceCapacityDecision>
401 {
402 get;
403 } = new();
404
405 /// <summary>
406 /// Gets the total number of decision objects
407 /// contained in the solution.
408 /// </summary>
409 [XmlIgnore]
410 public int DecisionCount =>
411 ProductionDecisions.Count +
412 WorkCenterSchedulingDecisions.Count +
413 InventoryDecisions.Count +
414 TransportDecisions.Count +
415 PurchaseDecisions.Count +
416 DistributionDecisions.Count +
417 WorkCenterCapacityDecisions.Count +
418 WarehouseCapacityDecisions.Count +
420
421 /// <summary>
422 /// Gets a value indicating whether the solution
423 /// contains at least one decision object.
424 /// </summary>
425 [XmlIgnore]
426 public bool HasDecisions =>
427 DecisionCount > 0;
428
429 /// <summary>
430 /// Gets a value indicating whether every decision uses
431 /// the same planning horizon as the solution.
432 /// </summary>
433 [XmlIgnore]
436 decision =>
437 decision.PlanningHorizon ==
439
441 decision =>
442 decision.PlanningHorizon ==
444
446 decision =>
447 decision.PlanningHorizon ==
449
451 decision =>
452 decision.PlanningHorizon ==
454
456 decision =>
457 decision.PlanningHorizon ==
459
461 decision =>
462 decision.PlanningHorizon ==
464
466 decision =>
467 decision.PlanningHorizon ==
469
471 decision =>
472 decision.PlanningHorizon ==
474
476 decision =>
477 decision.PlanningHorizon ==
479
480 /// <summary>
481 /// Gets a value indicating whether every decision object
482 /// is internally valid.
483 /// </summary>
484 [XmlIgnore]
487 decision =>
488 decision.IsInternallyValid) &&
489
491 decision =>
492 decision.IsInternallyValid) &&
493
495 decision =>
496 decision.IsInternallyValid) &&
497
499 decision =>
500 decision.IsInternallyValid) &&
501
503 decision =>
504 decision.IsInternallyValid) &&
505
507 decision =>
508 decision.IsInternallyValid) &&
509
511 decision =>
512 decision.IsInternallyValid) &&
513
515 decision =>
516 decision.IsInternallyValid) &&
517
519 decision =>
520 decision.IsInternallyValid);
521
522 /// <summary>
523 /// Gets a value indicating whether every decision key
524 /// is unique within its decision family.
525 /// </summary>
526 [XmlIgnore]
529 .Select(
530 decision =>
531 decision.RoutingId)
532 .Distinct()
533 .Count() ==
534 ProductionDecisions.Count &&
535
536 WorkCenterSchedulingDecisions.Select(decision => (decision.WorkCenter.PlantId, decision.WorkCenter.WorkCenterId)).Distinct().Count() ==
537 WorkCenterSchedulingDecisions.Count &&
538
540 .Select(
541 decision =>
542 (
543 decision.ItemId,
544 decision.Warehouse.Kind,
545 decision.Warehouse.ReferenceId
546 ))
547 .Distinct()
548 .Count() ==
549 InventoryDecisions.Count &&
550
552 .Select(
553 decision =>
554 (
555 decision.ItemId,
556 decision.TransportResourceId,
557 decision.Origin.Kind,
558 decision.Origin.ReferenceId,
559 decision.Destination.Kind,
560 decision.Destination.ReferenceId
561 ))
562 .Distinct()
563 .Count() ==
564 TransportDecisions.Count &&
565
567 .Select(
568 decision =>
569 (
570 decision.SupplierId,
571 decision.ItemId,
572 decision.DestinationWarehouse.Kind,
573 decision.DestinationWarehouse.ReferenceId
574 ))
575 .Distinct()
576 .Count() ==
577 PurchaseDecisions.Count &&
578
580 .Select(
581 decision =>
582 (
583 decision.DistributionCenterId,
584 decision.ItemId,
585 decision.Warehouse.Kind,
586 decision.Warehouse.ReferenceId
587 ))
588 .Distinct()
589 .Count() ==
590 DistributionDecisions.Count &&
591
593 .Select(
594 decision =>
595 (
596 decision.WorkCenter.PlantId,
597 decision.WorkCenter.WorkCenterId
598 ))
599 .Distinct()
600 .Count() ==
601 WorkCenterCapacityDecisions.Count &&
602
604 .Select(
605 decision =>
606 (
607 decision.Warehouse.Kind,
608 decision.Warehouse.ReferenceId
609 ))
610 .Distinct()
611 .Count() ==
612 WarehouseCapacityDecisions.Count &&
613
615 .Select(
616 decision =>
617 decision.TransportResourceId)
618 .Distinct()
619 .Count() ==
621
622 /// <summary>
623 /// Gets a value indicating whether the solution
624 /// is internally consistent.
625 /// </summary>
626 /// <remarks>
627 /// This property does not verify the solution against
628 /// a particular supply-chain instance.
629 ///
630 /// Instance references, capacities, material balances,
631 /// demand satisfaction and other model constraints will
632 /// be checked by the solution validator.
633 /// </remarks>
634 [XmlIgnore]
635 public bool IsInternallyValid =>
636 Id != Guid.Empty &&
637 PlanningHorizon > 0 &&
638 HasDecisions &&
642
643 /// <summary>
644 /// Adds a production decision to the solution.
645 /// </summary>
646 /// <param name="decision">
647 /// Production decision to add.
648 /// </param>
650 ProductionDecision decision)
651 {
652 ArgumentNullException.ThrowIfNull(decision);
653
654 PrepareDecision(
655 decision.PlanningHorizon,
656 decision.ResizeTimeSeries,
657 nameof(decision));
658
659 if (!decision.IsInternallyValid)
660 {
661 throw new ArgumentException(
662 "The production decision is not " +
663 "internally valid.",
664 nameof(decision));
665 }
666
667 if (ProductionDecisions.Any(
668 existing =>
669 existing.RoutingId ==
670 decision.RoutingId))
671 {
672 throw new InvalidOperationException(
673 $"A production decision already exists " +
674 $"for routing {decision.RoutingId}.");
675 }
676
677 ProductionDecisions.Add(decision);
678 SubscribeToObject(decision);
679
680 NotifyDecisionProperties();
681 }
682
683 /// <summary>
684 /// Adds an inventory decision to the solution.
685 /// </summary>
686 /// <param name="decision">
687 /// Inventory decision to add.
688 /// </param>
690 InventoryDecision decision)
691 {
692 ArgumentNullException.ThrowIfNull(decision);
693
694 PrepareDecision(
695 decision.PlanningHorizon,
696 decision.ResizeTimeSeries,
697 nameof(decision));
698
699 if (!decision.IsInternallyValid)
700 {
701 throw new ArgumentException(
702 "The inventory decision is not " +
703 "internally valid.",
704 nameof(decision));
705 }
706
707 if (InventoryDecisions.Any(
708 existing =>
709 existing.ItemId ==
710 decision.ItemId &&
711 SameWarehouse(
712 existing.Warehouse,
713 decision.Warehouse)))
714 {
715 throw new InvalidOperationException(
716 "An inventory decision already exists " +
717 "for this item and warehouse.");
718 }
719
720 InventoryDecisions.Add(decision);
721 SubscribeToObject(decision);
722
723 NotifyDecisionProperties();
724 }
725
726 /// <summary>
727 /// Adds a transport decision to the solution.
728 /// </summary>
729 /// <param name="decision">
730 /// Transport decision to add.
731 /// </param>
733 TransportDecision decision)
734 {
735 ArgumentNullException.ThrowIfNull(decision);
736
737 PrepareDecision(
738 decision.PlanningHorizon,
739 decision.ResizeTimeSeries,
740 nameof(decision));
741
742 if (!decision.IsInternallyValid)
743 {
744 throw new ArgumentException(
745 "The transport decision is not " +
746 "internally valid.",
747 nameof(decision));
748 }
749
750 if (TransportDecisions.Any(
751 existing =>
752 existing.Matches(
753 decision.ItemId,
754 decision.TransportResourceId,
755 decision.Origin,
756 decision.Destination)))
757 {
758 throw new InvalidOperationException(
759 "A transport decision already exists " +
760 "for this item, resource and lane.");
761 }
762
763 TransportDecisions.Add(decision);
764 SubscribeToObject(decision);
765
766 NotifyDecisionProperties();
767 }
768
769 /// <summary>
770 /// Adds a purchase decision to the solution.
771 /// </summary>
772 /// <param name="decision">
773 /// Purchase decision to add.
774 /// </param>
776 PurchaseDecision decision)
777 {
778 ArgumentNullException.ThrowIfNull(decision);
779
780 PrepareDecision(
781 decision.PlanningHorizon,
782 decision.ResizeTimeSeries,
783 nameof(decision));
784
785 if (!decision.IsInternallyValid)
786 {
787 throw new ArgumentException(
788 "The purchase decision is not " +
789 "internally valid.",
790 nameof(decision));
791 }
792
793 if (PurchaseDecisions.Any(
794 existing =>
795 existing.Matches(
796 decision.SupplierId,
797 decision.ItemId,
798 decision.DestinationWarehouse)))
799 {
800 throw new InvalidOperationException(
801 "A purchase decision already exists " +
802 "for this supplier, item and warehouse.");
803 }
804
805 PurchaseDecisions.Add(decision);
806 SubscribeToObject(decision);
807
808 NotifyDecisionProperties();
809 }
810
811 /// <summary>
812 /// Adds a distribution decision to the solution.
813 /// </summary>
814 /// <param name="decision">
815 /// Distribution decision to add.
816 /// </param>
818 DistributionDecision decision)
819 {
820 ArgumentNullException.ThrowIfNull(decision);
821
822 PrepareDecision(
823 decision.PlanningHorizon,
824 decision.ResizeTimeSeries,
825 nameof(decision));
826
827 if (!decision.IsInternallyValid)
828 {
829 throw new ArgumentException(
830 "The distribution decision is not " +
831 "internally valid.",
832 nameof(decision));
833 }
834
835 if (DistributionDecisions.Any(
836 existing =>
837 existing.Matches(
838 decision.DistributionCenterId,
839 decision.ItemId,
840 decision.Warehouse)))
841 {
842 throw new InvalidOperationException(
843 "A distribution decision already exists " +
844 "for this distribution center, item " +
845 "and warehouse.");
846 }
847
848 DistributionDecisions.Add(decision);
849 SubscribeToObject(decision);
850
851 NotifyDecisionProperties();
852 }
853
854 /// <summary>
855 /// Adds a work-center capacity decision to the solution.
856 /// </summary>
857 /// <param name="decision">
858 /// Work-center capacity decision to add.
859 /// </param>
862 {
863 ArgumentNullException.ThrowIfNull(decision);
864
865 PrepareDecision(
866 decision.PlanningHorizon,
867 decision.ResizeTimeSeries,
868 nameof(decision));
869
870 if (!decision.IsInternallyValid)
871 {
872 throw new ArgumentException(
873 "The work-center capacity decision is not " +
874 "internally valid.",
875 nameof(decision));
876 }
877
879 existing =>
880 existing.Matches(
881 decision.WorkCenter)))
882 {
883 throw new InvalidOperationException(
884 "A capacity decision already exists " +
885 "for this work center.");
886 }
887
888 WorkCenterCapacityDecisions.Add(decision);
889 SubscribeToObject(decision);
890
891 NotifyDecisionProperties();
892 }
893
894 /// <summary>
895 /// Adds a warehouse-capacity decision to the solution.
896 /// </summary>
897 /// <param name="decision">
898 /// Warehouse-capacity decision to add.
899 /// </param>
902 {
903 ArgumentNullException.ThrowIfNull(decision);
904
905 PrepareDecision(
906 decision.PlanningHorizon,
907 decision.ResizeTimeSeries,
908 nameof(decision));
909
910 if (!decision.IsInternallyValid)
911 {
912 throw new ArgumentException(
913 "The warehouse-capacity decision is not " +
914 "internally valid.",
915 nameof(decision));
916 }
917
919 existing =>
920 existing.Matches(
921 decision.Warehouse)))
922 {
923 throw new InvalidOperationException(
924 "A capacity decision already exists " +
925 "for this warehouse.");
926 }
927
928 WarehouseCapacityDecisions.Add(decision);
929 SubscribeToObject(decision);
930
931 NotifyDecisionProperties();
932 }
933
934 /// <summary>
935 /// Adds a transport-resource capacity decision
936 /// to the solution.
937 /// </summary>
938 /// <param name="decision">
939 /// Transport-resource capacity decision to add.
940 /// </param>
943 {
944 ArgumentNullException.ThrowIfNull(decision);
945
946 PrepareDecision(
947 decision.PlanningHorizon,
948 decision.ResizeTimeSeries,
949 nameof(decision));
950
951 if (!decision.IsInternallyValid)
952 {
953 throw new ArgumentException(
954 "The transport-resource capacity decision " +
955 "is not internally valid.",
956 nameof(decision));
957 }
958
960 existing =>
961 existing.Matches(
962 decision.TransportResourceId)))
963 {
964 throw new InvalidOperationException(
965 "A capacity decision already exists " +
966 "for this transport resource.");
967 }
968
970 SubscribeToObject(decision);
971
972 NotifyDecisionProperties();
973 }
974
975 /// <summary>
976 /// Resizes every decision time series to the specified
977 /// planning horizon.
978 /// </summary>
979 /// <param name="periodCount">
980 /// Non-negative number of planning periods.
981 /// </param>
982 /// <remarks>
983 /// Existing values are preserved whenever possible.
984 /// New periods are initialized with zero.
985 /// </remarks>
986 public void ResizeTimeSeries(int periodCount)
987 {
988 if (periodCount < 0)
989 {
990 throw new ArgumentOutOfRangeException(
991 nameof(periodCount),
992 periodCount,
993 "The period count cannot be negative.");
994 }
995
996 SetProperty(
997 ref _planningHorizon,
998 periodCount,
999 nameof(PlanningHorizon));
1000
1001 foreach (ProductionDecision decision
1003 {
1004 decision.ResizeTimeSeries(periodCount);
1005 }
1006
1007 foreach (WorkCenterSchedulingDecision decision
1009 {
1010 decision.ResizeTimeSeries(periodCount);
1011 }
1012
1013 foreach (InventoryDecision decision
1015 {
1016 decision.ResizeTimeSeries(periodCount);
1017 }
1018
1019 foreach (TransportDecision decision
1021 {
1022 decision.ResizeTimeSeries(periodCount);
1023 }
1024
1025 foreach (PurchaseDecision decision
1027 {
1028 decision.ResizeTimeSeries(periodCount);
1029 }
1030
1031 foreach (DistributionDecision decision
1033 {
1034 decision.ResizeTimeSeries(periodCount);
1035 }
1036
1037 foreach (WorkCenterCapacityDecision decision
1039 {
1040 decision.ResizeTimeSeries(periodCount);
1041 }
1042
1043 foreach (WarehouseCapacityDecision decision
1045 {
1046 decision.ResizeTimeSeries(periodCount);
1047 }
1048
1049 foreach (TransportResourceCapacityDecision decision
1051 {
1052 decision.ResizeTimeSeries(periodCount);
1053 }
1054
1055 NotifyDecisionProperties();
1056 }
1057
1058 /// <summary>
1059 /// Removes every decision object from the solution.
1060 /// </summary>
1061 public void ClearDecisions()
1062 {
1063 UnsubscribeFromObjects(
1065
1066 UnsubscribeFromObjects(
1068
1069 UnsubscribeFromObjects(
1071
1072 UnsubscribeFromObjects(
1074
1075 UnsubscribeFromObjects(
1077
1078 UnsubscribeFromObjects(
1080
1081 UnsubscribeFromObjects(
1083
1084 UnsubscribeFromObjects(
1086
1087 UnsubscribeFromObjects(
1089
1090 ProductionDecisions.Clear();
1092 InventoryDecisions.Clear();
1093 TransportDecisions.Clear();
1094 PurchaseDecisions.Clear();
1095 DistributionDecisions.Clear();
1099
1100 Completeness =
1101 SolutionCompleteness.Unknown;
1102
1103 NotifyDecisionProperties();
1104 }
1105
1106 /// <inheritdoc/>
1107 public override string ToString()
1108 {
1109 string solutionName =
1110 string.IsNullOrWhiteSpace(Name)
1111 ? Id.ToString()
1112 : Name;
1113
1114 return
1115 $"{solutionName}: " +
1116 $"{DecisionCount} decisions; " +
1117 $"horizon {PlanningHorizon}; " +
1118 $"{Completeness}";
1119 }
1120
1122 {
1123 ReconnectObject(_generationMetadata);
1124 ReconnectObject(_evaluation);
1125
1126 ReconnectObjects(
1128
1129 ReconnectObjects(
1131
1132 ReconnectObjects(
1134
1135 ReconnectObjects(
1137
1138 ReconnectObjects(
1140
1141 ReconnectObjects(
1143
1144 ReconnectObjects(
1146
1147 ReconnectObjects(
1149
1150 ReconnectObjects(
1152
1153 NotifyDecisionProperties();
1154 }
1155
1156 private void PrepareDecision(
1157 int decisionPlanningHorizon,
1158 Action<int> resizeAction,
1159 string parameterName)
1160 {
1161 ArgumentNullException.ThrowIfNull(resizeAction);
1162
1163 if (PlanningHorizon == 0)
1164 {
1165 if (decisionPlanningHorizon <= 0)
1166 {
1167 throw new ArgumentException(
1168 "Neither the solution nor the decision " +
1169 "defines a valid planning horizon.",
1170 parameterName);
1171 }
1172
1174 decisionPlanningHorizon);
1175
1176 return;
1177 }
1178
1179 if (decisionPlanningHorizon == 0)
1180 {
1181 resizeAction(PlanningHorizon);
1182
1183 return;
1184 }
1185
1186 if (decisionPlanningHorizon !=
1188 {
1189 throw new ArgumentException(
1190 $"The decision planning horizon " +
1191 $"({decisionPlanningHorizon}) differs from " +
1192 $"the solution planning horizon " +
1193 $"({PlanningHorizon}).",
1194 parameterName);
1195 }
1196 }
1197
1198 private void SubscribeToObject(
1199 ModelObject modelObject)
1200 {
1201 modelObject.PropertyChanged +=
1202 OnNestedPropertyChanged;
1203 }
1204
1205 private void UnsubscribeFromObject(
1206 ModelObject modelObject)
1207 {
1208 modelObject.PropertyChanged -=
1209 OnNestedPropertyChanged;
1210 }
1211
1212 private void ReconnectObject(
1213 ModelObject modelObject)
1214 {
1215 UnsubscribeFromObject(modelObject);
1216 SubscribeToObject(modelObject);
1217 }
1218
1219 private void ReconnectObjects<T>(
1220 IEnumerable<T> modelObjects)
1221 where T : ModelObject
1222 {
1223 foreach (T modelObject in modelObjects)
1224 {
1225 ReconnectObject(modelObject);
1226 }
1227 }
1228
1229 private void UnsubscribeFromObjects<T>(
1230 IEnumerable<T> modelObjects)
1231 where T : ModelObject
1232 {
1233 foreach (T modelObject in modelObjects)
1234 {
1235 UnsubscribeFromObject(modelObject);
1236 }
1237 }
1238
1239 private void OnNestedPropertyChanged(
1240 object? sender,
1241 PropertyChangedEventArgs eventArgs)
1242 {
1243 NotifyDecisionProperties();
1244 }
1245
1246 private void NotifyDecisionProperties()
1247 {
1248 OnPropertyChanged(
1249 nameof(DecisionCount));
1250
1251 OnPropertyChanged(
1252 nameof(HasDecisions));
1253
1254 OnPropertyChanged(
1256
1257 OnPropertyChanged(
1259
1260 OnPropertyChanged(
1261 nameof(HasUniqueDecisionKeys));
1262
1263 OnPropertyChanged(
1264 nameof(IsInternallyValid));
1265 }
1266
1267 private static bool SameWarehouse(
1268 WarehouseReference first,
1269 WarehouseReference second)
1270 {
1271 return first.Kind ==
1272 second.Kind &&
1273 first.ReferenceId ==
1274 second.ReferenceId;
1275 }
1276}
Creates lot-sizing solutions whose decision structure matches a supply-chain instance.
static LotSizingSolution Create(SupplyChain supplyChain, string instanceIdentifier="", string name="", string instanceFingerprint="", bool validateSupplyChain=true)
Creates a zero-initialized solution whose decision structure matches the specified supply-chain insta...
Stores the distribution decisions associated with one distribution center, one item and one warehouse...
int PlanningHorizon
Gets the number of planning periods represented by the delivered-quantity series.
WarehouseReference Warehouse
Gets or sets the warehouse from which the item is delivered.
int ItemId
Gets or sets the identifier of the distributed item.
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
int DistributionCenterId
Gets or sets the identifier of the distribution center.
bool IsInternallyValid
Gets a value indicating whether the distribution decision is internally consistent.
Stores the inventory decisions associated with one item and one warehouse over the complete planning ...
int ItemId
Gets or sets the identifier of the stored item.
WarehouseReference Warehouse
Gets or sets the reference to the warehouse associated with the inventory decision.
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
int PlanningHorizon
Gets the number of planning periods represented by the inventory-level series.
bool IsInternallyValid
Gets a value indicating whether the inventory decision is internally consistent.
Stores the production decisions associated with one production routing over the complete planning hor...
int PlanningHorizon
Gets the number of planning periods represented by the production-quantity series.
int RoutingId
Gets or sets the identifier of the production routing associated with the decision.
bool IsInternallyValid
Gets a value indicating whether the production decision is internally consistent.
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
Stores the purchase decisions associated with one supplier, one item and one destination warehouse ov...
int PlanningHorizon
Gets the number of planning periods represented by the purchased-quantity series.
WarehouseReference DestinationWarehouse
Gets or sets the warehouse to which the purchased item is delivered.
int ItemId
Gets or sets the identifier of the purchased item.
int SupplierId
Gets or sets the identifier of the supplier associated with the purchase decision.
void ResizeTimeSeries(int periodCount)
Resizes the purchased-quantity series to the specified planning horizon.
bool IsInternallyValid
Gets a value indicating whether the purchase decision is internally consistent.
Stores the transport decisions associated with one item, one transport resource and one directed ware...
WarehouseReference Destination
Gets or sets the destination warehouse reference.
bool IsInternallyValid
Gets a value indicating whether the transport decision is internally consistent.
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.
int PlanningHorizon
Gets the number of planning periods represented by the transported-quantity series.
WarehouseReference Origin
Gets or sets the origin warehouse reference.
int ItemId
Gets or sets the identifier of the transported item.
Stores the global capacity decisions associated with one transport resource over the complete plannin...
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
int PlanningHorizon
Gets the number of planning periods represented by the activation series.
bool IsInternallyValid
Gets a value indicating whether the transport-resource capacity decision is internally consistent.
int TransportResourceId
Gets or sets the identifier of the transport resource associated with the capacity decision.
Stores the global capacity decisions associated with one warehouse over the complete planning horizon...
WarehouseReference Warehouse
Gets or sets the warehouse associated with the capacity decision.
bool IsInternallyValid
Gets a value indicating whether the warehouse-capacity decision is internally consistent.
int PlanningHorizon
Gets the number of planning periods represented by the activation series.
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
Stores the global capacity decisions associated with one work center over the complete planning horiz...
int PlanningHorizon
Gets the number of planning periods represented by the activation series.
void ResizeTimeSeries(int periodCount)
Resizes every decision series to the specified planning horizon.
WorkCenterReference WorkCenter
Gets or sets the reference to the work center associated with the capacity decision.
bool IsInternallyValid
Gets a value indicating whether the work-center capacity decision is internally consistent.
Describes the independent evaluation of a lot-sizing solution candidate.
Represents one candidate solution for a lot-sizing supply-chain instance.
void AddPurchaseDecision(PurchaseDecision decision)
Adds a purchase decision to the solution.
void AddWarehouseCapacityDecision(WarehouseCapacityDecision decision)
Adds a warehouse-capacity decision to the solution.
LotSizingSolution(string name, string instanceIdentifier, int planningHorizon)
Initializes a named lot-sizing solution for an instance.
static LotSizingSolution CreateFor(SupplyChain supplyChain, string instanceIdentifier="", string name="", string instanceFingerprint="", bool validateSupplyChain=true)
Creates a zero-initialized solution whose decision structure matches the specified supply-chain insta...
List< ProductionDecision > ProductionDecisions
Gets the production decisions contained in the solution.
LotSizingSolution()
Initializes an empty lot-sizing solution.
List< WorkCenterCapacityDecision > WorkCenterCapacityDecisions
Gets the global work-center capacity decisions contained in the solution.
SolutionCompleteness Completeness
Gets or sets whether the solution contains all decisions expected for the associated instance.
bool HasInternallyValidDecisions
Gets a value indicating whether every decision object is internally valid.
void ClearDecisions()
Removes every decision object from the solution.
bool HasConsistentPlanningHorizon
Gets a value indicating whether every decision uses the same planning horizon as the solution.
SolutionGenerationMetadata GenerationMetadata
Gets or sets the metadata describing how the solution was generated.
string Description
Gets or sets an optional human-readable description of the solution.
void ResizeTimeSeries(int periodCount)
Resizes every decision time series to the specified planning horizon.
int PlanningHorizon
Gets or sets the number of planning periods represented by the solution.
List< TransportDecision > TransportDecisions
Gets the transport decisions contained in the solution.
string InstanceIdentifier
Gets or sets the identifier of the supply-chain instance associated with the solution.
Guid Id
Gets or sets the unique identifier of the solution.
List< PurchaseDecision > PurchaseDecisions
Gets the purchase decisions contained in the solution.
bool HasUniqueDecisionKeys
Gets a value indicating whether every decision key is unique within its decision family.
void AddInventoryDecision(InventoryDecision decision)
Adds an inventory decision to the solution.
string InstanceFingerprint
Gets or sets an optional fingerprint of the associated supply-chain instance.
List< DistributionDecision > DistributionDecisions
Gets the distribution decisions contained in the solution.
List< WorkCenterSchedulingDecision > WorkCenterSchedulingDecisions
int DecisionCount
Gets the total number of decision objects contained in the solution.
void AddTransportDecision(TransportDecision decision)
Adds a transport decision to the solution.
void AddWorkCenterCapacityDecision(WorkCenterCapacityDecision decision)
Adds a work-center capacity decision to the solution.
void AddDistributionDecision(DistributionDecision decision)
Adds a distribution decision to the solution.
bool IsInternallyValid
Gets a value indicating whether the solution is internally consistent.
List< WarehouseCapacityDecision > WarehouseCapacityDecisions
Gets the global warehouse-capacity decisions contained in the solution.
void AddTransportResourceCapacityDecision(TransportResourceCapacityDecision decision)
Adds a transport-resource capacity decision to the solution.
SolutionEvaluation Evaluation
Gets or sets the independent evaluation of the solution.
void AddProductionDecision(ProductionDecision decision)
Adds a production decision to the solution.
LotSizingSolution(int planningHorizon)
Initializes a lot-sizing solution for a planning horizon.
bool HasDecisions
Gets a value indicating whether the solution contains at least one decision object.
List< TransportResourceCapacityDecision > TransportResourceCapacityDecisions
Gets the global transport-resource capacity decisions contained in the solution.
string Name
Gets or sets the human-readable name of the solution.
List< InventoryDecision > InventoryDecisions
Gets the inventory decisions contained in the solution.
SolutionCompleteness
Indicates whether a lot-sizing solution contains all decision values expected for its associated inst...