LotSizingDataModel.Instance 2.0.1
Lot-sizing instance representation, descriptors and problem characterization.
Loading...
Searching...
No Matches
LotSizingInstanceValidator.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
10
12
13/// <summary>
14/// Validates the structural and referential consistency of a
15/// complete lot-sizing problem instance.
16/// </summary>
17/// <remarks>
18/// The validator checks instance-level information such as:
19/// <list type="bullet">
20/// <item>
21/// <description>instance identity and metadata;</description>
22/// </item>
23/// <item>
24/// <description>product-structure analysis consistency;</description>
25/// </item>
26/// <item>
27/// <description>problem-classification consistency;</description>
28/// </item>
29/// <item>
30/// <description>known-result identifiers and references;</description>
31/// </item>
32/// <item>
33/// <description>best-known-result eligibility;</description>
34/// </item>
35/// <item>
36/// <description>solution-method recommendation reports;</description>
37/// </item>
38/// <item>
39/// <description>supply-chain fingerprints.</description>
40/// </item>
41/// </list>
42///
43/// An instance does not need to contain a known result or a
44/// detailed solution in order to be valid.
45///
46/// Validation of the internal mathematical consistency of the
47/// supply-chain model and validation of detailed solution
48/// decisions remain the responsibility of their dedicated
49/// validators.
50/// </remarks>
51public static class LotSizingInstanceValidator
52{
53 /// <summary>
54 /// Validates a lot-sizing problem instance.
55 /// </summary>
56 /// <param name="instance">
57 /// Instance to validate.
58 /// </param>
59 /// <param name="validateCurrentFingerprint">
60 /// Value indicating whether the current supply-chain
61 /// fingerprint must be calculated and compared with
62 /// recorded fingerprints.
63 /// </param>
64 /// <returns>
65 /// Ordered collection of validation errors. An empty
66 /// collection indicates that the instance is structurally
67 /// valid.
68 /// </returns>
69 /// <exception cref="ArgumentNullException">
70 /// Thrown when <paramref name="instance"/> is
71 /// <see langword="null"/>.
72 /// </exception>
73 public static IReadOnlyList<string> Validate(
74 LotSizingInstance instance,
75 bool validateCurrentFingerprint = true)
76 {
77 ArgumentNullException.ThrowIfNull(instance);
78
79 var errors =
80 new List<string>();
81
82 ValidateInstanceMetadata(
83 instance,
84 errors);
85
86 ValidateProductStructure(
87 instance.ProductStructure,
88 errors);
89
90 ValidateProblemClassification(
91 instance.ProblemClassification,
92 errors);
93
94 ValidateRecommendationReport(
95 instance,
96 errors);
97
98 Dictionary<string, KnownResult> knownResultsById =
99 ValidateKnownResults(
100 instance,
101 errors);
102
103 ValidateBestKnownResult(
104 instance,
105 knownResultsById,
106 errors);
107
108 ValidateSupersedingReferences(
109 instance.KnownResults,
110 knownResultsById,
111 errors);
112
113 ValidateSupersedingCycles(
114 knownResultsById,
115 errors);
116
117 if (validateCurrentFingerprint)
118 {
119 ValidateFingerprints(
120 instance,
121 errors);
122 }
123
124 return NormalizeMessages(
125 errors);
126 }
127
128 /// <summary>
129 /// Returns non-fatal consistency and quality warnings for
130 /// a lot-sizing problem instance.
131 /// </summary>
132 /// <param name="instance">
133 /// Instance to examine.
134 /// </param>
135 /// <param name="validateCurrentFingerprint">
136 /// Value indicating whether the current supply-chain
137 /// fingerprint must be calculated and compared with
138 /// fingerprints recorded by analyses, known results and
139 /// solution-method recommendations.
140 /// </param>
141 /// <returns>
142 /// Ordered collection of warning messages.
143 /// </returns>
144 /// <exception cref="ArgumentNullException">
145 /// Thrown when <paramref name="instance"/> is
146 /// <see langword="null"/>.
147 /// </exception>
148 public static IReadOnlyList<string> GetWarnings(
149 LotSizingInstance instance,
150 bool validateCurrentFingerprint = true)
151 {
152 ArgumentNullException.ThrowIfNull(instance);
153
154 var warnings =
155 new List<string>();
156
157 if (!instance.HasName)
158 {
159 warnings.Add(
160 "The instance does not have a human-readable " +
161 "name.");
162 }
163
164 if (!instance.HasCreationDate)
165 {
166 warnings.Add(
167 "The instance creation date has not been " +
168 "recorded.");
169 }
170
171 if (!instance.HasSourceInformation)
172 {
173 warnings.Add(
174 "No source information has been recorded for " +
175 "the instance.");
176 }
177
178 AddProductStructureWarnings(
179 instance.ProductStructure,
180 warnings);
181
182 AddClassificationWarnings(
183 instance.ProblemClassification,
184 warnings);
185
186 AddRecommendationWarnings(
187 instance,
188 warnings);
189
190 foreach (KnownResult result
191 in instance.KnownResults
192 .Where(result => result is not null))
193 {
194 AddKnownResultWarnings(
195 result,
196 warnings);
197 }
198
199 if (instance.HasEligibleBestKnownResult)
200 {
201 KnownResult? bestKnownResult =
202 instance.BestKnownResult;
203
204 if (bestKnownResult is not null &&
205 bestKnownResult.VerificationStatus ==
207 {
208 warnings.Add(
209 $"Best known result " +
210 $"'{bestKnownResult.ResultId}' has not " +
211 "been verified.");
212 }
213
214 if (bestKnownResult is not null &&
215 bestKnownResult.VerificationStatus ==
216 KnownResultVerificationStatus.SourceReported)
217 {
218 warnings.Add(
219 $"Best known result " +
220 $"'{bestKnownResult.ResultId}' is based " +
221 "only on information reported by its " +
222 "source.");
223 }
224 }
225
226 if (validateCurrentFingerprint)
227 {
228 AddFingerprintWarnings(
229 instance,
230 warnings);
231 }
232
233 return NormalizeMessages(
234 warnings);
235 }
236
237 /// <summary>
238 /// Determines whether a lot-sizing problem instance is
239 /// structurally valid.
240 /// </summary>
241 /// <param name="instance">
242 /// Instance to validate.
243 /// </param>
244 /// <param name="validateCurrentFingerprint">
245 /// Value indicating whether recorded fingerprints must
246 /// be compared with the current supply-chain state.
247 /// </param>
248 /// <returns>
249 /// <see langword="true"/> when no validation error is
250 /// found; otherwise, <see langword="false"/>.
251 /// </returns>
252 public static bool IsValid(
253 LotSizingInstance instance,
254 bool validateCurrentFingerprint = true)
255 {
256 return Validate(
257 instance,
258 validateCurrentFingerprint).Count == 0;
259 }
260
261 /// <summary>
262 /// Validates a lot-sizing instance and throws an
263 /// exception when at least one error is found.
264 /// </summary>
265 /// <param name="instance">
266 /// Instance to validate.
267 /// </param>
268 /// <param name="validateCurrentFingerprint">
269 /// Value indicating whether recorded fingerprints must
270 /// be compared with the current supply-chain state.
271 /// </param>
272 /// <exception cref="InvalidOperationException">
273 /// Thrown when the instance is invalid.
274 /// </exception>
275 public static void EnsureValid(
276 LotSizingInstance instance,
277 bool validateCurrentFingerprint = true)
278 {
279 IReadOnlyList<string> errors =
280 Validate(
281 instance,
282 validateCurrentFingerprint);
283
284 if (errors.Count == 0)
285 {
286 return;
287 }
288
289 throw new InvalidOperationException(
290 "The lot-sizing instance is invalid:" +
291 Environment.NewLine +
292 string.Join(
293 Environment.NewLine,
294 errors.Select(
295 error =>
296 "- " + error)));
297 }
298
299 private static void ValidateInstanceMetadata(
300 LotSizingInstance instance,
301 ICollection<string> errors)
302 {
303 if (!instance.HasFormatVersion)
304 {
305 errors.Add(
306 "The instance format version is missing.");
307 }
308
309 if (!instance.HasInstanceId)
310 {
311 errors.Add(
312 "The instance identifier is missing.");
313 }
314
315 if (instance.SupplyChain is null)
316 {
317 errors.Add(
318 "The supply-chain model is missing.");
319 }
320
321 if (instance.ProductStructure is null)
322 {
323 errors.Add(
324 "The product-structure descriptor is " +
325 "missing.");
326 }
327
328 if (instance.ProblemClassification is null)
329 {
330 errors.Add(
331 "The problem-classification object is " +
332 "missing.");
333 }
334
335 if (instance.CreatedAtUtc.HasValue &&
336 instance.ModifiedAtUtc.HasValue &&
337 instance.ModifiedAtUtc.Value <
338 instance.CreatedAtUtc.Value)
339 {
340 errors.Add(
341 "The instance modification date precedes its " +
342 "creation date.");
343 }
344 }
345
346 private static void ValidateProductStructure(
347 ProductStructureDescriptor descriptor,
348 ICollection<string> errors)
349 {
350 if (descriptor is null)
351 {
352 return;
353 }
354
355 ValidateItemIdentifierCollection(
356 descriptor.RootItemIds,
357 "product-structure root-item",
358 errors);
359
360 ValidateItemIdentifierCollection(
361 descriptor.LeafItemIds,
362 "product-structure leaf-item",
363 errors);
364
365 ValidateItemIdentifierCollection(
366 descriptor.SharedComponentItemIds,
367 "shared-component item",
368 errors);
369
370 if (descriptor.HasCycle &&
371 descriptor.CheckStatus !=
373 {
374 errors.Add(
375 "The product structure contains a cycle but " +
376 "is not marked as invalid.");
377 }
378
379 if (descriptor.HasCycle &&
380 descriptor.DetectedType !=
381 ProductStructureType.Unknown)
382 {
383 errors.Add(
384 "A cyclic product structure cannot have a " +
385 "valid detected product-structure type.");
386 }
387
388 switch (descriptor.CheckStatus)
389 {
390 case ProductStructureCheckStatus.NotAnalyzed:
391 ValidateNotAnalyzedProductStructure(
392 descriptor,
393 errors);
394 break;
395
396 case ProductStructureCheckStatus.DeclaredOnly:
397 ValidateDeclaredOnlyProductStructure(
398 descriptor,
399 errors);
400 break;
401
402 case ProductStructureCheckStatus.DetectedOnly:
403 ValidateDetectedOnlyProductStructure(
404 descriptor,
405 errors);
406 break;
407
409 .DeclaredAndConfirmed:
410 ValidateConfirmedProductStructure(
411 descriptor,
412 errors);
413 break;
414
416 .DeclaredAndContradicted:
417 ValidateContradictedProductStructure(
418 descriptor,
419 errors);
420 break;
421
422 case ProductStructureCheckStatus.Invalid:
423 ValidateInvalidProductStructure(
424 descriptor,
425 errors);
426 break;
427
428 case ProductStructureCheckStatus.Outdated:
429 ValidateOutdatedProductStructure(
430 descriptor,
431 errors);
432 break;
433
434 default:
435 errors.Add(
436 $"Unsupported product-structure check " +
437 $"status '{descriptor.CheckStatus}'.");
438 break;
439 }
440 }
441
442 private static void ValidateNotAnalyzedProductStructure(
443 ProductStructureDescriptor descriptor,
444 ICollection<string> errors)
445 {
446 if (descriptor.HasDeclaredType)
447 {
448 errors.Add(
449 "A declared product-structure type exists but " +
450 "the descriptor status is NotAnalyzed instead " +
451 "of DeclaredOnly.");
452 }
453
454 if (descriptor.HasDetectedType)
455 {
456 errors.Add(
457 "A product-structure type has been detected " +
458 "but the descriptor status is NotAnalyzed.");
459 }
460
461 if (descriptor.HasBeenAnalyzed)
462 {
463 errors.Add(
464 "A product-structure analysis date exists but " +
465 "the descriptor status is NotAnalyzed.");
466 }
467 }
468
469 private static void ValidateDeclaredOnlyProductStructure(
470 ProductStructureDescriptor descriptor,
471 ICollection<string> errors)
472 {
473 if (!descriptor.HasDeclaredType)
474 {
475 errors.Add(
476 "The product-structure status is DeclaredOnly " +
477 "but no declared type is available.");
478 }
479
480 if (descriptor.HasDetectedType)
481 {
482 errors.Add(
483 "The product-structure status is DeclaredOnly " +
484 "but an automatically detected type exists.");
485 }
486
487 if (descriptor.HasBeenAnalyzed)
488 {
489 errors.Add(
490 "The product-structure status is DeclaredOnly " +
491 "but an automatic analysis date exists.");
492 }
493 }
494
495 private static void ValidateDetectedOnlyProductStructure(
496 ProductStructureDescriptor descriptor,
497 ICollection<string> errors)
498 {
499 if (descriptor.HasDeclaredType)
500 {
501 errors.Add(
502 "The product-structure status is DetectedOnly " +
503 "but a declared type also exists.");
504 }
505
506 if (!descriptor.HasDetectedType)
507 {
508 errors.Add(
509 "The product-structure status is DetectedOnly " +
510 "but no detected type is available.");
511 }
512
513 ValidateCompletedProductStructureAnalysis(
514 descriptor,
515 errors);
516 }
517
518 private static void ValidateConfirmedProductStructure(
519 ProductStructureDescriptor descriptor,
520 ICollection<string> errors)
521 {
522 if (!descriptor.HasDeclaredType ||
523 !descriptor.HasDetectedType)
524 {
525 errors.Add(
526 "A confirmed product structure requires both " +
527 "declared and detected types.");
528 }
529
530 if (descriptor.DeclaredType !=
531 descriptor.DetectedType)
532 {
533 errors.Add(
534 "The product structure is marked as confirmed " +
535 "but the declared and detected types differ.");
536 }
537
538 ValidateCompletedProductStructureAnalysis(
539 descriptor,
540 errors);
541 }
542
543 private static void ValidateContradictedProductStructure(
544 ProductStructureDescriptor descriptor,
545 ICollection<string> errors)
546 {
547 if (!descriptor.HasDeclaredType ||
548 !descriptor.HasDetectedType)
549 {
550 errors.Add(
551 "A contradicted product structure requires " +
552 "both declared and detected types.");
553 }
554
555 if (descriptor.DeclaredType ==
556 descriptor.DetectedType)
557 {
558 errors.Add(
559 "The product structure is marked as " +
560 "contradicted but the declared and detected " +
561 "types are identical.");
562 }
563
564 ValidateCompletedProductStructureAnalysis(
565 descriptor,
566 errors);
567 }
568
569 private static void ValidateInvalidProductStructure(
570 ProductStructureDescriptor descriptor,
571 ICollection<string> errors)
572 {
573 if (descriptor.DetectedType !=
574 ProductStructureType.Unknown)
575 {
576 errors.Add(
577 "An invalid product structure must not retain " +
578 "a valid detected structure type.");
579 }
580
581 if (!descriptor.HasBeenAnalyzed)
582 {
583 errors.Add(
584 "The product structure is marked as invalid " +
585 "but no analysis date has been recorded.");
586 }
587 }
588
589 private static void ValidateOutdatedProductStructure(
590 ProductStructureDescriptor descriptor,
591 ICollection<string> errors)
592 {
593 if (!descriptor.HasBeenAnalyzed &&
594 !descriptor.HasDetectedType)
595 {
596 errors.Add(
597 "The product structure is marked as outdated " +
598 "but no previous automatic analysis exists.");
599 }
600 }
601
602 private static void
603 ValidateCompletedProductStructureAnalysis(
604 ProductStructureDescriptor descriptor,
605 ICollection<string> errors)
606 {
607 if (!descriptor.HasBeenAnalyzed)
608 {
609 errors.Add(
610 "A completed product-structure analysis must " +
611 "have an analysis date.");
612 }
613
614 if (string.IsNullOrWhiteSpace(
615 descriptor.AnalyzerVersion))
616 {
617 errors.Add(
618 "A completed product-structure analysis must " +
619 "record its analyzer version.");
620 }
621 }
622
623 private static void ValidateProblemClassification(
624 LotSizingProblemClassification classification,
625 ICollection<string> errors)
626 {
627 if (classification is null)
628 {
629 return;
630 }
631
632 if (classification.Features is null)
633 {
634 errors.Add(
635 "The problem classification does not contain " +
636 "a feature profile.");
637
638 return;
639 }
640
641 ValidateClassificationMatches(
642 classification,
643 errors);
644
645 switch (classification.Status)
646 {
647 case ProblemClassificationStatus.NotAnalyzed:
648 ValidateNotAnalyzedClassification(
649 classification,
650 errors);
651 break;
652
653 case ProblemClassificationStatus.Classified:
654 ValidateClassifiedClassification(
655 classification,
656 errors);
657 break;
658
660 .PartiallyClassified:
661 ValidatePartiallyClassifiedClassification(
662 classification,
663 errors);
664 break;
665
666 case ProblemClassificationStatus.Ambiguous:
667 ValidateAmbiguousClassification(
668 classification,
669 errors);
670 break;
671
672 case ProblemClassificationStatus.Unclassified:
673 ValidateUnclassifiedClassification(
674 classification,
675 errors);
676 break;
677
678 case ProblemClassificationStatus.Invalid:
679 ValidateInvalidClassification(
680 classification,
681 errors);
682 break;
683
684 case ProblemClassificationStatus.Outdated:
685 ValidateOutdatedClassification(
686 classification,
687 errors);
688 break;
689
690 default:
691 errors.Add(
692 $"Unsupported problem-classification " +
693 $"status '{classification.Status}'.");
694 break;
695 }
696 }
697
698 private static void ValidateClassificationMatches(
699 LotSizingProblemClassification classification,
700 ICollection<string> errors)
701 {
702 for (int index = 0;
703 index < classification.Matches.Count;
704 index++)
705 {
706 KnownProblemTypeMatch? match =
707 classification.Matches[index];
708
709 if (match is null)
710 {
711 errors.Add(
712 $"Problem-classification match at index " +
713 $"{index} is null.");
714
715 continue;
716 }
717
718 if (!match.HasProblemTypeCode)
719 {
720 errors.Add(
721 $"Problem-classification match at index " +
722 $"{index} has no problem-family code.");
723 }
724
725 if (!double.IsFinite(match.Score) ||
726 match.Score < 0.0 ||
727 match.Score > 1.0)
728 {
729 errors.Add(
730 $"Problem-classification match " +
731 $"'{DisplayProblemTypeCode(match)}' has " +
732 "an invalid score.");
733 }
734
735 if (match.MatchKind ==
736 ProblemMatchKind.Exact &&
738 {
739 errors.Add(
740 $"Exact problem-family match " +
741 $"'{DisplayProblemTypeCode(match)}' " +
742 "contains at least one blocking " +
743 "mismatch.");
744 }
745
746 if (match.MatchKind ==
747 ProblemMatchKind.Unknown)
748 {
749 errors.Add(
750 $"Problem-family match " +
751 $"'{DisplayProblemTypeCode(match)}' has " +
752 "an unknown match kind.");
753 }
754
755 if (match.Scope ==
757 {
758 errors.Add(
759 $"Problem-family match " +
760 $"'{DisplayProblemTypeCode(match)}' has " +
761 "an unknown classification scope.");
762 }
763
764 ValidateStringCollection(
765 match.AffectedEntityKeys,
766 $"affected entity key of match " +
767 $"'{DisplayProblemTypeCode(match)}'",
768 errors);
769
770 ValidateStringCollection(
772 $"additional feature code of match " +
773 $"'{DisplayProblemTypeCode(match)}'",
774 errors);
775
776 ValidateEvidence(
777 match,
778 errors);
779 }
780
781 if (classification.HasPrimaryProblemType &&
782 classification.PrimaryMatch is null)
783 {
784 errors.Add(
785 $"Primary problem-family code " +
786 $"'{classification.PrimaryProblemTypeCode}' " +
787 "does not reference a reported match.");
788 }
789 }
790
791 private static void ValidateEvidence(
792 KnownProblemTypeMatch match,
793 ICollection<string> errors)
794 {
795 for (int index = 0;
796 index < match.Evidence.Count;
797 index++)
798 {
799 ClassificationEvidence? evidence =
800 match.Evidence[index];
801
802 if (evidence is null)
803 {
804 errors.Add(
805 $"Evidence at index {index} of match " +
806 $"'{DisplayProblemTypeCode(match)}' is " +
807 "null.");
808
809 continue;
810 }
811
812 if (string.IsNullOrWhiteSpace(
813 evidence.FeatureCode))
814 {
815 errors.Add(
816 $"Evidence at index {index} of match " +
817 $"'{DisplayProblemTypeCode(match)}' has " +
818 "no feature code.");
819 }
820
821 if (!double.IsFinite(evidence.Weight) ||
822 evidence.Weight < 0.0)
823 {
824 errors.Add(
825 $"Evidence at index {index} of match " +
826 $"'{DisplayProblemTypeCode(match)}' has " +
827 "an invalid weight.");
828 }
829 }
830 }
831
832 private static void ValidateNotAnalyzedClassification(
833 LotSizingProblemClassification classification,
834 ICollection<string> errors)
835 {
836 if (classification.HasBeenAnalyzed)
837 {
838 errors.Add(
839 "The problem classification is NotAnalyzed " +
840 "but a classification date is present.");
841 }
842
843 if (classification.HasMatches)
844 {
845 errors.Add(
846 "The problem classification is NotAnalyzed " +
847 "but problem-family matches are present.");
848 }
849
850 if (classification.HasPrimaryProblemType)
851 {
852 errors.Add(
853 "The problem classification is NotAnalyzed " +
854 "but a primary problem family is present.");
855 }
856 }
857
858 private static void ValidateClassifiedClassification(
859 LotSizingProblemClassification classification,
860 ICollection<string> errors)
861 {
862 ValidateCompletedClassificationMetadata(
863 classification,
864 errors);
865
866 if (!classification.HasPrimaryProblemType)
867 {
868 errors.Add(
869 "A classified problem must have a primary " +
870 "problem-family code.");
871 }
872
873 if (classification.PrimaryMatch is null)
874 {
875 errors.Add(
876 "A classified problem must have a valid " +
877 "primary problem-family match.");
878 }
879 else if (!classification.PrimaryMatch.IsExactMatch)
880 {
881 errors.Add(
882 "A fully classified problem must use an exact " +
883 "primary problem-family match.");
884 }
885
886 if (classification.HasUnclassifiedFeatures)
887 {
888 errors.Add(
889 "A fully classified problem cannot contain " +
890 "globally unclassified features.");
891 }
892
893 if (classification.HasErrors)
894 {
895 errors.Add(
896 "A fully classified problem cannot contain " +
897 "classification errors.");
898 }
899 }
900
901 private static void
902 ValidatePartiallyClassifiedClassification(
903 LotSizingProblemClassification classification,
904 ICollection<string> errors)
905 {
906 ValidateCompletedClassificationMetadata(
907 classification,
908 errors);
909
910 if (!classification.HasMatches)
911 {
912 errors.Add(
913 "A partially classified problem must contain " +
914 "at least one problem-family match.");
915 }
916
917 if (!classification.HasPrimaryProblemType &&
918 !classification.Matches.Any(
919 match =>
920 match is not null &&
921 (
922 match.MatchKind ==
924 .RecognizedRelaxation ||
925 match.MatchKind ==
927 .RecognizedSubproblem
928 )))
929 {
930 errors.Add(
931 "A partially classified problem must either " +
932 "have a primary family or contain a " +
933 "recognized relaxation or subproblem.");
934 }
935 }
936
937 private static void ValidateAmbiguousClassification(
938 LotSizingProblemClassification classification,
939 ICollection<string> errors)
940 {
941 ValidateCompletedClassificationMetadata(
942 classification,
943 errors);
944
945 if (classification.HasPrimaryProblemType)
946 {
947 errors.Add(
948 "An ambiguous classification must not select " +
949 "a primary problem family.");
950 }
951
952 int candidateCount =
953 classification.Matches.Count(
954 match =>
955 match is not null &&
956 match.IsDirectMatch &&
958
959 if (candidateCount < 2)
960 {
961 errors.Add(
962 "An ambiguous classification must contain at " +
963 "least two direct complete-problem matches.");
964 }
965 }
966
967 private static void ValidateUnclassifiedClassification(
968 LotSizingProblemClassification classification,
969 ICollection<string> errors)
970 {
971 ValidateCompletedClassificationMetadata(
972 classification,
973 errors);
974
975 if (classification.HasPrimaryProblemType)
976 {
977 errors.Add(
978 "An unclassified problem must not have a " +
979 "primary problem-family code.");
980 }
981
982 if (classification.Matches.Any(
983 match =>
984 match is not null &&
985 match.IsDirectMatch &&
987 {
988 errors.Add(
989 "An unclassified problem cannot contain a " +
990 "direct complete-problem match.");
991 }
992 }
993
994 private static void ValidateInvalidClassification(
995 LotSizingProblemClassification classification,
996 ICollection<string> errors)
997 {
998 if (!classification.HasErrors)
999 {
1000 errors.Add(
1001 "An invalid problem classification must " +
1002 "contain at least one explanatory error.");
1003 }
1004 }
1005
1006 private static void ValidateOutdatedClassification(
1007 LotSizingProblemClassification classification,
1008 ICollection<string> errors)
1009 {
1010 if (!classification.HasBeenAnalyzed &&
1011 !classification.HasMatches)
1012 {
1013 errors.Add(
1014 "The problem classification is marked as " +
1015 "outdated but no previous classification " +
1016 "result exists.");
1017 }
1018 }
1019
1020 private static void
1021 ValidateCompletedClassificationMetadata(
1022 LotSizingProblemClassification classification,
1023 ICollection<string> errors)
1024 {
1025 if (!classification.HasBeenAnalyzed)
1026 {
1027 errors.Add(
1028 "A completed problem classification must " +
1029 "record its classification date.");
1030 }
1031
1032 if (!classification.HasClassifierVersion)
1033 {
1034 errors.Add(
1035 "A completed problem classification must " +
1036 "record its classifier version.");
1037 }
1038
1039 if (!classification.HasCatalogInformation)
1040 {
1041 errors.Add(
1042 "A completed problem classification must " +
1043 "record its catalog name and version.");
1044 }
1045 }
1046
1047 private static void ValidateRecommendationReport(
1048 LotSizingInstance instance,
1049 ICollection<string> errors)
1050 {
1052 {
1053 return;
1054 }
1055
1056 SolutionMethodRecommendationReport report =
1058
1059 IReadOnlyList<string> reportErrors =
1060 report.Validate();
1061
1062 foreach (string reportError in reportErrors)
1063 {
1064 errors.Add(
1065 "Solution-method recommendation report: " +
1066 reportError);
1067 }
1068 }
1069
1070 private static void AddRecommendationWarnings(
1071 LotSizingInstance instance,
1072 ICollection<string> warnings)
1073 {
1075 {
1076 return;
1077 }
1078
1079 SolutionMethodRecommendationReport report =
1081
1082 if (report.HasBeenGenerated &&
1083 !report.HasRecommendations)
1084 {
1085 warnings.Add(
1086 "The solution-method recommendation report " +
1087 "was generated but contains no " +
1088 "recommendation.");
1089 }
1090
1091 if (report.HasRecommendations &&
1092 !report.HasApplicableMethod)
1093 {
1094 warnings.Add(
1095 "The solution-method recommendation report " +
1096 "does not identify an applicable method.");
1097 }
1098
1099 foreach (SolutionMethodRecommendation recommendation
1100 in report.Recommendations.Where(
1101 recommendation =>
1102 recommendation is not null))
1103 {
1104 if (!recommendation.HasBeenEvaluated)
1105 {
1106 warnings.Add(
1107 $"Method recommendation " +
1108 $"'{recommendation.MethodCode}' has not " +
1109 "been evaluated.");
1110 }
1111
1112 foreach (string recommendationWarning
1113 in recommendation.Warnings)
1114 {
1115 if (!string.IsNullOrWhiteSpace(
1116 recommendationWarning))
1117 {
1118 warnings.Add(
1119 $"Method '{recommendation.MethodCode}': " +
1120 recommendationWarning.Trim());
1121 }
1122 }
1123 }
1124 }
1125
1126 private static Dictionary<string, KnownResult>
1127 ValidateKnownResults(
1128 LotSizingInstance instance,
1129 ICollection<string> errors)
1130 {
1131 var knownResultsById =
1132 new Dictionary<string, KnownResult>(
1133 StringComparer.OrdinalIgnoreCase);
1134
1135 for (int index = 0;
1136 index < instance.KnownResults.Count;
1137 index++)
1138 {
1139 KnownResult? result =
1140 instance.KnownResults[index];
1141
1142 if (result is null)
1143 {
1144 errors.Add(
1145 $"Known result at index {index} is null.");
1146
1147 continue;
1148 }
1149
1150 if (!result.HasResultId)
1151 {
1152 errors.Add(
1153 $"Known result at index {index} has no " +
1154 "stable identifier.");
1155
1156 continue;
1157 }
1158
1159 if (!knownResultsById.TryAdd(
1160 result.ResultId,
1161 result))
1162 {
1163 errors.Add(
1164 $"Known-result identifier " +
1165 $"'{result.ResultId}' is used more than " +
1166 "once.");
1167 }
1168
1169 ValidateKnownResult(
1170 result,
1171 errors);
1172 }
1173
1174 return knownResultsById;
1175 }
1176
1177 private static void ValidateKnownResult(
1178 KnownResult result,
1179 ICollection<string> errors)
1180 {
1181 bool hasMeaningfulInformation =
1182 result.HasReportedObjectiveValue ||
1183 result.HasDetailedSolution ||
1184 result.HasFeasibilityInformation ||
1185 result.HasOptimalityInformation ||
1186 result.HasMethodInformation ||
1187 result.HasSourceInformation ||
1188 result.HasComment;
1189
1190 if (!hasMeaningfulInformation)
1191 {
1192 errors.Add(
1193 $"Known result '{result.ResultId}' does not " +
1194 "contain any result, claim, source or " +
1195 "descriptive information.");
1196 }
1197
1198 if (result.IsSuperseded &&
1199 string.Equals(
1200 result.ResultId,
1201 result.SupersededByResultId,
1202 StringComparison.OrdinalIgnoreCase))
1203 {
1204 errors.Add(
1205 $"Known result '{result.ResultId}' cannot " +
1206 "supersede itself.");
1207 }
1208
1209 if (result.ReportedObjectiveValue.HasValue &&
1210 !double.IsFinite(
1211 result.ReportedObjectiveValue.Value))
1212 {
1213 errors.Add(
1214 $"Known result '{result.ResultId}' contains " +
1215 "a non-finite objective value.");
1216 }
1217 }
1218
1219 private static void ValidateBestKnownResult(
1220 LotSizingInstance instance,
1221 IReadOnlyDictionary<string, KnownResult>
1222 knownResultsById,
1223 ICollection<string> errors)
1224 {
1225 if (!instance.HasBestKnownResultId)
1226 {
1227 return;
1228 }
1229
1230 if (!knownResultsById.TryGetValue(
1231 instance.BestKnownResultId,
1232 out KnownResult? bestKnownResult))
1233 {
1234 errors.Add(
1235 $"Best-known-result identifier " +
1236 $"'{instance.BestKnownResultId}' does not " +
1237 "reference a known result.");
1238
1239 return;
1240 }
1241
1242 if (!bestKnownResult
1243 .CanBeSelectedAsBestKnownResult)
1244 {
1245 errors.Add(
1246 $"Known result '{bestKnownResult.ResultId}' " +
1247 "is not eligible to be selected as the best " +
1248 "known result.");
1249 }
1250 }
1251
1252 private static void ValidateSupersedingReferences(
1253 IEnumerable<KnownResult> knownResults,
1254 IReadOnlyDictionary<string, KnownResult>
1255 knownResultsById,
1256 ICollection<string> errors)
1257 {
1258 foreach (KnownResult result
1259 in knownResults.Where(
1260 result =>
1261 result is not null &&
1262 result.IsSuperseded))
1263 {
1264 if (!knownResultsById.ContainsKey(
1265 result.SupersededByResultId))
1266 {
1267 errors.Add(
1268 $"Known result '{result.ResultId}' is " +
1269 $"superseded by unknown result " +
1270 $"'{result.SupersededByResultId}'.");
1271 }
1272 }
1273 }
1274
1275 private static void ValidateSupersedingCycles(
1276 IReadOnlyDictionary<string, KnownResult>
1277 knownResultsById,
1278 ICollection<string> errors)
1279 {
1280 var reportedCycles =
1281 new HashSet<string>(
1282 StringComparer.OrdinalIgnoreCase);
1283
1284 foreach (KnownResult startResult
1285 in knownResultsById.Values)
1286 {
1287 var positionByResultId =
1288 new Dictionary<string, int>(
1289 StringComparer.OrdinalIgnoreCase);
1290
1291 var path =
1292 new List<string>();
1293
1294 KnownResult? currentResult =
1295 startResult;
1296
1297 while (currentResult is not null &&
1298 currentResult.IsSuperseded)
1299 {
1300 if (positionByResultId.TryGetValue(
1301 currentResult.ResultId,
1302 out int cycleStartIndex))
1303 {
1304 string[] cycleResultIds =
1305 path
1306 .Skip(cycleStartIndex)
1307 .Concat(
1308 new[]
1309 {
1310 currentResult.ResultId
1311 })
1312 .ToArray();
1313
1314 string cycleDescription =
1315 string.Join(
1316 " -> ",
1317 cycleResultIds);
1318
1319 if (reportedCycles.Add(
1320 cycleDescription))
1321 {
1322 errors.Add(
1323 "The known-result supersession " +
1324 $"graph contains a cycle: " +
1325 $"{cycleDescription}.");
1326 }
1327
1328 break;
1329 }
1330
1331 positionByResultId.Add(
1332 currentResult.ResultId,
1333 path.Count);
1334
1335 path.Add(
1336 currentResult.ResultId);
1337
1338 if (!knownResultsById.TryGetValue(
1339 currentResult
1341 out KnownResult? nextResult))
1342 {
1343 break;
1344 }
1345
1346 currentResult =
1347 nextResult;
1348 }
1349 }
1350 }
1351
1352 private static void ValidateFingerprints(
1353 LotSizingInstance instance,
1354 ICollection<string> errors)
1355 {
1356 string currentFingerprint;
1357
1358 try
1359 {
1360 currentFingerprint =
1361 LotSizingInstanceFactory
1362 .ComputeSupplyChainFingerprint(
1363 instance.SupplyChain);
1364 }
1365 catch (InvalidOperationException exception)
1366 {
1367 errors.Add(
1368 "The current supply-chain fingerprint could " +
1369 $"not be calculated: {exception.Message}");
1370
1371 return;
1372 }
1373
1374 ProductStructureDescriptor descriptor =
1375 instance.ProductStructure;
1376
1377 bool productStructureShouldBeCurrent =
1378 descriptor.CheckStatus !=
1379 ProductStructureCheckStatus.NotAnalyzed &&
1380 descriptor.CheckStatus !=
1381 ProductStructureCheckStatus.DeclaredOnly &&
1382 descriptor.CheckStatus !=
1384
1385 if (productStructureShouldBeCurrent &&
1386 !string.IsNullOrWhiteSpace(
1387 descriptor.SupplyChainFingerprint) &&
1388 !string.Equals(
1389 descriptor.SupplyChainFingerprint,
1390 currentFingerprint,
1391 StringComparison.Ordinal))
1392 {
1393 errors.Add(
1394 "The product-structure analysis does not " +
1395 "match the current supply-chain fingerprint " +
1396 "and should be marked as outdated.");
1397 }
1398
1399 LotSizingProblemClassification classification =
1400 instance.ProblemClassification;
1401
1402 bool classificationShouldBeCurrent =
1403 classification.Status !=
1404 ProblemClassificationStatus.NotAnalyzed &&
1405 classification.Status !=
1407
1408 if (classificationShouldBeCurrent &&
1409 classification.HasSupplyChainFingerprint &&
1410 !classification
1412 currentFingerprint))
1413 {
1414 errors.Add(
1415 "The problem classification does not match " +
1416 "the current supply-chain fingerprint and " +
1417 "should be marked as outdated.");
1418 }
1419
1420 SolutionMethodRecommendationReport recommendationReport =
1422
1424 recommendationReport.HasSupplyChainFingerprint &&
1425 !recommendationReport
1427 currentFingerprint))
1428 {
1429 errors.Add(
1430 "The solution-method recommendation report " +
1431 "does not match the current supply-chain " +
1432 "fingerprint and must be regenerated.");
1433 }
1434 }
1435
1436 private static void AddProductStructureWarnings(
1437 ProductStructureDescriptor descriptor,
1438 ICollection<string> warnings)
1439 {
1440 if (descriptor.CheckStatus ==
1442 .DeclaredAndContradicted)
1443 {
1444 warnings.Add(
1445 $"The declared product-structure type " +
1446 $"'{descriptor.DeclaredType}' differs from " +
1447 $"the detected type " +
1448 $"'{descriptor.DetectedType}'.");
1449 }
1450
1451 if (descriptor.CheckStatus ==
1453 {
1454 warnings.Add(
1455 "The stored product-structure analysis is " +
1456 "outdated.");
1457 }
1458
1459 if (descriptor.HasBeenAnalyzed &&
1460 string.IsNullOrWhiteSpace(
1461 descriptor.SupplyChainFingerprint))
1462 {
1463 warnings.Add(
1464 "The product-structure analysis does not " +
1465 "record a supply-chain fingerprint.");
1466 }
1467 }
1468
1469 private static void AddClassificationWarnings(
1470 LotSizingProblemClassification classification,
1471 ICollection<string> warnings)
1472 {
1473 if (classification.Status ==
1475 {
1476 warnings.Add(
1477 "The stored problem classification is " +
1478 "outdated.");
1479 }
1480
1481 if (classification.Status ==
1483 .PartiallyClassified)
1484 {
1485 warnings.Add(
1486 "The problem is only partially classified.");
1487 }
1488
1489 if (classification.Status ==
1491 {
1492 warnings.Add(
1493 "The problem classification is ambiguous.");
1494 }
1495
1496 if (classification.HasPrimaryProblemType &&
1497 !classification
1499 {
1500 warnings.Add(
1501 $"Primary problem family " +
1502 $"'{classification.PrimaryProblemTypeCode}' " +
1503 "does not have a human-readable name.");
1504 }
1505
1506 if (classification.HasBeenAnalyzed &&
1507 !classification
1509 {
1510 warnings.Add(
1511 "The problem classification does not record " +
1512 "a supply-chain fingerprint.");
1513 }
1514
1515 foreach (string warning
1516 in classification.Warnings)
1517 {
1518 if (!string.IsNullOrWhiteSpace(warning))
1519 {
1520 warnings.Add(
1521 "Classifier: " +
1522 warning.Trim());
1523 }
1524 }
1525 }
1526
1527 private static void AddKnownResultWarnings(
1528 KnownResult result,
1529 ICollection<string> warnings)
1530 {
1531 if (result.HasReportedObjectiveValue &&
1532 !result.HasObjectiveName)
1533 {
1534 warnings.Add(
1535 $"Known result '{result.ResultId}' has an " +
1536 "objective value but no objective name.");
1537 }
1538
1539 if (result.HasReportedObjectiveValue &&
1540 !result.HasObjectiveUnit)
1541 {
1542 warnings.Add(
1543 $"Known result '{result.ResultId}' has an " +
1544 "objective value but no objective unit.");
1545 }
1546
1547 if (result.VerificationStatus ==
1549 .SourceReported &&
1550 !result.HasSourceInformation)
1551 {
1552 warnings.Add(
1553 $"Known result '{result.ResultId}' is marked " +
1554 "as source-reported but has no source " +
1555 "information.");
1556 }
1557
1558 if (result.HasDetailedSolution &&
1559 result.VerificationStatus ==
1561 {
1562 warnings.Add(
1563 $"Known result '{result.ResultId}' contains " +
1564 "a detailed solution that has not been " +
1565 "verified.");
1566 }
1567
1568 if (!result.HasSupplyChainFingerprint)
1569 {
1570 warnings.Add(
1571 $"Known result '{result.ResultId}' does not " +
1572 "record a supply-chain fingerprint.");
1573 }
1574 }
1575
1576 private static void AddFingerprintWarnings(
1577 LotSizingInstance instance,
1578 ICollection<string> warnings)
1579 {
1580 string currentFingerprint;
1581
1582 try
1583 {
1584 currentFingerprint =
1585 LotSizingInstanceFactory
1586 .ComputeSupplyChainFingerprint(
1587 instance.SupplyChain);
1588 }
1589 catch (InvalidOperationException)
1590 {
1591 return;
1592 }
1593
1594 foreach (KnownResult result
1595 in instance.KnownResults.Where(
1596 result =>
1597 result is not null &&
1599 {
1601 currentFingerprint))
1602 {
1603 warnings.Add(
1604 $"Known result '{result.ResultId}' was " +
1605 "recorded for a different supply-chain " +
1606 "fingerprint.");
1607 }
1608 }
1609
1610 SolutionMethodRecommendationReport recommendationReport =
1612
1614 recommendationReport.HasSupplyChainFingerprint &&
1615 !recommendationReport
1617 currentFingerprint))
1618 {
1619 warnings.Add(
1620 "The solution-method recommendation report " +
1621 "was generated for a different supply-chain " +
1622 "fingerprint.");
1623 }
1624 }
1625
1626 private static void ValidateItemIdentifierCollection(
1627 IEnumerable<int> itemIds,
1628 string collectionDescription,
1629 ICollection<string> errors)
1630 {
1631 int[] normalizedItemIds =
1632 itemIds.ToArray();
1633
1634 if (normalizedItemIds.Any(
1635 itemId =>
1636 itemId <= 0))
1637 {
1638 errors.Add(
1639 $"Every {collectionDescription} identifier " +
1640 "must be strictly positive.");
1641 }
1642
1643 int[] duplicateItemIds =
1644 normalizedItemIds
1645 .GroupBy(
1646 itemId =>
1647 itemId)
1648 .Where(
1649 group =>
1650 group.Count() > 1)
1651 .Select(
1652 group =>
1653 group.Key)
1654 .OrderBy(
1655 itemId =>
1656 itemId)
1657 .ToArray();
1658
1659 if (duplicateItemIds.Length > 0)
1660 {
1661 errors.Add(
1662 $"Duplicate {collectionDescription} " +
1663 "identifiers: " +
1664 string.Join(
1665 ", ",
1666 duplicateItemIds) +
1667 ".");
1668 }
1669 }
1670
1671 private static void ValidateStringCollection(
1672 IEnumerable<string> values,
1673 string valueDescription,
1674 ICollection<string> errors)
1675 {
1676 string[] normalizedValues =
1677 values.ToArray();
1678
1679 if (normalizedValues.Any(
1680 value =>
1681 string.IsNullOrWhiteSpace(value)))
1682 {
1683 errors.Add(
1684 $"Every {valueDescription} must be " +
1685 "non-empty.");
1686 }
1687
1688 string[] duplicateValues =
1689 normalizedValues
1690 .Where(
1691 value =>
1692 !string.IsNullOrWhiteSpace(value))
1693 .GroupBy(
1694 value =>
1695 value.Trim(),
1696 StringComparer.OrdinalIgnoreCase)
1697 .Where(
1698 group =>
1699 group.Count() > 1)
1700 .Select(
1701 group =>
1702 group.Key)
1703 .OrderBy(
1704 value =>
1705 value,
1706 StringComparer.OrdinalIgnoreCase)
1707 .ToArray();
1708
1709 if (duplicateValues.Length > 0)
1710 {
1711 errors.Add(
1712 $"Duplicate {valueDescription} values: " +
1713 string.Join(
1714 ", ",
1715 duplicateValues) +
1716 ".");
1717 }
1718 }
1719
1720 private static IReadOnlyList<string> NormalizeMessages(
1721 IEnumerable<string> messages)
1722 {
1723 return messages
1724 .Where(
1725 message =>
1726 !string.IsNullOrWhiteSpace(message))
1727 .Select(
1728 message =>
1729 message.Trim())
1730 .Distinct(
1731 StringComparer.Ordinal)
1732 .OrderBy(
1733 message =>
1734 message,
1735 StringComparer.Ordinal)
1736 .ToArray();
1737 }
1738
1739 private static string DisplayProblemTypeCode(
1740 KnownProblemTypeMatch match)
1741 {
1742 return string.IsNullOrWhiteSpace(
1743 match.ProblemTypeCode)
1744 ? "<missing code>"
1745 : match.ProblemTypeCode;
1746 }
1747}
double Weight
Gets or sets the relative importance assigned to this evidence during approximate matching.
string FeatureCode
Gets or sets the stable code of the feature that was evaluated.
double Score
Gets or sets the normalized score assigned to this match.
List< string > AffectedEntityKeys
Gets the stable keys of the entities affected by this classification match.
bool HasBlockingMismatches
Gets a value indicating whether at least one required classification condition is not satisfied.
List< string > AdditionalFeatureCodes
Gets the codes of additional instance features not covered by the recognized classical family.
List< ClassificationEvidence > Evidence
Gets the factual evidence supporting or contradicting this problem-family match.
bool AppliesToCompleteProblem
Gets a value indicating whether this match describes the complete problem directly.
string ProblemTypeCode
Gets or sets the stable code identifying the known lot-sizing problem family.
bool IsDirectMatch
Gets a value indicating whether the match can provide direct structural support for method selection.
ProblemMatchKind MatchKind
Gets or sets the nature of the correspondence between the instance and the known problem family.
ProblemClassificationScope Scope
Gets or sets the part of the instance to which this problem-family match applies.
bool HasProblemTypeCode
Gets a value indicating whether a problem-family code has been recorded.
bool IsExactMatch
Gets a value indicating whether the match is declared as exact.
bool HasClassifierVersion
Gets a value indicating whether the classifier version has been recorded.
bool HasSupplyChainFingerprint
Gets a value indicating whether a supply-chain fingerprint has been recorded.
bool HasCatalogInformation
Gets a value indicating whether both catalog name and catalog version have been recorded.
KnownProblemTypeMatch? PrimaryMatch
Gets the match corresponding to the selected primary problem-family code.
LotSizingProblemFeatures Features
Gets or sets the factual lot-sizing problem features used during classification.
bool HasUnclassifiedFeatures
Gets a value indicating whether unclassified features remain after classification.
bool HasPrimaryProblemTypeName
Gets a value indicating whether the primary problem-family name has been recorded.
bool MatchesSupplyChainFingerprint(string currentFingerprint)
Determines whether the stored classification was produced from the supplied supply-chain fingerprint.
List< string > Warnings
Gets the warnings produced during feature extraction or problem classification.
bool HasErrors
Gets a value indicating whether at least one error was produced.
bool HasMatches
Gets a value indicating whether at least one problem-family match has been reported.
List< KnownProblemTypeMatch > Matches
Gets all known-problem-family matches detected for the analyzed instance.
bool HasBeenAnalyzed
Gets a value indicating whether a completed analysis date has been recorded.
ProblemClassificationStatus Status
Gets or sets the current global classification status.
bool HasPrimaryProblemType
Gets a value indicating whether a primary problem-family code has been selected.
Adds explicit closed-loop return streams to a lot-sizing instance without changing the historical Sup...
LotSizingProblemClassification ProblemClassification
Gets or sets the automatic lot-sizing problem classification.
SolutionMethodRecommendationReport SolutionMethodRecommendationReport
Gets or sets the solution-method recommendation report generated for this problem instance.
List< KnownResult > KnownResults
Gets the known results associated with the instance.
bool HasSolutionMethodRecommendationReport
Gets a value indicating whether a recommendation report has been generated or contains recommendation...
bool HasCreationDate
Gets a value indicating whether a creation date has been recorded.
bool HasInstanceId
Gets a value indicating whether the instance has a stable identifier.
DateTime? ModifiedAtUtc
Gets or sets the UTC date and time at which the instance was last modified.
DateTime? CreatedAtUtc
Gets or sets the UTC date and time at which the instance was created.
SupplyChain SupplyChain
Gets or sets the supply-chain model represented by the instance.
bool HasBestKnownResultId
Gets a value indicating whether a best known result identifier has been selected.
string BestKnownResultId
Gets or sets the identifier of the result currently considered to be the best known result.
ProductStructureDescriptor ProductStructure
Gets or sets the product-structure descriptor associated with the supply-chain model.
bool HasName
Gets a value indicating whether the instance has a human-readable name.
bool HasFormatVersion
Gets a value indicating whether a format version has been recorded.
bool HasSourceInformation
Gets a value indicating whether source information has been recorded.
List< int > RootItemIds
Gets the identifiers of root items in the bill-of-materials graph.
ProductStructureType DeclaredType
Gets or sets the product-structure type declared by an author, publication or data provider.
bool HasCycle
Gets or sets a value indicating whether the analyzed bill-of-materials graph contains at least one cy...
bool HasDeclaredType
Gets a value indicating whether an author or source declared a product-structure type.
List< int > LeafItemIds
Gets the identifiers of leaf items in the bill-of-materials graph.
string SupplyChainFingerprint
Gets or sets the fingerprint of the supply-chain data used during the product-structure analysis.
ProductStructureCheckStatus CheckStatus
Gets or sets the current status of the product-structure declaration and automatic verification.
bool HasDetectedType
Gets a value indicating whether an automatic analysis detected a product-structure type.
ProductStructureType DetectedType
Gets or sets the product-structure type detected automatically from the supply-chain data.
string AnalyzerVersion
Gets or sets the version of the component that performed the product-structure analysis.
bool HasBeenAnalyzed
Gets a value indicating whether an automatic analysis date has been recorded.
List< int > SharedComponentItemIds
Gets the identifiers of components used by more than one immediate parent item.
List< string > Warnings
Gets the non-fatal warnings produced during method evaluation.
bool HasBeenEvaluated
Gets a value indicating whether the method has been evaluated.
bool HasBeenGenerated
Gets a value indicating whether this report has been generated.
List< SolutionMethodRecommendation > Recommendations
Gets the ranked solution-method recommendations.
bool HasSupplyChainFingerprint
Gets a value indicating whether a supply-chain fingerprint has been recorded.
bool HasRecommendations
Gets a value indicating whether at least one recommendation has been recorded.
bool MatchesSupplyChainFingerprint(string currentFingerprint)
Determines whether the report was generated for the supplied supply-chain fingerprint.
bool HasApplicableMethod
Gets a value indicating whether at least one method can be applied directly or after adaptation.
IReadOnlyList< string > Validate()
Validates the structural consistency of the report.
Represents a known result associated with a lot-sizing problem instance.
bool HasObjectiveUnit
Gets a value indicating whether the objective unit has been recorded.
bool MatchesSupplyChainFingerprint(string currentFingerprint)
Determines whether this result was obtained for the supplied supply-chain fingerprint.
bool HasReportedObjectiveValue
Gets a value indicating whether an explicit objective value has been reported.
bool HasObjectiveName
Gets a value indicating whether the objective name has been recorded.
string ResultId
Gets or sets the stable identifier of this result.
KnownResultVerificationStatus VerificationStatus
Gets or sets the level of verification applied to this result.
string SupersededByResultId
Gets or sets the identifier of a newer result that supersedes this result.
bool HasSupplyChainFingerprint
Gets a value indicating whether a supply-chain fingerprint has been recorded.
bool HasSourceInformation
Gets a value indicating whether at least one source field has been recorded.
double? ReportedObjectiveValue
Gets or sets the objective value explicitly reported for this result.
bool HasComment
Gets a value indicating whether an explanatory comment has been recorded.
bool IsSuperseded
Gets a value indicating whether this result has been superseded by another result.
bool HasDetailedSolution
Gets a value indicating whether a detailed solution is associated with the result.
bool HasResultId
Gets a value indicating whether the result has a stable identifier.
Validates the structural and referential consistency of a complete lot-sizing problem instance.
static bool IsValid(LotSizingInstance instance, bool validateCurrentFingerprint=true)
Determines whether a lot-sizing problem instance is structurally valid.
static IReadOnlyList< string > GetWarnings(LotSizingInstance instance, bool validateCurrentFingerprint=true)
Returns non-fatal consistency and quality warnings for a lot-sizing problem instance.
static void EnsureValid(LotSizingInstance instance, bool validateCurrentFingerprint=true)
Validates a lot-sizing instance and throws an exception when at least one error is found.
static IReadOnlyList< string > Validate(LotSizingInstance instance, bool validateCurrentFingerprint=true)
Validates a lot-sizing problem instance.
ProblemMatchKind
Identifies how a supply-chain instance matches a known lot-sizing problem family.
ProductStructureCheckStatus
Indicates the current status of the declaration, automatic detection and verification of a product bi...
ProblemClassificationStatus
Indicates the current status of the automatic classification of a lot-sizing problem instance.
ProductStructureType
Identifies the structural category of a product bill-of-materials graph.
ProblemClassificationScope
Identifies the part of a lot-sizing instance to which a known-problem-family match applies.
KnownResultVerificationStatus
Indicates the level of verification associated with a known result for a lot-sizing problem instance.