17 ArgumentNullException.ThrowIfNull(supplyChain);
20 new Dictionary<string, NodeSeed>(StringComparer.Ordinal);
22 foreach (
Supplier supplier
in supplyChain.Suppliers)
26 SupplierKey(supplier.Id),
31 foreach (Plant plant
in supplyChain.Plants)
35 PlantWarehouseKey(plant.Id),
41 in supplyChain.StandaloneWarehouses)
45 StandaloneWarehouseKey(warehouse.Id),
51 in supplyChain.DistributionCenters)
55 DistributionCenterKey(center.Id),
61 new Dictionary<ArcKey, int>();
63 foreach (var delivery
in supplyChain.SupplierDeliveries)
65 string from = SupplierKey(delivery.SupplierId);
66 string to = WarehouseKey(delivery.Warehouse);
74 EnsureWarehouseNode(nodeSeeds, delivery.Warehouse);
83 foreach (TransportResource resource
84 in supplyChain.TransportResources)
86 foreach (AssignedTransportLane lane
in supplyChain.GetTransportLanes(resource.Id))
88 string from = WarehouseKey(lane.Origin);
89 string to = WarehouseKey(lane.Destination);
91 EnsureWarehouseNode(nodeSeeds, lane.Origin);
92 EnsureWarehouseNode(nodeSeeds, lane.Destination);
102 foreach (var sourcing
103 in supplyChain.DistributionCenterSourcings)
105 string from = WarehouseKey(sourcing.Warehouse);
107 DistributionCenterKey(
108 sourcing.DistributionCenterId);
110 EnsureWarehouseNode(nodeSeeds, sourcing.Warehouse);
112 EnsureReferencedNode(
116 sourcing.DistributionCenterId);
126 nodeSeeds.Keys.ToDictionary(
128 _ =>
new HashSet<string>(StringComparer.Ordinal),
129 StringComparer.Ordinal);
131 var reverseAdjacency =
132 nodeSeeds.Keys.ToDictionary(
134 _ =>
new HashSet<string>(StringComparer.Ordinal),
135 StringComparer.Ordinal);
137 foreach (ArcKey arc
in arcMultiplicity.Keys)
139 adjacency[arc.From].Add(arc.To);
140 reverseAdjacency[arc.To].Add(arc.From);
159 : ComputeEchelonCount(
166 .OrderBy(seed => seed.Key, StringComparer.Ordinal)
173 ReferenceId = seed.ReferenceId,
174 IsDeclared = seed.IsDeclared,
176 reverseAdjacency[seed.Key].Count,
178 adjacency[seed.Key].Count
184 .OrderBy(pair => pair.Key.From, StringComparer.Ordinal)
185 .ThenBy(pair => pair.Key.To, StringComparer.Ordinal)
186 .ThenBy(pair => pair.Key.Kind)
191 FromKey = pair.Key.From,
193 Kind = pair.Key.Kind,
194 RelationshipMultiplicity = pair.Value
204 HasCycles = hasCycles,
205 EchelonCount = echelonCount
208 bool hasMultiSourcing =
209 HasSupplierMultiSourcing(supplyChain) ||
210 HasDistributionCenterMultiSourcing(supplyChain);
212 bool hasTransshipment =
213 supplyChain.TransportResources.Any(
214 resource => supplyChain.GetTransportLanes(resource.Id).Any());
216 bool hasDistributionNetwork =
217 supplyChain.DistributionCenterSourcings.Count > 0;
219 bool hasExternalDemandAtDistributionCenters =
220 supplyChain.Demands.Count > 0;
224 ForwardNetwork = forward,
225 ReverseNetwork =
null,
227 HasMultiSourcing = hasMultiSourcing,
228 HasTransshipment = hasTransshipment,
229 HasDistributionNetwork =
230 hasDistributionNetwork,
231 HasExternalDemandAtDistributionCenters =
232 hasExternalDemandAtDistributionCenters
236 private static bool HasSupplierMultiSourcing(
237 SupplyChain supplyChain)
239 return supplyChain.SupplierDeliveries
242 $
"{delivery.ItemId}|" +
243 $
"{delivery.Warehouse.Kind}|" +
244 $
"{delivery.Warehouse.ReferenceId}",
245 StringComparer.Ordinal)
249 .Select(delivery => delivery.SupplierId)
255 private static bool HasDistributionCenterMultiSourcing(
256 SupplyChain supplyChain)
258 return supplyChain.DistributionCenterSourcings
261 $
"{sourcing.ItemId}|" +
262 $
"{sourcing.DistributionCenterId}",
263 StringComparer.Ordinal)
269 $
"{sourcing.Warehouse.Kind}|" +
270 $
"{sourcing.Warehouse.ReferenceId}")
271 .Distinct(StringComparer.Ordinal)
277 IEnumerable<string> keys,
278 IReadOnlyDictionary<
string, HashSet<string>> adjacency,
279 IReadOnlyDictionary<
string, HashSet<string>> reverseAdjacency,
282 string[] nodes = keys.ToArray();
284 if (nodes.Length == 0)
289 int physicalEdgeCount =
290 adjacency.Values.Sum(targets => targets.Count);
292 if (physicalEdgeCount == 0)
303 nodes.Max(key => reverseAdjacency[key].Count);
306 nodes.Max(key => adjacency[key].Count);
308 if (maxIn <= 1 && maxOut <= 1)
313 if (maxIn > 1 && maxOut <= 1)
318 if (maxOut > 1 && maxIn <= 1)
323 if (IsUndirectedForest(nodes, adjacency))
331 private static bool HasDirectedCycle(
332 IEnumerable<string> keys,
333 IReadOnlyDictionary<
string, HashSet<string>> adjacency,
334 IReadOnlyDictionary<
string, HashSet<string>> reverseAdjacency)
339 key => reverseAdjacency[key].Count,
340 StringComparer.Ordinal);
345 .Where(pair => pair.Value == 0)
346 .Select(pair => pair.Key));
350 while (queue.Count > 0)
352 string current = queue.Dequeue();
355 foreach (
string target
in adjacency[current])
359 if (indegree[target] == 0)
361 queue.Enqueue(target);
366 return visited != indegree.Count;
369 private static int ComputeEchelonCount(
370 IEnumerable<string> keys,
371 IReadOnlyDictionary<
string, HashSet<string>> adjacency,
372 IReadOnlyDictionary<
string, HashSet<string>> reverseAdjacency)
374 string[] nodes = keys.ToArray();
376 if (nodes.Length == 0)
384 key => reverseAdjacency[key].Count,
385 StringComparer.Ordinal);
391 StringComparer.Ordinal);
396 .Where(pair => pair.Value == 0)
397 .Select(pair => pair.Key));
399 while (queue.Count > 0)
401 string current = queue.Dequeue();
403 foreach (
string target
in adjacency[current])
412 if (indegree[target] == 0)
414 queue.Enqueue(target);
419 return depth.Values.Max();
422 private static bool IsUndirectedForest(
423 IReadOnlyCollection<string> nodes,
424 IReadOnlyDictionary<
string, HashSet<string>> adjacency)
429 _ =>
new HashSet<string>(StringComparer.Ordinal),
430 StringComparer.Ordinal);
434 foreach (
string from
in nodes)
436 foreach (
string to
in adjacency[from])
438 if (undirected[from].Add(to))
440 undirected[to].Add(from);
448 new HashSet<string>(StringComparer.Ordinal);
450 foreach (
string start
in nodes)
452 if (!visited.Add(start))
459 var stack =
new Stack<string>();
462 while (stack.Count > 0)
464 string current = stack.Pop();
466 foreach (
string neighbor
in undirected[current])
468 if (visited.Add(neighbor))
470 stack.Push(neighbor);
476 return edgeCount == nodes.Count - components;
479 private static void AddArc(
480 IDictionary<ArcKey, int> arcs,
485 var key =
new ArcKey(from, to, kind);
487 if (arcs.TryGetValue(key, out
int count))
489 arcs[key] = count + 1;
497 private static void AddDeclaredNode(
498 IDictionary<string, NodeSeed> nodes,
511 private static void EnsureReferencedNode(
512 IDictionary<string, NodeSeed> nodes,
517 if (!nodes.ContainsKey(key))
529 private static void EnsureWarehouseNode(
530 IDictionary<string, NodeSeed> nodes,
531 WarehouseReference warehouse)
535 WarehouseReferenceKind.PlantWarehouse
536 ? SupplyNetworkNodeKind.PlantWarehouse
539 EnsureReferencedNode(
541 WarehouseKey(warehouse),
543 warehouse.ReferenceId);
546 private static string SupplierKey(
int id) =>
549 private static string DistributionCenterKey(
int id) =>
550 $
"distributionCenter:{id}";
552 private static string PlantWarehouseKey(
int plantId) =>
553 $
"plantWarehouse:{plantId}";
555 private static string StandaloneWarehouseKey(
int warehouseId) =>
556 $
"warehouse:{warehouseId}";
558 private static string WarehouseKey(
559 WarehouseReference warehouse)
561 return warehouse.Kind ==
562 WarehouseReferenceKind.PlantWarehouse
563 ? PlantWarehouseKey(warehouse.ReferenceId)
564 : StandaloneWarehouseKey(warehouse.ReferenceId);
567 private sealed record NodeSeed(
573 private readonly record
struct ArcKey(