LotSizingDataModel.Core 2.0.1
Core domain model, shared abstractions and XML-serializable entities.
Loading...
Searching...
No Matches
ResourceLoadAnalyzer.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
8
10
11/// <summary>
12/// Aggregates the capacity loads generated by several items
13/// using the same resource during one planning period.
14///
15/// The analyzer supports:
16/// - production work centers;
17/// - warehouses;
18/// - transport resources.
19///
20/// It does not modify the supply-chain model.
21/// </summary>
22public sealed class ResourceLoadAnalyzer
23{
24 /// <summary>
25 /// Initializes a resource-load analyzer and creates
26 /// a new supply-chain index.
27 /// </summary>
29 : this(
31 supplyChain ??
32 throw new ArgumentNullException(
33 nameof(supplyChain))))
34 {
35 }
36
37 /// <summary>
38 /// Initializes a resource-load analyzer using
39 /// an existing supply-chain index.
40 /// </summary>
42 {
43 Index = index ??
44 throw new ArgumentNullException(nameof(index));
45
47 Queries = new SupplyChainQueries(index);
48 }
49
50 /// <summary>
51 /// Gets the analyzed supply chain.
52 /// </summary>
53 public SupplyChain SupplyChain { get; }
54
55 /// <summary>
56 /// Gets the entity index used by the analyzer.
57 /// </summary>
58 public SupplyChainIndex Index { get; }
59
60 /// <summary>
61 /// Gets the query service used to resolve relationships.
62 /// </summary>
64
65 /// <summary>
66 /// Rebuilds the underlying entity index.
67 ///
68 /// Call this method after directly changing entity
69 /// collections in the supply chain.
70 /// </summary>
71 public void RebuildIndex()
72 {
73 Index.Rebuild();
74 }
75
76 #region Production load
77
78 /// <summary>
79 /// Aggregates the production loads generated by several
80 /// items on the same work center during one period.
81 ///
82 /// Several entries referring to the same item are combined.
83 /// Their quantities are summed and setup is counted once
84 /// when at least one entry activates it.
85 /// </summary>
87 WorkCenterReference workCenterReference,
88 int period,
89 IEnumerable<ItemResourceActivity> activities)
90 {
91 ArgumentNullException.ThrowIfNull(
92 workCenterReference);
93
94 ValidatePeriod(period);
95
96 WorkCenter workCenter =
97 Index.GetRequiredWorkCenter(
98 workCenterReference);
99
100 IReadOnlyList<NormalizedActivity>
101 normalizedActivities =
102 NormalizeActivities(activities);
103
104 var itemLoads =
105 new List<ItemLoadResult>(
106 normalizedActivities.Count);
107
108 foreach (NormalizedActivity activity
109 in normalizedActivities)
110 {
111 ProductionCharacteristic characteristic =
112 Queries.GetRequiredProductionCharacteristic(
113 activity.ItemId,
114 workCenterReference);
115
116 double unitCapacity =
117 characteristic.UnitCapacityConsumption
118 ?.GetConsumption(period) ??
119 0.0;
120
121 double setupCapacity =
122 activity.SetupActivated
123 ? characteristic.SetupTime
124 ?.GetSetupTime(period) ??
125 0.0
126 : 0.0;
127
128 itemLoads.Add(
129 CreateItemLoad(
130 activity,
131 unitCapacity,
132 setupCapacity,
133 itemSpecificRegularCapacity: null,
134 itemSpecificAdditionalCapacity: null));
135 }
136
137 return CreateReport(
138 ResourceLoadKind.Production,
139 resourceKey:
140 $"{workCenterReference.PlantId}:" +
141 $"{workCenterReference.WorkCenterId}",
142 resourceName: workCenter.Name,
143 period,
144 itemLoads,
145 globalRegularCapacity:
146 workCenter.CapacityConstraint
147 ?.GetMaximumCapacity(period),
148 globalAdditionalCapacity:
149 workCenter.AdditionalCapacity
150 ?.GetMaximumAdditionalCapacity(period));
151 }
152
153 #endregion
154
155 #region Warehouse load
156
157 /// <summary>
158 /// Aggregates the storage loads generated by several items
159 /// in the same warehouse during one period.
160 ///
161 /// The quantity of an activity represents the inventory
162 /// level of the corresponding item during the period.
163 /// </summary>
165 WarehouseReference warehouseReference,
166 int period,
167 IEnumerable<ItemResourceActivity> activities)
168 {
169 ArgumentNullException.ThrowIfNull(
170 warehouseReference);
171
172 ValidatePeriod(period);
173
174 Warehouse warehouse =
175 Index.GetRequiredWarehouse(
176 warehouseReference);
177
178 IReadOnlyList<NormalizedActivity>
179 normalizedActivities =
180 NormalizeActivities(activities);
181
182 var itemLoads =
183 new List<ItemLoadResult>(
184 normalizedActivities.Count);
185
186 foreach (NormalizedActivity activity
187 in normalizedActivities)
188 {
189 Inventory inventory =
190 Queries.GetRequiredInventory(
191 activity.ItemId,
192 warehouseReference);
193
194 double unitCapacity =
195 inventory.UnitCapacityConsumption
196 ?.GetConsumption(period) ??
197 0.0;
198
199 double setupCapacity =
200 activity.SetupActivated
201 ? inventory.SetupTime
202 ?.GetSetupTime(period) ??
203 0.0
204 : 0.0;
205
206 itemLoads.Add(
207 CreateItemLoad(
208 activity,
209 unitCapacity,
210 setupCapacity,
211 itemSpecificRegularCapacity:
212 inventory.CapacityConstraint
213 ?.GetMaximumCapacity(period),
214 itemSpecificAdditionalCapacity:
215 inventory.AdditionalCapacity
216 ?.GetMaximumAdditionalCapacity(
217 period)));
218 }
219
220 return CreateReport(
221 ResourceLoadKind.Storage,
222 resourceKey:
223 $"{warehouseReference.Kind}:" +
224 $"{warehouseReference.ReferenceId}",
225 resourceName: warehouse.Name,
226 period,
227 itemLoads,
228 globalRegularCapacity:
229 warehouse.CapacityConstraint
230 ?.GetMaximumCapacity(period),
231 globalAdditionalCapacity:
232 warehouse.AdditionalCapacity
233 ?.GetMaximumAdditionalCapacity(period));
234 }
235
236 #endregion
237
238 #region Transport load
239
240 /// <summary>
241 /// Aggregates the transport loads generated by several items
242 /// using the same transport resource during one period.
243 ///
244 /// The quantity of an activity must represent the total
245 /// quantity transported by the resource for the item during
246 /// the period, over all its transport lanes.
247 /// </summary>
249 int transportResourceId,
250 int period,
251 IEnumerable<ItemResourceActivity> activities)
252 {
253 ValidatePeriod(period);
254
255 TransportResource resource =
256 Index.GetRequiredTransportResource(
257 transportResourceId);
258
259 IReadOnlyList<NormalizedActivity>
260 normalizedActivities =
261 NormalizeActivities(activities);
262
263 var itemLoads =
264 new List<ItemLoadResult>(
265 normalizedActivities.Count);
266
267 foreach (NormalizedActivity activity
268 in normalizedActivities)
269 {
270 TransportCharacteristic characteristic =
271 Queries.GetRequiredTransportCharacteristic(
272 activity.ItemId,
273 transportResourceId);
274
275 double unitCapacity =
276 characteristic.UnitCapacityConsumption
277 ?.GetConsumption(period) ??
278 0.0;
279
280 double setupCapacity =
281 activity.SetupActivated
282 ? characteristic.SetupTime
283 ?.GetSetupTime(period) ??
284 0.0
285 : 0.0;
286
287 itemLoads.Add(
288 CreateItemLoad(
289 activity,
290 unitCapacity,
291 setupCapacity,
292 itemSpecificRegularCapacity:
293 characteristic.CapacityConstraint
294 ?.GetMaximumCapacity(period),
295 itemSpecificAdditionalCapacity:
296 characteristic.AdditionalCapacity
297 ?.GetMaximumAdditionalCapacity(
298 period)));
299 }
300
301 return CreateReport(
302 ResourceLoadKind.Transport,
303 resourceKey: transportResourceId.ToString(),
304 resourceName: resource.Name,
305 period,
306 itemLoads,
307 globalRegularCapacity:
308 resource.CapacityConstraint
309 ?.GetMaximumCapacity(period),
310 globalAdditionalCapacity:
311 resource.AdditionalCapacity
312 ?.GetMaximumAdditionalCapacity(period));
313 }
314
315 #endregion
316
317 #region Private calculations
318
319 private ResourceLoadReport CreateReport(
320 ResourceLoadKind resourceKind,
321 string resourceKey,
322 string resourceName,
323 int period,
324 IEnumerable<ItemLoadResult> itemLoads,
325 double? globalRegularCapacity,
326 double? globalAdditionalCapacity)
327 {
328 ValidateOptionalCapacityConfiguration(
329 globalRegularCapacity,
330 globalAdditionalCapacity,
331 "global resource capacity");
332
333 return new ResourceLoadReport(
334 resourceKind,
335 resourceKey,
336 resourceName,
337 period,
338 itemLoads,
339 globalRegularCapacity,
340 globalAdditionalCapacity);
341 }
342
343 private static ItemLoadResult CreateItemLoad(
344 NormalizedActivity activity,
345 double unitCapacity,
346 double setupCapacity,
347 double? itemSpecificRegularCapacity,
348 double? itemSpecificAdditionalCapacity)
349 {
350 ValidateNonNegativeFiniteValue(
351 unitCapacity,
352 nameof(unitCapacity));
353
354 ValidateNonNegativeFiniteValue(
355 setupCapacity,
356 nameof(setupCapacity));
357
358 ValidateOptionalCapacityConfiguration(
359 itemSpecificRegularCapacity,
360 itemSpecificAdditionalCapacity,
361 "item-specific capacity");
362
363 double quantityCapacity =
364 activity.Quantity * unitCapacity;
365
366 if (!double.IsFinite(quantityCapacity))
367 {
368 throw new InvalidOperationException(
369 "The calculated unit-capacity requirement " +
370 "exceeds the supported numerical range.");
371 }
372
373 double requiredCapacity =
374 quantityCapacity + setupCapacity;
375
376 if (!double.IsFinite(requiredCapacity))
377 {
378 throw new InvalidOperationException(
379 "The calculated resource load exceeds the " +
380 "supported numerical range.");
381 }
382
383 return new ItemLoadResult(
384 activity.ItemId,
385 activity.Quantity,
386 activity.SetupActivated,
387 unitCapacity,
388 setupCapacity,
389 requiredCapacity,
390 itemSpecificRegularCapacity,
391 itemSpecificAdditionalCapacity);
392 }
393
394 private IReadOnlyList<NormalizedActivity>
395 NormalizeActivities(
396 IEnumerable<ItemResourceActivity> activities)
397 {
398 ArgumentNullException.ThrowIfNull(activities);
399
400 var normalized =
401 new Dictionary<int, NormalizedActivity>();
402
403 foreach (ItemResourceActivity activity
404 in activities)
405 {
406 if (activity is null)
407 {
408 throw new ArgumentException(
409 "The activity collection cannot contain null.",
410 nameof(activities));
411 }
412
413 Index.GetRequiredItem(activity.ItemId);
414
415 ValidateNonNegativeFiniteValue(
416 activity.Quantity,
417 nameof(activity.Quantity));
418
419 if (!normalized.TryGetValue(
420 activity.ItemId,
421 out NormalizedActivity? existing))
422 {
423 normalized.Add(
424 activity.ItemId,
425 new NormalizedActivity(
426 activity.ItemId,
427 activity.Quantity,
428 activity.SetupActivated));
429
430 continue;
431 }
432
433 double totalQuantity =
434 existing.Quantity +
435 activity.Quantity;
436
437 if (!double.IsFinite(totalQuantity))
438 {
439 throw new InvalidOperationException(
440 $"The aggregated quantity for item " +
441 $"{activity.ItemId} exceeds the supported " +
442 "numerical range.");
443 }
444
445 normalized[activity.ItemId] =
446 new NormalizedActivity(
447 activity.ItemId,
448 totalQuantity,
449 existing.SetupActivated ||
450 activity.SetupActivated);
451 }
452
453 return normalized.Values
454 .OrderBy(activity => activity.ItemId)
455 .ToArray();
456 }
457
458 private void ValidatePeriod(int period)
459 {
460 if (period < 1 ||
461 period > SupplyChain.PlanningHorizon)
462 {
463 throw new ArgumentOutOfRangeException(
464 nameof(period),
465 period,
466 $"The period must be between 1 and " +
467 $"{SupplyChain.PlanningHorizon}.");
468 }
469 }
470
471 private static void
472 ValidateOptionalCapacityConfiguration(
473 double? regularCapacity,
474 double? additionalCapacity,
475 string capacityName)
476 {
477 if (regularCapacity.HasValue)
478 {
479 ValidateNonNegativeFiniteValue(
480 regularCapacity.Value,
481 nameof(regularCapacity));
482 }
483
484 if (additionalCapacity.HasValue)
485 {
486 ValidateNonNegativeFiniteValue(
487 additionalCapacity.Value,
488 nameof(additionalCapacity));
489
490 if (!regularCapacity.HasValue)
491 {
492 throw new InvalidOperationException(
493 $"The {capacityName} defines additional " +
494 "capacity without regular capacity.");
495 }
496 }
497 }
498
499 private static void ValidateNonNegativeFiniteValue(
500 double value,
501 string parameterName)
502 {
503 if (!double.IsFinite(value) ||
504 value < 0.0)
505 {
506 throw new ArgumentOutOfRangeException(
507 parameterName,
508 value,
509 "The value must be finite and non-negative.");
510 }
511 }
512
513 #endregion
514
515 /// <summary>
516 /// Identifies the type of resource represented
517 /// by a load report.
518 /// </summary>
520 {
521 /// <summary>
522 /// Production resource such as a work center.
523 /// </summary>
525
526 /// <summary>
527 /// Storage resource such as a warehouse.
528 /// </summary>
530
531 /// <summary>
532 /// Transport resource.
533 /// </summary>
535 }
536
537 /// <summary>
538 /// Represents the activity of one item on a resource
539 /// during one planning period.
540 /// </summary>
541 public sealed class ItemResourceActivity
542 {
543 /// <summary>
544 /// Initializes an item-resource activity.
545 /// </summary>
546 /// <param name="itemId">
547 /// Identifier of the processed item.
548 /// </param>
549 /// <param name="quantity">
550 /// Non-negative finite quantity processed, stored
551 /// or transported during the period.
552 /// </param>
553 /// <param name="setupActivated">
554 /// Indicates whether the setup capacity must be included.
555 /// </param>
557 int itemId,
558 double quantity,
559 bool setupActivated)
560 {
561 if (itemId <= 0)
562 {
563 throw new ArgumentOutOfRangeException(
564 nameof(itemId),
565 itemId,
566 "The item identifier must be strictly positive.");
567 }
568
569 ValidateNonNegativeFiniteValue(
570 quantity,
571 nameof(quantity));
572
573 ItemId = itemId;
574 Quantity = quantity;
575 SetupActivated = setupActivated;
576 }
577
578 /// <summary>
579 /// Gets the item identifier.
580 /// </summary>
581 public int ItemId { get; }
582
583 /// <summary>
584 /// Gets the processed, stored or transported quantity.
585 /// </summary>
586 public double Quantity { get; }
587
588 /// <summary>
589 /// Gets a value indicating whether setup is activated.
590 /// </summary>
591 public bool SetupActivated { get; }
592 }
593
594 /// <summary>
595 /// Internal activity obtained after grouping all entries
596 /// referring to the same item.
597 /// </summary>
598 private sealed class NormalizedActivity
599 {
600 public NormalizedActivity(
601 int itemId,
602 double quantity,
603 bool setupActivated)
604 {
605 ItemId = itemId;
606 Quantity = quantity;
607 SetupActivated = setupActivated;
608 }
609
610 public int ItemId { get; }
611
612 public double Quantity { get; }
613
614 public bool SetupActivated { get; }
615 }
616
617 /// <summary>
618 /// Represents the load generated by one item on a resource.
619 /// </summary>
620 public sealed class ItemLoadResult
621 {
622 /// <summary>
623 /// Initializes the capacity-load result of one item
624 /// on a resource during one planning period.
625 /// </summary>
626 /// <param name="itemId">
627 /// Identifier of the item.
628 /// </param>
629 /// <param name="quantity">
630 /// Quantity processed, stored or transported.
631 /// </param>
632 /// <param name="setupActivated">
633 /// Indicates whether setup is activated.
634 /// </param>
635 /// <param name="unitCapacity">
636 /// Capacity consumed per quantity unit.
637 /// </param>
638 /// <param name="setupCapacity">
639 /// Setup capacity included in the load.
640 /// </param>
641 /// <param name="requiredCapacity">
642 /// Total capacity required by the item.
643 /// </param>
644 /// <param name="maximumRegularCapacity">
645 /// Item-specific regular-capacity limit,
646 /// or null when no such limit exists.
647 /// </param>
648 /// <param name="maximumAdditionalCapacity">
649 /// Item-specific additional-capacity limit,
650 /// or null when no such limit exists.
651 /// </param>
653 int itemId,
654 double quantity,
655 bool setupActivated,
656 double unitCapacity,
657 double setupCapacity,
658 double requiredCapacity,
659 double? maximumRegularCapacity,
660 double? maximumAdditionalCapacity)
661 {
662 ValidateNonNegativeFiniteValue(
663 quantity,
664 nameof(quantity));
665
666 ValidateNonNegativeFiniteValue(
667 unitCapacity,
668 nameof(unitCapacity));
669
670 ValidateNonNegativeFiniteValue(
671 setupCapacity,
672 nameof(setupCapacity));
673
674 ValidateNonNegativeFiniteValue(
675 requiredCapacity,
676 nameof(requiredCapacity));
677
678 ValidateOptionalCapacityConfiguration(
679 maximumRegularCapacity,
680 maximumAdditionalCapacity,
681 "item-specific capacity");
682
683 ItemId = itemId;
684 Quantity = quantity;
685 SetupActivated = setupActivated;
686 UnitCapacity = unitCapacity;
687 SetupCapacity = setupCapacity;
688 RequiredCapacity = requiredCapacity;
689
691 maximumRegularCapacity;
692
694 maximumAdditionalCapacity;
695 }
696
697 /// <summary>
698 /// Gets the item identifier.
699 /// </summary>
700 public int ItemId { get; }
701
702 /// <summary>
703 /// Gets the item quantity.
704 /// </summary>
705 public double Quantity { get; }
706
707 /// <summary>
708 /// Gets a value indicating whether setup is activated.
709 /// </summary>
710 public bool SetupActivated { get; }
711
712 /// <summary>
713 /// Gets the capacity consumed per item unit.
714 /// </summary>
715 public double UnitCapacity { get; }
716
717 /// <summary>
718 /// Gets the setup capacity included in the load.
719 /// </summary>
720 public double SetupCapacity { get; }
721
722 /// <summary>
723 /// Gets the total capacity required by the item.
724 /// </summary>
725 public double RequiredCapacity { get; }
726
727 /// <summary>
728 /// Gets the item-specific regular-capacity limit.
729 ///
730 /// Null means that no item-specific limit exists.
731 /// </summary>
732 public double? MaximumRegularCapacity { get; }
733
734 /// <summary>
735 /// Gets the item-specific additional-capacity limit.
736 /// </summary>
737 public double? MaximumAdditionalCapacity { get; }
738
739 /// <summary>
740 /// Gets a value indicating whether an item-specific
741 /// capacity constraint exists.
742 /// </summary>
744 MaximumRegularCapacity.HasValue;
745
746 /// <summary>
747 /// Gets the item-specific total maximum capacity.
748 /// </summary>
749 public double? MaximumTotalCapacity =>
750 MaximumRegularCapacity.HasValue
751 ? MaximumRegularCapacity.Value +
753 : null;
754
755 /// <summary>
756 /// Gets the additional capacity required beyond
757 /// the regular item-specific capacity.
758 /// </summary>
760 MaximumRegularCapacity.HasValue
761 ? Math.Max(
762 0.0,
765 : 0.0;
766
767 /// <summary>
768 /// Gets a value indicating whether the item load fits
769 /// its regular item-specific capacity.
770 /// </summary>
771 public bool FitsRegularCapacity =>
772 !MaximumRegularCapacity.HasValue ||
775
776 /// <summary>
777 /// Gets a value indicating whether the item load fits
778 /// its regular plus additional item-specific capacity.
779 /// </summary>
780 public bool FitsTotalCapacity =>
781 !MaximumTotalCapacity.HasValue ||
784
785 /// <summary>
786 /// Gets the amount exceeding the item-specific
787 /// maximum capacity.
788 /// </summary>
789 public double CapacityExcess =>
790 MaximumTotalCapacity.HasValue
791 ? Math.Max(
792 0.0,
795 : 0.0;
796
797 /// <inheritdoc/>
798 public override string ToString()
799 {
801 {
802 return
803 $"Item {ItemId}: required " +
804 $"{RequiredCapacity}; no item-specific limit";
805 }
806
807 return
808 $"Item {ItemId}: required {RequiredCapacity}; " +
809 $"maximum {MaximumTotalCapacity}; " +
810 $"feasible {FitsTotalCapacity}";
811 }
812 }
813
814 /// <summary>
815 /// Represents the aggregated load of one resource
816 /// during one planning period.
817 /// </summary>
818 public sealed class ResourceLoadReport
819 {
820 private readonly ItemLoadResult[] _itemLoads;
821 /// <summary>
822 /// Initializes a report describing the aggregated load
823 /// of a resource during one planning period.
824 /// </summary>
825 /// <param name="resourceKind">
826 /// Type of resource being analyzed: production, storage
827 /// or transport.
828 /// </param>
829 /// <param name="resourceKey">
830 /// Technical key uniquely identifying the resource.
831 /// </param>
832 /// <param name="resourceName">
833 /// Human-readable name of the resource.
834 /// </param>
835 /// <param name="period">
836 /// One-based planning period analyzed by the report.
837 /// </param>
838 /// <param name="itemLoads">
839 /// Individual item loads aggregated on the resource.
840 /// </param>
841 /// <param name="maximumRegularCapacity">
842 /// Maximum regular capacity available during the period,
843 /// or null when no global regular-capacity constraint exists.
844 /// </param>
845 /// <param name="maximumAdditionalCapacity">
846 /// Maximum additional capacity available during the period,
847 /// or null when no global additional capacity is defined.
848 /// </param>
849 /// <exception cref="ArgumentException">
850 /// Thrown when the resource key is empty or invalid.
851 /// </exception>
852 /// <exception cref="ArgumentNullException">
853 /// Thrown when the item-load collection is null.
854 /// </exception>
855 /// <exception cref="ArgumentOutOfRangeException">
856 /// Thrown when the planning period is not strictly positive.
857 /// </exception>
859 ResourceLoadKind resourceKind,
860 string resourceKey,
861 string resourceName,
862 int period,
863 IEnumerable<ItemLoadResult> itemLoads,
864 double? maximumRegularCapacity,
865 double? maximumAdditionalCapacity)
866 {
867 ArgumentException.ThrowIfNullOrWhiteSpace(
868 resourceKey);
869
870 ArgumentNullException.ThrowIfNull(itemLoads);
871
872 if (period <= 0)
873 {
874 throw new ArgumentOutOfRangeException(
875 nameof(period),
876 period,
877 "The period must be strictly positive.");
878 }
879
880 ValidateOptionalCapacityConfiguration(
881 maximumRegularCapacity,
882 maximumAdditionalCapacity,
883 "global resource capacity");
884
885 ResourceKind = resourceKind;
886 ResourceKey = resourceKey;
887 ResourceName = resourceName ?? string.Empty;
888 Period = period;
889
890 _itemLoads = itemLoads
891 .OrderBy(load => load.ItemId)
892 .ToArray();
893
895 maximumRegularCapacity;
896
898 maximumAdditionalCapacity;
899
901 CalculateTotalRequiredCapacity(
902 _itemLoads);
903 }
904
905 /// <summary>
906 /// Gets the resource type.
907 /// </summary>
909
910 /// <summary>
911 /// Gets the technical resource key.
912 /// </summary>
913 public string ResourceKey { get; }
914
915 /// <summary>
916 /// Gets the resource name.
917 /// </summary>
918 public string ResourceName { get; }
919
920 /// <summary>
921 /// Gets the analyzed planning period.
922 /// </summary>
923 public int Period { get; }
924
925 /// <summary>
926 /// Gets the individual item loads.
927 /// </summary>
928 public IReadOnlyList<ItemLoadResult> ItemLoads =>
929 Array.AsReadOnly(_itemLoads);
930
931 /// <summary>
932 /// Gets the number of loaded item types.
933 /// </summary>
934 public int ItemCount => _itemLoads.Length;
935
936 /// <summary>
937 /// Gets the aggregated required capacity.
938 /// </summary>
939 public double TotalRequiredCapacity { get; }
940
941 /// <summary>
942 /// Gets the global regular-capacity limit.
943 /// </summary>
944 public double? MaximumRegularCapacity { get; }
945
946 /// <summary>
947 /// Gets the global additional-capacity limit.
948 /// </summary>
949 public double? MaximumAdditionalCapacity { get; }
950
951 /// <summary>
952 /// Gets a value indicating whether the resource
953 /// has a global capacity constraint.
954 /// </summary>
956 MaximumRegularCapacity.HasValue;
957
958 /// <summary>
959 /// Gets the global total maximum capacity.
960 /// </summary>
961 public double? MaximumTotalCapacity =>
962 MaximumRegularCapacity.HasValue
963 ? MaximumRegularCapacity.Value +
965 : null;
966
967 /// <summary>
968 /// Gets the additional capacity required beyond
969 /// the global regular capacity.
970 /// </summary>
972 MaximumRegularCapacity.HasValue
973 ? Math.Max(
974 0.0,
977 : 0.0;
978
979 /// <summary>
980 /// Gets a value indicating whether the aggregated load
981 /// fits the regular global capacity.
982 /// </summary>
983 public bool FitsRegularCapacity =>
984 !MaximumRegularCapacity.HasValue ||
987
988 /// <summary>
989 /// Gets a value indicating whether the aggregated load
990 /// fits the regular plus additional global capacity.
991 /// </summary>
992 public bool FitsTotalCapacity =>
993 !MaximumTotalCapacity.HasValue ||
996
997 /// <summary>
998 /// Gets the amount exceeding the global maximum capacity.
999 /// </summary>
1000 public double CapacityExcess =>
1001 MaximumTotalCapacity.HasValue
1002 ? Math.Max(
1003 0.0,
1006 : 0.0;
1007
1008 /// <summary>
1009 /// Gets a value indicating whether all item-specific
1010 /// capacity constraints are respected.
1011 /// </summary>
1013 _itemLoads.All(
1014 load => load.FitsTotalCapacity);
1015
1016 /// <summary>
1017 /// Gets the item loads exceeding an item-specific
1018 /// capacity limit.
1019 /// </summary>
1020 public IReadOnlyList<ItemLoadResult>
1022 _itemLoads
1023 .Where(
1024 load =>
1025 !load.FitsTotalCapacity)
1026 .ToArray();
1027
1028 /// <summary>
1029 /// Gets a value indicating whether both the global
1030 /// capacity and all item-specific capacities are respected.
1031 /// </summary>
1032 public bool IsFeasible =>
1035
1036 /// <summary>
1037 /// Returns a textual summary of the resource load,
1038 /// its available capacity and its feasibility.
1039 /// </summary>
1040 /// <returns>
1041 /// A human-readable description of the resource-load report.
1042 /// </returns>
1043 public override string ToString()
1044 {
1045 string capacityDescription =
1047 ? $"maximum {MaximumTotalCapacity}"
1048 : "no global capacity limit";
1049
1050 return
1051 $"{ResourceKind} resource {ResourceKey}, " +
1052 $"period {Period}: required " +
1053 $"{TotalRequiredCapacity}, " +
1054 $"{capacityDescription}, " +
1055 $"feasible {IsFeasible}";
1056 }
1057
1058 private static double
1059 CalculateTotalRequiredCapacity(
1060 IEnumerable<ItemLoadResult> loads)
1061 {
1062 double total = 0.0;
1063
1064 foreach (ItemLoadResult load in loads)
1065 {
1066 total += load.RequiredCapacity;
1067
1068 if (!double.IsFinite(total))
1069 {
1070 throw new InvalidOperationException(
1071 "The aggregated resource load exceeds " +
1072 "the supported numerical range.");
1073 }
1074 }
1075
1076 return total;
1077 }
1078 }
1079}
Represents the load generated by one item on a resource.
double? MaximumRegularCapacity
Gets the item-specific regular-capacity limit.
ItemLoadResult(int itemId, double quantity, bool setupActivated, double unitCapacity, double setupCapacity, double requiredCapacity, double? maximumRegularCapacity, double? maximumAdditionalCapacity)
Initializes the capacity-load result of one item on a resource during one planning period.
bool FitsTotalCapacity
Gets a value indicating whether the item load fits its regular plus additional item-specific capacity...
double? MaximumTotalCapacity
Gets the item-specific total maximum capacity.
double RequiredCapacity
Gets the total capacity required by the item.
bool SetupActivated
Gets a value indicating whether setup is activated.
bool FitsRegularCapacity
Gets a value indicating whether the item load fits its regular item-specific capacity.
bool HasItemSpecificCapacity
Gets a value indicating whether an item-specific capacity constraint exists.
double SetupCapacity
Gets the setup capacity included in the load.
double RequiredAdditionalCapacity
Gets the additional capacity required beyond the regular item-specific capacity.
double? MaximumAdditionalCapacity
Gets the item-specific additional-capacity limit.
double CapacityExcess
Gets the amount exceeding the item-specific maximum capacity.
Represents the activity of one item on a resource during one planning period.
double Quantity
Gets the processed, stored or transported quantity.
ItemResourceActivity(int itemId, double quantity, bool setupActivated)
Initializes an item-resource activity.
bool SetupActivated
Gets a value indicating whether setup is activated.
Represents the aggregated load of one resource during one planning period.
override string ToString()
Returns a textual summary of the resource load, its available capacity and its feasibility.
bool HasGlobalCapacityConstraint
Gets a value indicating whether the resource has a global capacity constraint.
ResourceLoadReport(ResourceLoadKind resourceKind, string resourceKey, string resourceName, int period, IEnumerable< ItemLoadResult > itemLoads, double? maximumRegularCapacity, double? maximumAdditionalCapacity)
Initializes a report describing the aggregated load of a resource during one planning period.
double RequiredAdditionalCapacity
Gets the additional capacity required beyond the global regular capacity.
double CapacityExcess
Gets the amount exceeding the global maximum capacity.
bool AllItemSpecificCapacitiesSatisfied
Gets a value indicating whether all item-specific capacity constraints are respected.
IReadOnlyList< ItemLoadResult > ItemLoads
Gets the individual item loads.
bool FitsTotalCapacity
Gets a value indicating whether the aggregated load fits the regular plus additional global capacity.
bool IsFeasible
Gets a value indicating whether both the global capacity and all item-specific capacities are respect...
IReadOnlyList< ItemLoadResult > ItemSpecificCapacityViolations
Gets the item loads exceeding an item-specific capacity limit.
bool FitsRegularCapacity
Gets a value indicating whether the aggregated load fits the regular global capacity.
double? MaximumAdditionalCapacity
Gets the global additional-capacity limit.
ResourceLoadReport AnalyzeWorkCenterLoad(WorkCenterReference workCenterReference, int period, IEnumerable< ItemResourceActivity > activities)
Aggregates the production loads generated by several items on the same work center during one period.
SupplyChainQueries Queries
Gets the query service used to resolve relationships.
SupplyChainIndex Index
Gets the entity index used by the analyzer.
void RebuildIndex()
Rebuilds the underlying entity index.
ResourceLoadAnalyzer(SupplyChain supplyChain)
Initializes a resource-load analyzer and creates a new supply-chain index.
ResourceLoadAnalyzer(SupplyChainIndex index)
Initializes a resource-load analyzer using an existing supply-chain index.
SupplyChain SupplyChain
Gets the analyzed supply chain.
ResourceLoadReport AnalyzeTransportResourceLoad(int transportResourceId, int period, IEnumerable< ItemResourceActivity > activities)
Aggregates the transport loads generated by several items using the same transport resource during on...
ResourceLoadReport AnalyzeWarehouseLoad(WarehouseReference warehouseReference, int period, IEnumerable< ItemResourceActivity > activities)
Aggregates the storage loads generated by several items in the same warehouse during one period.
ResourceLoadKind
Identifies the type of resource represented by a load report.
Provides fast access to the entities contained in a supply chain.
SupplyChain SupplyChain
Gets the indexed supply chain.
A transport resource with its own shared capacities and costs.
Represents a warehouse in the supply chain.
Definition Warehouse.cs:19
Represents a serializable reference to a warehouse.
Represents a production work center belonging to a plant.
Definition WorkCenter.cs:15
Represents a serializable reference to a work center.
Provides high-level read-only queries over a supply-chain model.
Represents the inventory of an item in a warehouse.
Definition Inventory.cs:20
AdditionalCapacity? AdditionalCapacity
Gets or sets the maximum additional storage capacity available specifically for this item.
CapacityConstraint? CapacityConstraint
Gets or sets the maximum storage capacity reserved for this item during each planning period.
Represents the production characteristics of an item on a specific work center.
Represents the transport characteristics of an item for a specific transport resource.
AdditionalCapacity? AdditionalCapacity
Gets or sets the maximum additional transport capacity available specifically for this item.
CapacityConstraint? CapacityConstraint
Gets or sets the maximum transport capacity available specifically for this item during each planning...