2using System.Collections.Generic;
17 private readonly Dictionary<int, List<ComponentRequirement>>
18 _requirementsByParent =
new();
20 private readonly Dictionary<int, List<ComponentRequirement>>
21 _requirementsByComponent =
new();
33 throw new ArgumentNullException(
34 nameof(supplyChain))))
48 throw new ArgumentNullException(nameof(index));
75 _requirementsByParent.Clear();
76 _requirementsByComponent.Clear();
84 Index.GetRequiredItem(
87 Index.GetRequiredItem(
91 _requirementsByParent,
96 _requirementsByComponent,
102 #region Direct relationships
107 public IReadOnlyList<ComponentRequirement>
110 Index.GetRequiredItem(parentItemId);
112 if (!_requirementsByParent.TryGetValue(
114 out List<ComponentRequirement>? requirements))
119 return requirements.ToArray();
131 Index.GetRequiredItem(
140 public IReadOnlyList<ComponentRequirement>
143 Index.GetRequiredItem(componentItemId);
145 if (!_requirementsByComponent.TryGetValue(
147 out List<ComponentRequirement>? requirements))
152 return requirements.ToArray();
164 Index.GetRequiredItem(
174 Index.GetRequiredItem(itemId);
176 return !_requirementsByParent.ContainsKey(itemId);
185 Index.GetRequiredItem(itemId);
187 return !_requirementsByComponent.ContainsKey(itemId);
197 .OrderBy(item => item.Id)
208 .OrderBy(item => item.Id)
214 #region Topological order and cycle detection
231 out IReadOnlyList<Item> items)
233 Dictionary<int, int> incomingEdgeCounts =
245 var availableItemIds =
248 .Where(pair => pair.Value == 0)
249 .Select(pair => pair.Key));
255 while (availableItemIds.Count > 0)
258 availableItemIds.First();
260 availableItemIds.Remove(currentItemId);
263 Index.GetRequiredItem(currentItemId));
265 if (!_requirementsByParent.TryGetValue(
267 out List<ComponentRequirement>? requirements))
275 int componentItemId =
278 incomingEdgeCounts[componentItemId]--;
280 if (incomingEdgeCounts[componentItemId] == 0)
282 availableItemIds.Add(componentItemId);
289 items = Array.Empty<
Item>();
293 items = orderedItems;
307 out IReadOnlyList<Item> items))
312 throw new InvalidOperationException(
313 "The bill of materials contains a circular dependency.");
331 #region Level calculation
341 IReadOnlyList<Item> topologicalOrder =
344 Dictionary<int, int> levels =
349 foreach (
Item parentItem
in topologicalOrder)
351 if (!_requirementsByParent.TryGetValue(
353 out List<ComponentRequirement>? requirements))
359 checked(levels[parentItem.
Id] + 1);
367 if (componentLevel > currentLevel)
384 IReadOnlyDictionary<int, int> levels =
389 item.BillOfMaterialsLevel =
400 IReadOnlyDictionary<int, int> calculatedLevels =
406 calculatedLevels[item.
Id]);
411 #region Cumulative requirements
427 public IReadOnlyDictionary<int, long>
431 Index.GetRequiredItem(parentItemId);
433 IReadOnlyList<Item> topologicalOrder =
437 new Dictionary<int, long>
442 foreach (
Item currentItem
in topologicalOrder)
444 if (!quantities.TryGetValue(
446 out
long currentQuantity))
451 if (!_requirementsByParent.TryGetValue(
453 out List<ComponentRequirement>? requirements))
465 contribution = checked(
469 catch (OverflowException exception)
471 throw new InvalidOperationException(
472 "The cumulative bill-of-material coefficient " +
473 "exceeds the supported integer range.",
477 quantities.TryGetValue(
479 out
long existingQuantity);
488 catch (OverflowException exception)
490 throw new InvalidOperationException(
491 "The cumulative bill-of-material coefficient " +
492 "exceeds the supported integer range.",
498 quantities.Remove(parentItemId);
513 public IReadOnlyDictionary<int, double>
516 double parentQuantity)
518 if (!
double.IsFinite(parentQuantity) ||
519 parentQuantity < 0.0)
521 throw new ArgumentOutOfRangeException(
522 nameof(parentQuantity),
524 "The parent-item quantity must be finite " +
525 "and non-negative.");
528 IReadOnlyDictionary<int, long> coefficients =
533 new Dictionary<int, double>();
535 foreach (KeyValuePair<int, long> pair
539 pair.Value * parentQuantity;
541 if (!
double.IsFinite(quantity))
543 throw new InvalidOperationException(
544 "A gross component requirement exceeds " +
545 "the supported numerical range.");
548 requirements[pair.Key] = quantity;
556 private static void AddRequirement(
557 IDictionary<
int, List<ComponentRequirement>> dictionary,
561 if (!dictionary.TryGetValue(
563 out List<ComponentRequirement>? requirements))
566 new List<ComponentRequirement>();
573 requirements.Add(requirement);
BillOfMaterialsAnalyzer(SupplyChain supplyChain)
Initializes an analyzer and creates a new entity index.
IReadOnlyList< Item > GetReverseTopologicalOrder()
Gets an order in which components appear before their parent items.
IReadOnlyDictionary< int, double > CalculateGrossComponentRequirements(int parentItemId, double parentQuantity)
Calculates the gross component requirements for a given production quantity of a parent item.
void ApplyCalculatedLevels()
Recalculates and updates the BillOfMaterialsLevel property of every item.
bool IsRootItem(int itemId)
Determines whether an item is not used as a component of another item.
IReadOnlyList< Item > GetLeafItems()
Gets all leaf items of the bill of materials.
bool HasCycle
Determines whether the bill of materials contains at least one circular dependency.
IReadOnlyDictionary< int, int > CalculateLevels()
Calculates the bill-of-material level of every item.
IReadOnlyList< Item > GetDirectParents(int componentItemId)
Gets the direct parent items using a component.
bool IsLeafItem(int itemId)
Determines whether an item has no component.
bool HasConsistentStoredLevels()
Determines whether the levels currently stored in the items match the levels calculated from the bill...
IReadOnlyList< Item > GetTopologicalOrder()
Gets a topological order in which parent items appear before their components.
bool TryGetTopologicalOrder(out IReadOnlyList< Item > items)
Attempts to calculate a topological order in which every parent item appears before its components.
IReadOnlyDictionary< int, long > GetCumulativeRequirementCoefficients(int parentItemId)
Calculates the cumulative component coefficients required to manufacture one unit of a parent item.
IReadOnlyList< Item > GetDirectComponents(int parentItemId)
Gets the direct components of a parent item.
void Rebuild()
Rebuilds the entity index and the bill-of-material indexes.
SupplyChain SupplyChain
Gets the analyzed supply chain.
IReadOnlyList< ComponentRequirement > GetDirectRequirements(int parentItemId)
Gets the direct component requirements of a parent item.
IReadOnlyList< Item > GetRootItems()
Gets all root items of the bill of materials.
IReadOnlyList< ComponentRequirement > GetDirectParentRequirements(int componentItemId)
Gets the requirements in which an item is used as a direct component.
BillOfMaterialsAnalyzer(SupplyChainIndex index)
Initializes an analyzer using an existing entity index.
SupplyChainIndex Index
Gets the entity index used by the analyzer.
int Id
Gets or sets the numerical identifier of the entity.
Provides fast access to the entities contained in a supply chain.
SupplyChain SupplyChain
Gets the indexed supply chain.
Represents a bill-of-materials relationship between two items.
int ParentItemId
Gets or sets the identifier of the item being manufactured.
int ComponentItemId
Gets or sets the identifier of the required component.
int Quantity
Gets or sets the quantity of the component required to manufacture one unit of the parent item.
Represents a finished or semi-finished item handled by the supply chain.
int BillOfMaterialsLevel
Gets or sets the item's bill-of-materials level.
List< Item > Items
Gets the items present in the supply chain.