LotSizingDataModel.Instance 2.0.1
Lot-sizing instance representation, descriptors and problem characterization.
Loading...
Searching...
No Matches
ProductStructureAnalysis.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
5
7
8/// <summary>
9/// Represents the detailed result of an automatic analysis
10/// of a product bill-of-materials graph.
11/// </summary>
12/// <remarks>
13/// The analyzed graph uses directed arcs from component items
14/// to the immediate parent items that consume them.
15///
16/// This class is an immutable analysis snapshot. Persistent
17/// instance metadata is stored separately in
18/// ProductStructureDescriptor.
19/// </remarks>
20[Serializable]
21public sealed class ProductStructureAnalysis
22{
23 private readonly int[] _rootItemIds;
24 private readonly int[] _leafItemIds;
25 private readonly int[] _isolatedItemIds;
26 private readonly int[] _sharedComponentItemIds;
27 private readonly int[] _cyclicItemIds;
28 private readonly string[] _errors;
29 private readonly string[] _warnings;
30
31 /// <summary>
32 /// Initializes a product-structure analysis result.
33 /// </summary>
34 /// <param name="detectedType">
35 /// Product-structure type detected by the analyzer.
36 /// </param>
37 /// <param name="itemCount">
38 /// Number of items considered by the analysis.
39 /// </param>
40 /// <param name="relationshipCount">
41 /// Number of distinct component-to-parent relationships.
42 /// </param>
43 /// <param name="connectedComponentCount">
44 /// Number of connected components in the underlying
45 /// undirected product-structure graph.
46 /// </param>
47 /// <param name="maximumDepth">
48 /// Maximum number of relationships on a directed path
49 /// from a leaf item to a root item.
50 /// </param>
51 /// <param name="maximumImmediateComponentCount">
52 /// Largest number of immediate components consumed
53 /// by a single parent item.
54 /// </param>
55 /// <param name="maximumImmediateParentCount">
56 /// Largest number of immediate parent items consuming
57 /// a single component.
58 /// </param>
59 /// <param name="rootItemIds">
60 /// Identifiers of root items.
61 /// </param>
62 /// <param name="leafItemIds">
63 /// Identifiers of leaf items.
64 /// </param>
65 /// <param name="isolatedItemIds">
66 /// Identifiers of items having no bill-of-materials
67 /// relationship.
68 /// </param>
69 /// <param name="sharedComponentItemIds">
70 /// Identifiers of components consumed by several
71 /// immediate parent items.
72 /// </param>
73 /// <param name="cyclicItemIds">
74 /// Identifiers of items belonging to at least one cycle.
75 /// </param>
76 /// <param name="errors">
77 /// Errors detected during the analysis.
78 /// </param>
79 /// <param name="warnings">
80 /// Non-fatal warnings detected during the analysis.
81 /// </param>
83 ProductStructureType detectedType,
84 int itemCount,
85 int relationshipCount,
86 int connectedComponentCount,
87 int maximumDepth,
88 int maximumImmediateComponentCount,
89 int maximumImmediateParentCount,
90 IEnumerable<int> rootItemIds,
91 IEnumerable<int> leafItemIds,
92 IEnumerable<int> isolatedItemIds,
93 IEnumerable<int> sharedComponentItemIds,
94 IEnumerable<int> cyclicItemIds,
95 IEnumerable<string> errors,
96 IEnumerable<string> warnings)
97 {
98 ValidateNonNegativeCount(
99 itemCount,
100 nameof(itemCount));
101
102 ValidateNonNegativeCount(
103 relationshipCount,
104 nameof(relationshipCount));
105
106 ValidateNonNegativeCount(
107 connectedComponentCount,
108 nameof(connectedComponentCount));
109
110 ValidateNonNegativeCount(
111 maximumDepth,
112 nameof(maximumDepth));
113
114 ValidateNonNegativeCount(
115 maximumImmediateComponentCount,
116 nameof(maximumImmediateComponentCount));
117
118 ValidateNonNegativeCount(
119 maximumImmediateParentCount,
120 nameof(maximumImmediateParentCount));
121
122 DetectedType = detectedType;
123 ItemCount = itemCount;
124 RelationshipCount = relationshipCount;
125
127 connectedComponentCount;
128
129 MaximumDepth = maximumDepth;
130
132 maximumImmediateComponentCount;
133
135 maximumImmediateParentCount;
136
137 _rootItemIds =
138 NormalizeItemIds(
139 rootItemIds,
140 nameof(rootItemIds));
141
142 _leafItemIds =
143 NormalizeItemIds(
144 leafItemIds,
145 nameof(leafItemIds));
146
147 _isolatedItemIds =
148 NormalizeItemIds(
149 isolatedItemIds,
150 nameof(isolatedItemIds));
151
152 _sharedComponentItemIds =
153 NormalizeItemIds(
154 sharedComponentItemIds,
155 nameof(sharedComponentItemIds));
156
157 _cyclicItemIds =
158 NormalizeItemIds(
159 cyclicItemIds,
160 nameof(cyclicItemIds));
161
162 _errors =
163 NormalizeMessages(
164 errors,
165 nameof(errors));
166
167 _warnings =
168 NormalizeMessages(
169 warnings,
170 nameof(warnings));
171
172 if (_cyclicItemIds.Length > 0 &&
174 {
175 throw new ArgumentException(
176 "A cyclic product structure cannot have " +
177 "a valid detected structure type.",
178 nameof(detectedType));
179 }
180 }
181
182 /// <summary>
183 /// Gets the product-structure type detected
184 /// by the analyzer.
185 /// </summary>
186 /// <remarks>
187 /// The value is <see cref="ProductStructureType.Unknown"/>
188 /// when the graph is invalid or cannot be classified.
189 /// </remarks>
191
192 /// <summary>
193 /// Gets the number of items considered by the analysis.
194 /// </summary>
195 public int ItemCount { get; }
196
197 /// <summary>
198 /// Gets the number of distinct component-to-parent
199 /// relationships.
200 /// </summary>
201 public int RelationshipCount { get; }
202
203 /// <summary>
204 /// Gets the number of connected components in the
205 /// underlying undirected product-structure graph.
206 /// </summary>
207 public int ConnectedComponentCount { get; }
208
209 /// <summary>
210 /// Gets the maximum number of relationships on a directed
211 /// path from a leaf item to a root item.
212 /// </summary>
213 /// <remarks>
214 /// The value is zero when the instance contains no
215 /// bill-of-materials relationship or when a valid depth
216 /// cannot be calculated.
217 /// </remarks>
218 public int MaximumDepth { get; }
219
220 /// <summary>
221 /// Gets the largest number of immediate components
222 /// consumed by a single parent item.
223 /// </summary>
225
226 /// <summary>
227 /// Gets the largest number of immediate parent items
228 /// consuming a single component.
229 /// </summary>
230 public int MaximumImmediateParentCount { get; }
231
232 /// <summary>
233 /// Gets the identifiers of root items.
234 /// </summary>
235 /// <remarks>
236 /// A root item is not consumed as a component by another
237 /// item. An isolated item is therefore also a root.
238 /// </remarks>
239 public IReadOnlyList<int> RootItemIds =>
240 _rootItemIds;
241
242 /// <summary>
243 /// Gets the identifiers of leaf items.
244 /// </summary>
245 /// <remarks>
246 /// A leaf item does not consume another component.
247 /// An isolated item is therefore also a leaf.
248 /// </remarks>
249 public IReadOnlyList<int> LeafItemIds =>
250 _leafItemIds;
251
252 /// <summary>
253 /// Gets the identifiers of items having no
254 /// bill-of-materials relationship.
255 /// </summary>
256 public IReadOnlyList<int> IsolatedItemIds =>
257 _isolatedItemIds;
258
259 /// <summary>
260 /// Gets the identifiers of components consumed by more
261 /// than one immediate parent item.
262 /// </summary>
263 public IReadOnlyList<int> SharedComponentItemIds =>
264 _sharedComponentItemIds;
265
266 /// <summary>
267 /// Gets the identifiers of items belonging to at least
268 /// one directed cycle.
269 /// </summary>
270 public IReadOnlyList<int> CyclicItemIds =>
271 _cyclicItemIds;
272
273 /// <summary>
274 /// Gets the errors detected during the analysis.
275 /// </summary>
276 public IReadOnlyList<string> Errors =>
277 _errors;
278
279 /// <summary>
280 /// Gets the non-fatal warnings detected during
281 /// the analysis.
282 /// </summary>
283 public IReadOnlyList<string> Warnings =>
284 _warnings;
285
286 /// <summary>
287 /// Gets a value indicating whether the product-structure
288 /// graph contains at least one relationship.
289 /// </summary>
290 public bool HasRelationships =>
292
293 /// <summary>
294 /// Gets a value indicating whether at least one item
295 /// has no bill-of-materials relationship.
296 /// </summary>
297 public bool HasIsolatedItems =>
298 IsolatedItemIds.Count > 0;
299
300 /// <summary>
301 /// Gets a value indicating whether at least one component
302 /// is consumed by several immediate parent items.
303 /// </summary>
304 public bool HasSharedComponents =>
305 SharedComponentItemIds.Count > 0;
306
307 /// <summary>
308 /// Gets a value indicating whether the product-structure
309 /// graph contains at least one directed cycle.
310 /// </summary>
311 public bool HasCycle =>
312 CyclicItemIds.Count > 0;
313
314 /// <summary>
315 /// Gets a value indicating whether the product structure
316 /// contains more than one bill-of-materials level.
317 /// </summary>
318 public bool IsMultiLevel =>
319 MaximumDepth > 1;
320
321 /// <summary>
322 /// Gets a value indicating whether at least one parent
323 /// item consumes several immediate components.
324 /// </summary>
325 public bool HasAssemblyNodes =>
327
328 /// <summary>
329 /// Gets a value indicating whether at least one component
330 /// is consumed by several immediate parent items.
331 /// </summary>
334
335 /// <summary>
336 /// Gets a value indicating whether at least one error
337 /// was detected.
338 /// </summary>
339 public bool HasErrors =>
340 Errors.Count > 0;
341
342 /// <summary>
343 /// Gets a value indicating whether at least one warning
344 /// was detected.
345 /// </summary>
346 public bool HasWarnings =>
347 Warnings.Count > 0;
348
349 /// <summary>
350 /// Gets a value indicating whether the analyzed graph
351 /// is structurally valid.
352 /// </summary>
353 /// <remarks>
354 /// A valid graph contains no detected error and no
355 /// directed cycle.
356 /// </remarks>
357 public bool IsValid =>
358 !HasErrors &&
359 !HasCycle;
360
361 /// <inheritdoc/>
362 public override string ToString()
363 {
364 string validity =
365 IsValid
366 ? "valid"
367 : "invalid";
368
369 return
370 $"{DetectedType} — {validity}; " +
371 $"{ItemCount} items; " +
372 $"{RelationshipCount} relationships; " +
373 $"depth {MaximumDepth}";
374 }
375
376 private static int[] NormalizeItemIds(
377 IEnumerable<int> itemIds,
378 string parameterName)
379 {
380 ArgumentNullException.ThrowIfNull(itemIds);
381
382 int[] normalizedItemIds =
383 itemIds
384 .Distinct()
385 .OrderBy(
386 itemId =>
387 itemId)
388 .ToArray();
389
390 if (normalizedItemIds.Any(
391 itemId =>
392 itemId <= 0))
393 {
394 throw new ArgumentException(
395 "Every item identifier must be " +
396 "strictly positive.",
397 parameterName);
398 }
399
400 return normalizedItemIds;
401 }
402
403 private static string[] NormalizeMessages(
404 IEnumerable<string> messages,
405 string parameterName)
406 {
407 ArgumentNullException.ThrowIfNull(messages);
408
409 return messages
410 .Where(
411 message =>
412 !string.IsNullOrWhiteSpace(
413 message))
414 .Select(
415 message =>
416 message.Trim())
417 .Distinct(
418 StringComparer.Ordinal)
419 .ToArray();
420 }
421
422 private static void ValidateNonNegativeCount(
423 int value,
424 string parameterName)
425 {
426 if (value < 0)
427 {
428 throw new ArgumentOutOfRangeException(
429 parameterName,
430 value,
431 "The value cannot be negative.");
432 }
433 }
434}
int MaximumImmediateParentCount
Gets the largest number of immediate parent items consuming a single component.
bool HasIsolatedItems
Gets a value indicating whether at least one item has no bill-of-materials relationship.
ProductStructureAnalysis(ProductStructureType detectedType, int itemCount, int relationshipCount, int connectedComponentCount, int maximumDepth, int maximumImmediateComponentCount, int maximumImmediateParentCount, IEnumerable< int > rootItemIds, IEnumerable< int > leafItemIds, IEnumerable< int > isolatedItemIds, IEnumerable< int > sharedComponentItemIds, IEnumerable< int > cyclicItemIds, IEnumerable< string > errors, IEnumerable< string > warnings)
Initializes a product-structure analysis result.
bool HasWarnings
Gets a value indicating whether at least one warning was detected.
int ItemCount
Gets the number of items considered by the analysis.
bool IsMultiLevel
Gets a value indicating whether the product structure contains more than one bill-of-materials level.
bool HasErrors
Gets a value indicating whether at least one error was detected.
IReadOnlyList< int > LeafItemIds
Gets the identifiers of leaf items.
int RelationshipCount
Gets the number of distinct component-to-parent relationships.
int MaximumImmediateComponentCount
Gets the largest number of immediate components consumed by a single parent item.
int ConnectedComponentCount
Gets the number of connected components in the underlying undirected product-structure graph.
bool HasCycle
Gets a value indicating whether the product-structure graph contains at least one directed cycle.
int MaximumDepth
Gets the maximum number of relationships on a directed path from a leaf item to a root item.
bool HasRelationships
Gets a value indicating whether the product-structure graph contains at least one relationship.
IReadOnlyList< int > SharedComponentItemIds
Gets the identifiers of components consumed by more than one immediate parent item.
ProductStructureType DetectedType
Gets the product-structure type detected by the analyzer.
bool HasAssemblyNodes
Gets a value indicating whether at least one parent item consumes several immediate components.
IReadOnlyList< string > Errors
Gets the errors detected during the analysis.
bool HasDivergentComponents
Gets a value indicating whether at least one component is consumed by several immediate parent items.
IReadOnlyList< int > IsolatedItemIds
Gets the identifiers of items having no bill-of-materials relationship.
bool IsValid
Gets a value indicating whether the analyzed graph is structurally valid.
IReadOnlyList< int > CyclicItemIds
Gets the identifiers of items belonging to at least one directed cycle.
bool HasSharedComponents
Gets a value indicating whether at least one component is consumed by several immediate parent items.
IReadOnlyList< int > RootItemIds
Gets the identifiers of root items.
IReadOnlyList< string > Warnings
Gets the non-fatal warnings detected during the analysis.
ProductStructureType
Identifies the structural category of a product bill-of-materials graph.