2using System.Collections.Generic;
20 private readonly Dictionary<int, List<Demand>>
21 _demandsByItem =
new();
23 private readonly Dictionary<int, List<Demand>>
24 _demandsByDistributionCenter =
new();
36 throw new ArgumentNullException(
37 nameof(supplyChain))))
51 throw new ArgumentNullException(nameof(index));
78 _demandsByItem.Clear();
79 _demandsByDistributionCenter.Clear();
89 Index.GetRequiredDistributionCenter(
92 ValidateDemandHorizon(demand);
100 _demandsByDistributionCenter,
106 #region Demand relationship queries
114 Index.GetRequiredItem(itemId);
116 if (!_demandsByItem.TryGetValue(
118 out List<Demand>? demands))
120 return Array.Empty<
Demand>();
123 return demands.ToArray();
129 public IReadOnlyList<Demand>
131 int distributionCenterId)
133 Index.GetRequiredDistributionCenter(
134 distributionCenterId);
136 if (!_demandsByDistributionCenter.TryGetValue(
137 distributionCenterId,
138 out List<Demand>? demands))
140 return Array.Empty<
Demand>();
143 return demands.ToArray();
154 int distributionCenterId)
156 Index.GetRequiredItem(itemId);
158 Index.GetRequiredDistributionCenter(
159 distributionCenterId);
161 if (!_demandsByItem.TryGetValue(
163 out List<Demand>? demands))
168 return demands.FirstOrDefault(
171 distributionCenterId);
180 int distributionCenterId)
184 distributionCenterId) ??
185 throw new KeyNotFoundException(
186 $
"No demand exists for item {itemId} and " +
187 $
"distribution center {distributionCenterId}.");
192 #region Period calculations
200 int distributionCenterId,
203 ValidatePeriod(period);
207 distributionCenterId)
219 ValidatePeriod(period);
221 IReadOnlyList<Demand> demands =
234 int distributionCenterId,
237 ValidatePeriod(period);
239 IReadOnlyList<Demand> demands =
241 distributionCenterId);
254 ValidatePeriod(period);
263 #region Demand profiles
271 IReadOnlyList<Demand> demands =
274 return CreateProfile(demands);
283 int distributionCenterId)
285 IReadOnlyList<Demand> demands =
287 distributionCenterId);
289 return CreateProfile(demands);
298 return CreateProfile(
308 int distributionCenterId)
313 distributionCenterId);
315 double[] quantities =
322 quantities[period - 1] =
331 #region Cumulative calculations
365 int distributionCenterId)
368 distributionCenterId)
388 #region Private helpers
390 private DemandProfile CreateProfile(
391 IEnumerable<Demand> demands)
393 ArgumentNullException.ThrowIfNull(demands);
398 double[] quantities =
405 quantities[period - 1] =
411 return new DemandProfile(quantities);
414 private static double SumDemands(
415 IEnumerable<Demand> demands,
420 foreach (
Demand demand
in demands)
425 if (!
double.IsFinite(quantity) ||
428 throw new InvalidOperationException(
429 "A demand quantity must be finite " +
430 "and non-negative.");
435 if (!
double.IsFinite(total))
437 throw new InvalidOperationException(
438 "The aggregated demand exceeds the " +
439 "supported numerical range.");
446 private void ValidateDemandHorizon(
452 throw new InvalidOperationException(
453 $
"The demand for item {demand.ItemId} and " +
454 $
"distribution center " +
455 $
"{demand.DistributionCenterId} uses a " +
456 $
"planning horizon of {demand.PlanningHorizon}, " +
457 $
"whereas the global planning horizon is " +
458 $
"{SupplyChain.PlanningHorizon}.");
462 private void ValidatePeriod(
int period)
467 throw new ArgumentOutOfRangeException(
470 $
"The period must be between 1 and " +
471 $
"{SupplyChain.PlanningHorizon}.");
475 private static void AddDemand(
476 IDictionary<
int, List<Demand>> dictionary,
480 if (!dictionary.TryGetValue(
482 out List<Demand>? demands))
484 demands =
new List<Demand>();
503 private readonly
double[] _quantities;
509 IEnumerable<double> quantities)
511 ArgumentNullException.ThrowIfNull(quantities);
513 _quantities = quantities.ToArray();
516 index < _quantities.Length;
522 if (!
double.IsFinite(quantity) ||
525 throw new ArgumentOutOfRangeException(
528 "A demand-profile quantity must be " +
529 "finite and non-negative.");
534 CalculateTotalQuantity();
537 _quantities.Length == 0
557 (
double[])_quantities.Clone());
562 public double this[
int period]
566 ValidateProfilePeriod(period);
568 return _quantities[period - 1];
602 ValidateProfilePeriod(startPeriod);
603 ValidateProfilePeriod(endPeriod);
605 if (startPeriod > endPeriod)
607 throw new ArgumentException(
608 "The start period cannot be greater " +
609 "than the end period.");
614 for (
int period = startPeriod;
618 total +=
this[period];
620 if (!
double.IsFinite(total))
622 throw new InvalidOperationException(
623 "The cumulative demand exceeds the " +
624 "supported numerical range.");
631 private double CalculateTotalQuantity()
635 foreach (
double quantity
in _quantities)
639 if (!
double.IsFinite(total))
641 throw new InvalidOperationException(
642 "The total demand exceeds the supported " +
650 private (
int Period,
double Quantity)
653 if (_quantities.Length == 0)
659 double peakQuantity = _quantities[0];
662 index < _quantities.Length;
665 if (_quantities[index] >
671 peakPeriod = index + 1;
681 private void ValidateProfilePeriod(
int period)
686 throw new ArgumentOutOfRangeException(
689 $
"The period must be between 1 and " +
690 $
"{PlanningHorizon}.");
Represents an aggregated demand time series.
double PeakQuantity
Gets the highest period demand.
double GetCumulativeQuantity(int startPeriod, int endPeriod)
Calculates the cumulative demand over an inclusive period interval.
double TotalQuantity
Gets the cumulative demand over the complete profile.
double AverageQuantity
Gets the average demand per planning period.
DemandProfile(IEnumerable< double > quantities)
Initializes a demand profile.
int PeakPeriod
Gets the first period having the highest demand.
int PlanningHorizon
Gets the number of periods in the profile.
IReadOnlyList< double > Quantities
Gets a copy of the quantities.
double GetTotalDemandForDistributionCenter(int distributionCenterId, int period)
Calculates the total demand expressed by a distribution center during a planning period.
IReadOnlyList< Demand > GetDemandsForItem(int itemId)
Gets every demand defined for an item.
double GetCumulativeDemandForItem(int itemId)
Calculates the cumulative demand for an item over the complete planning horizon.
Demand GetRequiredDemand(int itemId, int distributionCenterId)
Gets the demand relationship defined for an item and a distribution center.
DemandProfile GetDemandProfileForItem(int itemId)
Calculates the aggregate demand profile of an item.
SupplyChain SupplyChain
Gets the analyzed supply chain.
DemandProfile GetDemandProfileForDistributionCenter(int distributionCenterId)
Calculates the aggregate demand profile of a distribution center.
double GetDemandQuantity(int itemId, int distributionCenterId, int period)
Gets the demand of an item at a distribution center during a planning period.
double GetGlobalDemand(int period)
Calculates the total demand of the complete supply chain during a planning period.
Demand? FindDemand(int itemId, int distributionCenterId)
Finds the demand relationship defined for an item and a distribution center.
void Rebuild()
Rebuilds the entity index and the demand indexes.
DemandAnalyzer(SupplyChain supplyChain)
Initializes a demand analyzer and creates an entity index.
DemandProfile GetGlobalDemandProfile()
Calculates the aggregate demand profile of the complete supply chain.
double GetTotalDemandForItem(int itemId, int period)
Calculates the total demand for an item during a planning period.
DemandAnalyzer(SupplyChainIndex index)
Initializes a demand analyzer using an existing index.
double GetCumulativeDemandForDistributionCenter(int distributionCenterId)
Calculates the cumulative demand expressed by a distribution center over the complete horizon.
DemandProfile GetDemandProfile(int itemId, int distributionCenterId)
Calculates the demand profile of one item at one distribution center.
SupplyChainIndex Index
Gets the entity index used by the analyzer.
IReadOnlyList< Demand > GetDemandsForDistributionCenter(int distributionCenterId)
Gets every demand expressed by a distribution center.
double GetCumulativeDemandForItem(int itemId, int startPeriod, int endPeriod)
Calculates the cumulative demand for an item over an inclusive period interval.
double GetCumulativeGlobalDemand(int startPeriod, int endPeriod)
Calculates the cumulative global demand over an inclusive period interval.
Provides fast access to the entities contained in a supply chain.
SupplyChain SupplyChain
Gets the indexed supply chain.
Represents the demand for an item issued by a distribution center.
int ItemId
Gets or sets the identifier of the requested item.
int PlanningHorizon
Gets the number of periods represented by the demand.
int DistributionCenterId
Gets or sets the identifier of the distribution center issuing the demand.
double GetQuantity(int period)
Gets the demand quantity for a planning period.
Represents the complete supply-chain data model.
int PlanningHorizon
Gets or sets the number of periods in the global planning horizon.