LotSizingDataModel.Instance 2.0.1
Lot-sizing instance representation, descriptors and problem characterization.
Loading...
Searching...
No Matches
SolutionMethodAdvisor.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.Linq;
5using System.Reflection;
9
11
12/// <summary>
13/// Evaluates and ranks solution methods for lot-sizing
14/// problem instances.
15/// </summary>
16/// <remarks>
17/// The advisor evaluates:
18/// <list type="bullet">
19/// <item>
20/// <description>
21/// problem-family compatibility;
22/// </description>
23/// </item>
24/// <item>
25/// <description>
26/// product-structure compatibility;
27/// </description>
28/// </item>
29/// <item>
30/// <description>
31/// required, supported and unsupported features;
32/// </description>
33/// </item>
34/// <item>
35/// <description>
36/// hard and recommended instance-size limits;
37/// </description>
38/// </item>
39/// <item>
40/// <description>
41/// applicability to the complete problem, a relaxation or a
42/// subproblem.
43/// </description>
44/// </item>
45/// </list>
46///
47/// The advisor does not execute the recommended methods and
48/// does not guarantee their practical performance.
49/// </remarks>
50public static class SolutionMethodAdvisor
51{
52 /// <summary>
53 /// Gets the current version of the solution-method advisor
54 /// evaluation semantics.
55 /// </summary>
56 public const string CurrentVersion =
57 "1.0";
58
59 /// <summary>
60 /// Gets the default score above which a directly
61 /// compatible method may be marked as recommended.
62 /// </summary>
64 0.85;
65
66 /// <summary>
67 /// Evaluates and ranks every usable method contained in a
68 /// solution-method catalog.
69 /// </summary>
70 /// <param name="instance">
71 /// Lot-sizing problem instance to analyze.
72 /// </param>
73 /// <param name="catalog">
74 /// Catalog containing the solution-method definitions.
75 /// </param>
76 /// <param name="recommendedScoreThreshold">
77 /// Minimum score required for a directly compatible
78 /// method to be marked as recommended.
79 /// </param>
80 /// <param name="includeIncompatibleMethods">
81 /// Value indicating whether incompatible methods must be
82 /// included in the returned collection.
83 /// </param>
84 /// <returns>
85 /// Ranked method recommendations.
86 /// </returns>
87 /// <exception cref="ArgumentNullException">
88 /// Thrown when <paramref name="instance"/> or
89 /// <paramref name="catalog"/> is
90 /// <see langword="null"/>.
91 /// </exception>
92 /// <exception cref="ArgumentOutOfRangeException">
93 /// Thrown when
94 /// <paramref name="recommendedScoreThreshold"/> is not
95 /// finite or does not lie between zero and one.
96 /// </exception>
97 /// <exception cref="InvalidOperationException">
98 /// Thrown when the method catalog is invalid or the
99 /// supply-chain fingerprint cannot be calculated.
100 /// </exception>
101 public static IReadOnlyList<SolutionMethodRecommendation>
103 LotSizingInstance instance,
104 SolutionMethodCatalog catalog,
105 double recommendedScoreThreshold =
107 bool includeIncompatibleMethods = true)
108 {
109 ArgumentNullException.ThrowIfNull(instance);
110 ArgumentNullException.ThrowIfNull(catalog);
111
112 ValidateRecommendedScoreThreshold(
113 recommendedScoreThreshold);
114
115 catalog.EnsureValid();
116
117 string supplyChainFingerprint =
120 instance.SupplyChain);
121
122 IReadOnlyList<SolutionMethodDefinition>
123 methodDefinitions =
124 catalog.GetMethodsForEvaluation();
125
126 var recommendations =
127 new List<SolutionMethodRecommendation>(
128 methodDefinitions.Count);
129
130 foreach (SolutionMethodDefinition methodDefinition
131 in methodDefinitions)
132 {
133 SolutionMethodRecommendation recommendation =
135 instance:
136 instance,
137
138 methodDefinition:
139 methodDefinition,
140
141 methodCatalogName:
142 catalog.CatalogName,
143
144 methodCatalogVersion:
145 catalog.CatalogVersion,
146
147 supplyChainFingerprint:
148 supplyChainFingerprint,
149
150 recommendedScoreThreshold:
151 recommendedScoreThreshold);
152
153 if (includeIncompatibleMethods ||
154 !recommendation.IsIncompatible)
155 {
156 recommendations.Add(
157 recommendation);
158 }
159 }
160
161 return RankRecommendations(
162 recommendations,
163 methodDefinitions);
164 }
165
166 /// <summary>
167 /// Evaluates one solution method for a lot-sizing problem
168 /// instance.
169 /// </summary>
170 /// <param name="instance">
171 /// Lot-sizing problem instance to analyze.
172 /// </param>
173 /// <param name="methodDefinition">
174 /// Solution-method definition to evaluate.
175 /// </param>
176 /// <param name="methodCatalogName">
177 /// Optional name of the catalog containing the method.
178 /// </param>
179 /// <param name="methodCatalogVersion">
180 /// Optional version of the catalog containing the method.
181 /// </param>
182 /// <param name="recommendedScoreThreshold">
183 /// Minimum score required for a directly compatible
184 /// method to be marked as recommended.
185 /// </param>
186 /// <returns>
187 /// Compatibility recommendation for the supplied method.
188 /// </returns>
190 LotSizingInstance instance,
191 SolutionMethodDefinition methodDefinition,
192 string methodCatalogName = "",
193 string methodCatalogVersion = "",
194 double recommendedScoreThreshold =
196 {
197 ArgumentNullException.ThrowIfNull(instance);
198 ArgumentNullException.ThrowIfNull(methodDefinition);
199
200 ValidateRecommendedScoreThreshold(
201 recommendedScoreThreshold);
202
203 methodDefinition.EnsureValid();
204
205 string supplyChainFingerprint =
208 instance.SupplyChain);
209
210 return EvaluateMethod(
211 instance:
212 instance,
213
214 methodDefinition:
215 methodDefinition,
216
217 methodCatalogName:
218 methodCatalogName,
219
220 methodCatalogVersion:
221 methodCatalogVersion,
222
223 supplyChainFingerprint:
224 supplyChainFingerprint,
225
226 recommendedScoreThreshold:
227 recommendedScoreThreshold);
228 }
229
230 /// <summary>
231 /// Ranks an existing collection of method
232 /// recommendations.
233 /// </summary>
234 /// <param name="recommendations">
235 /// Recommendations to rank.
236 /// </param>
237 /// <returns>
238 /// Recommendations ordered from the most to the least
239 /// appropriate.
240 /// </returns>
241 /// <remarks>
242 /// Ranking uses:
243 /// <list type="number">
244 /// <item>
245 /// <description>compatibility level;</description>
246 /// </item>
247 /// <item>
248 /// <description>compatibility score;</description>
249 /// </item>
250 /// <item>
251 /// <description>method name;</description>
252 /// </item>
253 /// <item>
254 /// <description>method code.</description>
255 /// </item>
256 /// </list>
257 /// </remarks>
258 public static IReadOnlyList<SolutionMethodRecommendation>
260 IEnumerable<SolutionMethodRecommendation>
261 recommendations)
262 {
263 ArgumentNullException.ThrowIfNull(
264 recommendations);
265
266 SolutionMethodRecommendation[] materialized =
267 recommendations.ToArray();
268
269 if (materialized.Any(
270 recommendation =>
271 recommendation is null))
272 {
273 throw new ArgumentException(
274 "The recommendation collection cannot " +
275 "contain a null element.",
276 nameof(recommendations));
277 }
278
280 materialized
281 .OrderByDescending(
282 recommendation =>
283 recommendation.CompatibilityLevel)
284 .ThenByDescending(
285 recommendation =>
286 recommendation.Score)
287 .ThenBy(
288 recommendation =>
289 recommendation.MethodName,
290 StringComparer.OrdinalIgnoreCase)
291 .ThenBy(
292 recommendation =>
293 recommendation.MethodCode,
294 StringComparer.OrdinalIgnoreCase)
295 .ToArray();
296
297 AssignSequentialRanks(
298 ordered);
299
300 return ordered;
301 }
302
304 LotSizingInstance instance,
305 SolutionMethodDefinition methodDefinition,
306 string methodCatalogName,
307 string methodCatalogVersion,
308 string supplyChainFingerprint,
309 double recommendedScoreThreshold)
310 {
311 LotSizingProblemClassification classification =
312 instance.ProblemClassification;
313
314 LotSizingProblemFeatures features =
315 classification.Features;
316
317 var recommendation =
319 methodDefinition)
320 {
321 AdvisorVersion =
323
324 MethodCatalogName =
325 methodCatalogName?.Trim() ??
326 string.Empty,
327
328 MethodCatalogVersion =
329 methodCatalogVersion?.Trim() ??
330 string.Empty,
331
332 SupplyChainFingerprint =
333 supplyChainFingerprint
334 };
335
336 var evidence =
337 new List<MethodCompatibilityEvidence>();
338
339 var adaptations =
340 new List<string>();
341
342 var warnings =
343 new List<string>();
344
345 bool supportsAlternativeScope =
346 methodDefinition.SupportsRelaxations ||
347 methodDefinition.SupportsSubproblems;
348
349 AddClassificationStatusWarnings(
350 classification,
351 warnings);
352
353 EvaluateProblemFamily(
354 classification:
355 classification,
356
357 methodDefinition:
358 methodDefinition,
359
360 supportsAlternativeScope:
361 supportsAlternativeScope,
362
363 evidence:
364 evidence,
365
366 adaptations:
367 adaptations);
368
369 EvaluateProductStructure(
370 features:
371 features,
372
373 methodDefinition:
374 methodDefinition,
375
376 supportsAlternativeScope:
377 supportsAlternativeScope,
378
379 evidence:
380 evidence,
381
382 adaptations:
383 adaptations);
384
385 EvaluateRequiredFeatures(
386 features:
387 features,
388
389 methodDefinition:
390 methodDefinition,
391
392 evidence:
393 evidence,
394
395 warnings:
396 warnings);
397
398 EvaluateUnsupportedFeatures(
399 features:
400 features,
401
402 methodDefinition:
403 methodDefinition,
404
405 supportsAlternativeScope:
406 supportsAlternativeScope,
407
408 evidence:
409 evidence,
410
411 adaptations:
412 adaptations,
413
414 warnings:
415 warnings);
416
417 EvaluatePartiallySupportedFeatures(
418 features:
419 features,
420
421 methodDefinition:
422 methodDefinition,
423
424 evidence:
425 evidence,
426
427 adaptations:
428 adaptations,
429
430 warnings:
431 warnings);
432
433 EvaluateSupportedFeatures(
434 features:
435 features,
436
437 methodDefinition:
438 methodDefinition,
439
440 evidence:
441 evidence,
442
443 warnings:
444 warnings);
445
446 EvaluatePreferredFeatures(
447 features:
448 features,
449
450 methodDefinition:
451 methodDefinition,
452
453 evidence:
454 evidence,
455
456 warnings:
457 warnings);
458
459 EvaluateSizeLimits(
460 features:
461 features,
462
463 methodDefinition:
464 methodDefinition,
465
466 supportsAlternativeScope:
467 supportsAlternativeScope,
468
469 evidence:
470 evidence,
471
472 adaptations:
473 adaptations,
474
475 warnings:
476 warnings);
477
478 recommendation.ReplaceEvidence(
479 evidence);
480
481 recommendation.ReplaceRequiredAdaptations(
482 adaptations);
483
484 recommendation.ReplaceWarnings(
485 warnings);
486
487 recommendation.Scope =
488 DetermineScope(
489 features:
490 features,
491
492 methodDefinition:
493 methodDefinition,
494
495 recommendation:
496 recommendation,
497
498 adaptations:
499 adaptations);
500
501 recommendation.ScopeDescription =
502 CreateScopeDescription(
503 recommendation.Scope);
504
505 recommendation.UpdateCompatibilityFromEvidence(
506 recommendedScoreThreshold:
507 recommendedScoreThreshold,
508
509 updateEvaluationDate:
510 true);
511
512 recommendation.Summary =
513 CreateSummary(
514 recommendation,
515 classification);
516
517 return recommendation;
518 }
519
520 private static IReadOnlyList<SolutionMethodRecommendation>
522 IEnumerable<SolutionMethodRecommendation>
523 recommendations,
524 IEnumerable<SolutionMethodDefinition>
525 methodDefinitions)
526 {
527 Dictionary<string, int> priorityByMethodCode =
528 methodDefinitions
529 .Where(
530 method =>
531 method is not null &&
532 method.HasMethodCode)
533 .GroupBy(
534 method =>
535 method.MethodCode,
536 StringComparer.OrdinalIgnoreCase)
537 .ToDictionary(
538 group =>
539 group.Key,
540
541 group =>
542 group.First().Priority,
543
544 StringComparer.OrdinalIgnoreCase);
545
546 SolutionMethodRecommendation[] ordered =
547 recommendations
548 .OrderByDescending(
549 recommendation =>
550 recommendation.CompatibilityLevel)
551 .ThenByDescending(
552 recommendation =>
553 recommendation.Score)
554 .ThenByDescending(
555 recommendation =>
556 priorityByMethodCode.TryGetValue(
557 recommendation.MethodCode,
558 out int priority)
559 ? priority
560 : 0)
561 .ThenBy(
562 recommendation =>
563 recommendation.MethodName,
564 StringComparer.OrdinalIgnoreCase)
565 .ThenBy(
566 recommendation =>
567 recommendation.MethodCode,
568 StringComparer.OrdinalIgnoreCase)
569 .ToArray();
570
571 AssignSequentialRanks(
572 ordered);
573
574 return ordered;
575 }
576
577 private static void EvaluateProblemFamily(
578 LotSizingProblemClassification classification,
579 SolutionMethodDefinition methodDefinition,
580 bool supportsAlternativeScope,
581 ICollection<MethodCompatibilityEvidence> evidence,
582 ICollection<string> adaptations)
583 {
584 if (methodDefinition.SupportsAnyProblemFamily)
585 {
586 evidence.Add(
587 CreateEvidence(
588 criterionCode:
589 "FAMILY.ANY_SUPPORTED",
590
591 description:
592 "The method is independent from a " +
593 "specific problem-family code.",
594
595 isSatisfied:
596 true,
597
598 isRequired:
599 true,
600
601 isBlocking:
602 false,
603
604 expectedValue:
605 "any",
606
607 observedValue:
608 GetObservedProblemFamilyDescription(
609 classification),
610
611 weight:
612 1.5));
613
614 return;
615 }
616
617 IReadOnlyList<string> candidateCodes =
618 GetCandidateProblemTypeCodes(
619 classification);
620
621 if (candidateCodes.Count == 0)
622 {
623 bool supportsUnclassified =
624 methodDefinition
625 .SupportsUnclassifiedProblems;
626
627 evidence.Add(
628 CreateEvidence(
629 criterionCode:
630 "FAMILY.CLASSIFICATION_AVAILABLE",
631
632 description:
633 supportsUnclassified
634 ? "The method accepts problems " +
635 "without a recognized family."
636 : "The method requires a " +
637 "recognized supported problem " +
638 "family.",
639
640 isSatisfied:
641 supportsUnclassified,
642
643 isRequired:
644 true,
645
646 isBlocking:
647 !supportsUnclassified &&
648 !supportsAlternativeScope,
649
650 expectedValue:
651 supportsUnclassified
652 ? "classified or unclassified"
653 : string.Join(
654 ";",
655 methodDefinition
656 .SupportedProblemTypeCodes),
657
658 observedValue:
659 classification.Status,
660
661 weight:
662 2.0));
663
664 if (!supportsUnclassified &&
665 supportsAlternativeScope)
666 {
667 adaptations.Add(
668 "Identify a supported relaxation or " +
669 "subproblem before applying the method.");
670 }
671
672 return;
673 }
674
675 bool isAmbiguous =
676 classification.Status ==
677 ProblemClassificationStatus.Ambiguous ||
678 candidateCodes.Count > 1;
679
680 if (isAmbiguous &&
681 !methodDefinition
682 .SupportsAmbiguousClassifications)
683 {
684 evidence.Add(
685 CreateEvidence(
686 criterionCode:
687 "FAMILY.AMBIGUOUS_CLASSIFICATION",
688
689 description:
690 "The method definition does not " +
691 "accept an ambiguous problem-family " +
692 "classification.",
693
694 isSatisfied:
695 false,
696
697 isRequired:
698 true,
699
700 isBlocking:
701 !supportsAlternativeScope,
702
703 expectedValue:
704 "unambiguous classification",
705
706 observedValue:
707 string.Join(
708 ";",
709 candidateCodes),
710
711 weight:
712 1.5));
713
714 if (supportsAlternativeScope)
715 {
716 adaptations.Add(
717 "Resolve the family ambiguity or apply " +
718 "the method only to a supported " +
719 "subproblem.");
720 }
721 }
722
723 string[] supportedCandidateCodes =
724 candidateCodes
725 .Where(
726 methodDefinition
727 .SupportsProblemTypeCode)
728 .ToArray();
729
730 bool familySupported =
731 supportedCandidateCodes.Length > 0;
732
733 evidence.Add(
734 CreateEvidence(
735 criterionCode:
736 "FAMILY.SUPPORTED",
737
738 description:
739 familySupported
740 ? "The method supports at least one " +
741 "recognized problem family."
742 : "The method does not directly " +
743 "support the recognized problem " +
744 "family.",
745
746 isSatisfied:
747 familySupported,
748
749 isRequired:
750 true,
751
752 isBlocking:
753 !familySupported &&
754 !supportsAlternativeScope,
755
756 expectedValue:
757 string.Join(
758 ";",
759 methodDefinition
760 .SupportedProblemTypeCodes),
761
762 observedValue:
763 string.Join(
764 ";",
765 candidateCodes),
766
767 weight:
768 3.0));
769
770 if (!familySupported &&
771 supportsAlternativeScope)
772 {
773 adaptations.Add(
774 "Apply the method to a supported relaxation " +
775 "or subproblem rather than to the complete " +
776 "problem.");
777 }
778
779 string? preferredCandidateCode =
780 supportedCandidateCodes
781 .FirstOrDefault(
782 methodDefinition
783 .PrefersProblemTypeCode);
784
785 if (!string.IsNullOrWhiteSpace(
786 preferredCandidateCode))
787 {
788 evidence.Add(
789 CreateEvidence(
790 criterionCode:
791 "FAMILY.PREFERRED",
792
793 description:
794 "The recognized problem family is " +
795 "identified as particularly " +
796 "appropriate for the method.",
797
798 isSatisfied:
799 true,
800
801 isRequired:
802 false,
803
804 isBlocking:
805 false,
806
807 expectedValue:
808 string.Join(
809 ";",
810 methodDefinition
811 .PreferredProblemTypeCodes),
812
813 observedValue:
814 preferredCandidateCode,
815
816 weight:
817 1.5));
818 }
819 }
820
821 private static void EvaluateProductStructure(
822 LotSizingProblemFeatures features,
823 SolutionMethodDefinition methodDefinition,
824 bool supportsAlternativeScope,
825 ICollection<MethodCompatibilityEvidence> evidence,
826 ICollection<string> adaptations)
827 {
828 ProductStructureType observedType =
829 features.ProductStructureType;
830
831 if (methodDefinition.SupportsAnyProductStructure)
832 {
833 evidence.Add(
834 CreateEvidence(
835 criterionCode:
836 "STRUCTURE.ANY_SUPPORTED",
837
838 description:
839 "The method supports every valid " +
840 "product-structure type.",
841
842 isSatisfied:
843 observedType !=
844 ProductStructureType.Unknown,
845
846 isRequired:
847 true,
848
849 isBlocking:
850 observedType ==
851 ProductStructureType.Unknown,
852
853 expectedValue:
854 "any valid structure",
855
856 observedValue:
857 observedType,
858
859 weight:
860 2.0));
861
862 return;
863 }
864
865 bool structureSupported =
866 observedType !=
867 ProductStructureType.Unknown &&
868 methodDefinition
869 .SupportsProductStructureType(
870 observedType);
871
872 evidence.Add(
873 CreateEvidence(
874 criterionCode:
875 "STRUCTURE.SUPPORTED",
876
877 description:
878 structureSupported
879 ? "The method supports the detected " +
880 "product structure."
881 : "The method does not directly " +
882 "support the detected product " +
883 "structure.",
884
885 isSatisfied:
886 structureSupported,
887
888 isRequired:
889 true,
890
891 isBlocking:
892 !structureSupported &&
893 !supportsAlternativeScope,
894
895 expectedValue:
896 string.Join(
897 ";",
898 methodDefinition
899 .SupportedProductStructureTypes),
900
901 observedValue:
902 observedType,
903
904 weight:
905 2.5));
906
907 if (!structureSupported &&
908 supportsAlternativeScope)
909 {
910 adaptations.Add(
911 "Decompose or relax the product structure " +
912 "before applying the method.");
913 }
914 }
915
916 private static void EvaluateRequiredFeatures(
917 LotSizingProblemFeatures features,
918 SolutionMethodDefinition methodDefinition,
919 ICollection<MethodCompatibilityEvidence> evidence,
920 ICollection<string> warnings)
921 {
922 foreach (string featureCode
923 in DistinctCodes(
924 methodDefinition
925 .RequiredFeatureCodes))
926 {
927 if (!TryGetBooleanFeature(
928 features,
929 featureCode,
930 out bool observedValue))
931 {
932 evidence.Add(
933 CreateEvidence(
934 criterionCode:
935 CreateFeatureCriterionCode(
936 "REQUIRED",
937 featureCode),
938
939 description:
940 $"Required feature " +
941 $"'{featureCode}' could not be " +
942 "evaluated.",
943
944 isSatisfied:
945 false,
946
947 isRequired:
948 true,
949
950 isBlocking:
951 true,
952
953 expectedValue:
954 true,
955
956 observedValue:
957 "unavailable",
958
959 featureCode:
960 featureCode,
961
962 weight:
963 2.0));
964
965 warnings.Add(
966 $"Required feature '{featureCode}' " +
967 "could not be read from the feature " +
968 "profile.");
969
970 continue;
971 }
972
973 evidence.Add(
974 CreateEvidence(
975 criterionCode:
976 CreateFeatureCriterionCode(
977 "REQUIRED",
978 featureCode),
979
980 description:
981 observedValue
982 ? $"Required feature " +
983 $"'{featureCode}' is active."
984 : $"The method requires feature " +
985 $"'{featureCode}', but it is " +
986 "not active.",
987
988 isSatisfied:
989 observedValue,
990
991 isRequired:
992 true,
993
994 isBlocking:
995 true,
996
997 expectedValue:
998 true,
999
1000 observedValue:
1001 observedValue,
1002
1003 featureCode:
1004 featureCode,
1005
1006 weight:
1007 2.0));
1008 }
1009 }
1010
1011 private static void EvaluateUnsupportedFeatures(
1012 LotSizingProblemFeatures features,
1013 SolutionMethodDefinition methodDefinition,
1014 bool supportsAlternativeScope,
1015 ICollection<MethodCompatibilityEvidence> evidence,
1016 ICollection<string> adaptations,
1017 ICollection<string> warnings)
1018 {
1019 foreach (string featureCode
1020 in DistinctCodes(
1021 methodDefinition
1022 .UnsupportedFeatureCodes))
1023 {
1024 if (!TryGetBooleanFeature(
1025 features,
1026 featureCode,
1027 out bool observedValue))
1028 {
1029 warnings.Add(
1030 $"Unsupported feature criterion " +
1031 $"'{featureCode}' could not be read from " +
1032 "the feature profile.");
1033
1034 continue;
1035 }
1036
1037 bool criterionSatisfied =
1038 !observedValue;
1039
1040 evidence.Add(
1041 CreateEvidence(
1042 criterionCode:
1043 CreateFeatureCriterionCode(
1044 "UNSUPPORTED",
1045 featureCode),
1046
1047 description:
1048 criterionSatisfied
1049 ? $"Unsupported feature " +
1050 $"'{featureCode}' is absent."
1051 : $"Feature '{featureCode}' is " +
1052 "active but is not supported " +
1053 "by the method.",
1054
1055 isSatisfied:
1056 criterionSatisfied,
1057
1058 isRequired:
1059 true,
1060
1061 isBlocking:
1062 !criterionSatisfied &&
1063 !supportsAlternativeScope,
1064
1065 expectedValue:
1066 false,
1067
1068 observedValue:
1069 observedValue,
1070
1071 featureCode:
1072 featureCode,
1073
1074 weight:
1075 2.0));
1076
1077 if (!criterionSatisfied &&
1078 supportsAlternativeScope)
1079 {
1080 adaptations.Add(
1081 $"Remove, relax or separately handle " +
1082 $"feature '{featureCode}'.");
1083 }
1084 }
1085 }
1086
1087 private static void
1088 EvaluatePartiallySupportedFeatures(
1089 LotSizingProblemFeatures features,
1090 SolutionMethodDefinition methodDefinition,
1091 ICollection<MethodCompatibilityEvidence> evidence,
1092 ICollection<string> adaptations,
1093 ICollection<string> warnings)
1094 {
1095 foreach (string featureCode
1096 in DistinctCodes(
1097 methodDefinition
1098 .PartiallySupportedFeatureCodes))
1099 {
1100 if (!TryGetBooleanFeature(
1101 features,
1102 featureCode,
1103 out bool observedValue))
1104 {
1105 warnings.Add(
1106 $"Partially supported feature " +
1107 $"'{featureCode}' could not be read from " +
1108 "the feature profile.");
1109
1110 continue;
1111 }
1112
1113 if (!observedValue)
1114 {
1115 continue;
1116 }
1117
1118 evidence.Add(
1119 CreateEvidence(
1120 criterionCode:
1121 CreateFeatureCriterionCode(
1122 "PARTIAL",
1123 featureCode),
1124
1125 description:
1126 $"Feature '{featureCode}' is active " +
1127 "and requires an adaptation of the " +
1128 "method.",
1129
1130 isSatisfied:
1131 false,
1132
1133 isRequired:
1134 true,
1135
1136 isBlocking:
1137 false,
1138
1139 expectedValue:
1140 "absent or adapted",
1141
1142 observedValue:
1143 true,
1144
1145 featureCode:
1146 featureCode,
1147
1148 weight:
1149 1.5));
1150
1151 adaptations.Add(
1152 $"Use an adapted implementation for feature " +
1153 $"'{featureCode}'.");
1154 }
1155 }
1156
1157 private static void EvaluateSupportedFeatures(
1158 LotSizingProblemFeatures features,
1159 SolutionMethodDefinition methodDefinition,
1160 ICollection<MethodCompatibilityEvidence> evidence,
1161 ICollection<string> warnings)
1162 {
1163 HashSet<string> requiredFeatures =
1164 methodDefinition
1165 .RequiredFeatureCodes
1166 .ToHashSet(
1167 StringComparer.OrdinalIgnoreCase);
1168
1169 foreach (string featureCode
1170 in DistinctCodes(
1171 methodDefinition
1172 .SupportedFeatureCodes))
1173 {
1174 if (requiredFeatures.Contains(
1175 featureCode))
1176 {
1177 continue;
1178 }
1179
1180 if (!TryGetBooleanFeature(
1181 features,
1182 featureCode,
1183 out bool observedValue))
1184 {
1185 warnings.Add(
1186 $"Supported feature '{featureCode}' " +
1187 "could not be read from the feature " +
1188 "profile.");
1189
1190 continue;
1191 }
1192
1193 if (!observedValue)
1194 {
1195 continue;
1196 }
1197
1198 evidence.Add(
1199 CreateEvidence(
1200 criterionCode:
1201 CreateFeatureCriterionCode(
1202 "SUPPORTED",
1203 featureCode),
1204
1205 description:
1206 $"Active feature '{featureCode}' is " +
1207 "supported by the method.",
1208
1209 isSatisfied:
1210 true,
1211
1212 isRequired:
1213 false,
1214
1215 isBlocking:
1216 false,
1217
1218 expectedValue:
1219 true,
1220
1221 observedValue:
1222 true,
1223
1224 featureCode:
1225 featureCode,
1226
1227 weight:
1228 1.0));
1229 }
1230 }
1231
1232 private static void EvaluatePreferredFeatures(
1233 LotSizingProblemFeatures features,
1234 SolutionMethodDefinition methodDefinition,
1235 ICollection<MethodCompatibilityEvidence> evidence,
1236 ICollection<string> warnings)
1237 {
1238 foreach (string featureCode
1239 in DistinctCodes(
1240 methodDefinition
1241 .PreferredFeatureCodes))
1242 {
1243 if (!TryGetBooleanFeature(
1244 features,
1245 featureCode,
1246 out bool observedValue))
1247 {
1248 warnings.Add(
1249 $"Preferred feature '{featureCode}' " +
1250 "could not be read from the feature " +
1251 "profile.");
1252
1253 continue;
1254 }
1255
1256 evidence.Add(
1257 CreateEvidence(
1258 criterionCode:
1259 CreateFeatureCriterionCode(
1260 "PREFERRED",
1261 featureCode),
1262
1263 description:
1264 observedValue
1265 ? $"Preferred feature " +
1266 $"'{featureCode}' is active."
1267 : $"Preferred feature " +
1268 $"'{featureCode}' is not active.",
1269
1270 isSatisfied:
1271 observedValue,
1272
1273 isRequired:
1274 false,
1275
1276 isBlocking:
1277 false,
1278
1279 expectedValue:
1280 true,
1281
1282 observedValue:
1283 observedValue,
1284
1285 featureCode:
1286 featureCode,
1287
1288 weight:
1289 0.75));
1290 }
1291 }
1292
1293 private static void EvaluateSizeLimits(
1294 LotSizingProblemFeatures features,
1295 SolutionMethodDefinition methodDefinition,
1296 bool supportsAlternativeScope,
1297 ICollection<MethodCompatibilityEvidence> evidence,
1298 ICollection<string> adaptations,
1299 ICollection<string> warnings)
1300 {
1301 EvaluateHardLimit(
1302 features,
1303 methodDefinition.MaximumItemCount,
1304 "LIMIT.MAX_ITEMS",
1305 "item count",
1306 supportsAlternativeScope,
1307 evidence,
1308 adaptations,
1309 warnings,
1310 "ItemCount");
1311
1312 EvaluateHardLimit(
1313 features,
1314 methodDefinition.MaximumPlanningHorizon,
1315 "LIMIT.MAX_HORIZON",
1316 "planning horizon",
1317 supportsAlternativeScope,
1318 evidence,
1319 adaptations,
1320 warnings,
1321 "PlanningHorizon");
1322
1323 EvaluateHardLimit(
1324 features,
1325 methodDefinition.MaximumPlantCount,
1326 "LIMIT.MAX_PLANTS",
1327 "plant count",
1328 supportsAlternativeScope,
1329 evidence,
1330 adaptations,
1331 warnings,
1332 "PlantCount");
1333
1334 EvaluateHardLimit(
1335 features,
1336 methodDefinition.MaximumWorkCenterCount,
1337 "LIMIT.MAX_WORK_CENTERS",
1338 "work-center count",
1339 supportsAlternativeScope,
1340 evidence,
1341 adaptations,
1342 warnings,
1343 "WorkCenterCount");
1344
1345 EvaluateHardLimit(
1346 features,
1347 methodDefinition.MaximumWarehouseCount,
1348 "LIMIT.MAX_WAREHOUSES",
1349 "warehouse count",
1350 supportsAlternativeScope,
1351 evidence,
1352 adaptations,
1353 warnings,
1354 "WarehouseCount");
1355
1356 EvaluateHardLimit(
1357 features,
1358 methodDefinition.MaximumSupplierCount,
1359 "LIMIT.MAX_SUPPLIERS",
1360 "supplier count",
1361 supportsAlternativeScope,
1362 evidence,
1363 adaptations,
1364 warnings,
1365 "SupplierCount");
1366
1367 EvaluateHardLimit(
1368 features,
1369 methodDefinition.MaximumTransportResourceCount,
1370 "LIMIT.MAX_TRANSPORT_RESOURCES",
1371 "transport-resource count",
1372 supportsAlternativeScope,
1373 evidence,
1374 adaptations,
1375 warnings,
1376 "TransportResourceCount");
1377
1378 EvaluateHardLimit(
1379 features,
1380 methodDefinition
1381 .MaximumBillOfMaterialsRelationshipCount,
1382 "LIMIT.MAX_BOM_RELATIONSHIPS",
1383 "bill-of-materials relationship count",
1384 supportsAlternativeScope,
1385 evidence,
1386 adaptations,
1387 warnings,
1388 "ProductStructureRelationshipCount",
1389 "BillOfMaterialsRelationshipCount",
1390 "ComponentRequirementCount");
1391
1392 EvaluateHardLimit(
1393 features,
1394 methodDefinition.MaximumProductStructureDepth,
1395 "LIMIT.MAX_STRUCTURE_DEPTH",
1396 "product-structure depth",
1397 supportsAlternativeScope,
1398 evidence,
1399 adaptations,
1400 warnings,
1401 "ProductStructureDepth",
1402 "MaximumProductStructureDepth",
1403 "BillOfMaterialsDepth");
1404
1405 EvaluateRecommendedLimit(
1406 features,
1407 methodDefinition.RecommendedMaximumItemCount,
1408 "LIMIT.RECOMMENDED_MAX_ITEMS",
1409 "recommended item count",
1410 evidence,
1411 warnings,
1412 "ItemCount");
1413
1414 EvaluateRecommendedLimit(
1415 features,
1416 methodDefinition
1417 .RecommendedMaximumPlanningHorizon,
1418 "LIMIT.RECOMMENDED_MAX_HORIZON",
1419 "recommended planning horizon",
1420 evidence,
1421 warnings,
1422 "PlanningHorizon");
1423
1424 EvaluateRecommendedLimit(
1425 features,
1426 methodDefinition
1427 .RecommendedMaximumBillOfMaterialsRelationshipCount,
1428 "LIMIT.RECOMMENDED_MAX_BOM_RELATIONSHIPS",
1429 "recommended bill-of-materials relationship count",
1430 evidence,
1431 warnings,
1432 "ProductStructureRelationshipCount",
1433 "BillOfMaterialsRelationshipCount",
1434 "ComponentRequirementCount");
1435
1436 EvaluateRecommendedLimit(
1437 features,
1438 methodDefinition
1439 .RecommendedMaximumProductStructureDepth,
1440 "LIMIT.RECOMMENDED_MAX_STRUCTURE_DEPTH",
1441 "recommended product-structure depth",
1442 evidence,
1443 warnings,
1444 "ProductStructureDepth",
1445 "MaximumProductStructureDepth",
1446 "BillOfMaterialsDepth");
1447 }
1448
1449 private static void EvaluateHardLimit(
1450 LotSizingProblemFeatures features,
1451 int? maximumValue,
1452 string criterionCode,
1453 string description,
1454 bool supportsAlternativeScope,
1455 ICollection<MethodCompatibilityEvidence> evidence,
1456 ICollection<string> adaptations,
1457 ICollection<string> warnings,
1458 params string[] featurePropertyNames)
1459 {
1460 if (!maximumValue.HasValue)
1461 {
1462 return;
1463 }
1464
1465 if (!TryGetIntegerFeature(
1466 features,
1467 featurePropertyNames,
1468 out int observedValue,
1469 out string resolvedFeatureCode))
1470 {
1471 warnings.Add(
1472 $"Hard limit '{criterionCode}' could not be " +
1473 "evaluated because the corresponding " +
1474 "feature value is unavailable.");
1475
1476 return;
1477 }
1478
1479 bool limitSatisfied =
1480 observedValue <=
1481 maximumValue.Value;
1482
1483 evidence.Add(
1484 CreateEvidence(
1485 criterionCode:
1486 criterionCode,
1487
1488 description:
1489 limitSatisfied
1490 ? $"The {description} is within the " +
1491 "method's hard limit."
1492 : $"The {description} exceeds the " +
1493 "method's hard limit.",
1494
1495 isSatisfied:
1496 limitSatisfied,
1497
1498 isRequired:
1499 true,
1500
1501 isBlocking:
1502 !limitSatisfied &&
1503 !supportsAlternativeScope,
1504
1505 expectedValue:
1506 $"<= {maximumValue.Value.ToString(
1507 CultureInfo.InvariantCulture)}",
1508
1509 observedValue:
1510 observedValue,
1511
1512 featureCode:
1513 resolvedFeatureCode,
1514
1515 weight:
1516 2.0));
1517
1518 if (!limitSatisfied &&
1519 supportsAlternativeScope)
1520 {
1521 adaptations.Add(
1522 $"Decompose the instance so that the " +
1523 $"{description} does not exceed " +
1524 $"{maximumValue.Value.ToString(
1525 CultureInfo.InvariantCulture)}.");
1526 }
1527 }
1528
1529 private static void EvaluateRecommendedLimit(
1530 LotSizingProblemFeatures features,
1531 int? maximumValue,
1532 string criterionCode,
1533 string description,
1534 ICollection<MethodCompatibilityEvidence> evidence,
1535 ICollection<string> warnings,
1536 params string[] featurePropertyNames)
1537 {
1538 if (!maximumValue.HasValue)
1539 {
1540 return;
1541 }
1542
1543 if (!TryGetIntegerFeature(
1544 features,
1545 featurePropertyNames,
1546 out int observedValue,
1547 out string resolvedFeatureCode))
1548 {
1549 warnings.Add(
1550 $"Recommended limit '{criterionCode}' could " +
1551 "not be evaluated because the corresponding " +
1552 "feature value is unavailable.");
1553
1554 return;
1555 }
1556
1557 bool limitSatisfied =
1558 observedValue <=
1559 maximumValue.Value;
1560
1561 evidence.Add(
1562 CreateEvidence(
1563 criterionCode:
1564 criterionCode,
1565
1566 description:
1567 limitSatisfied
1568 ? $"The {description} is within the " +
1569 "effective operating range."
1570 : $"The {description} exceeds the " +
1571 "method's recommended operating " +
1572 "range.",
1573
1574 isSatisfied:
1575 limitSatisfied,
1576
1577 isRequired:
1578 false,
1579
1580 isBlocking:
1581 false,
1582
1583 expectedValue:
1584 $"<= {maximumValue.Value.ToString(
1585 CultureInfo.InvariantCulture)}",
1586
1587 observedValue:
1588 observedValue,
1589
1590 featureCode:
1591 resolvedFeatureCode,
1592
1593 weight:
1594 0.75));
1595
1596 if (!limitSatisfied)
1597 {
1598 warnings.Add(
1599 $"The {description} exceeds the recommended " +
1600 $"maximum of {maximumValue.Value.ToString(
1601 CultureInfo.InvariantCulture)} for this " +
1602 "method.");
1603 }
1604 }
1605
1606 private static ProblemClassificationScope DetermineScope(
1607 LotSizingProblemFeatures features,
1608 SolutionMethodDefinition methodDefinition,
1609 SolutionMethodRecommendation recommendation,
1610 ICollection<string> adaptations)
1611 {
1612 bool directCompleteProblemUse =
1613 methodDefinition.SupportsCompleteProblems &&
1614 !recommendation.HasBlockingMismatches &&
1615 !recommendation.HasRequiredMismatches &&
1616 adaptations.Count == 0;
1617
1618 if (directCompleteProblemUse)
1619 {
1620 return ProblemClassificationScope.CompleteProblem;
1621 }
1622
1623 if (recommendation.HasBlockingMismatches)
1624 {
1625 return methodDefinition.SupportsCompleteProblems
1626 ? ProblemClassificationScope.CompleteProblem
1627 : ProblemClassificationScope.CustomSubset;
1628 }
1629
1630 if (methodDefinition.SupportsRelaxations)
1631 {
1632 if (!adaptations.Contains(
1633 "Apply the method to a relaxation of the " +
1634 "complete problem.",
1635 StringComparer.OrdinalIgnoreCase))
1636 {
1637 adaptations.Add(
1638 "Apply the method to a relaxation of the " +
1639 "complete problem.");
1640 }
1641
1643 .ProblemRelaxation;
1644 }
1645
1646 if (methodDefinition.SupportsSubproblems)
1647 {
1648 if (!adaptations.Contains(
1649 "Apply the method to suitable " +
1650 "subproblems.",
1651 StringComparer.OrdinalIgnoreCase))
1652 {
1653 adaptations.Add(
1654 "Apply the method to suitable " +
1655 "subproblems.");
1656 }
1657
1658 return features.IsMultiItem
1659 ? ProblemClassificationScope.SingleItem
1660 : ProblemClassificationScope.CustomSubset;
1661 }
1662
1663 return methodDefinition.SupportsCompleteProblems
1664 ? ProblemClassificationScope.CompleteProblem
1666 }
1667
1668 private static string CreateScopeDescription(
1670 {
1671 return scope switch
1672 {
1673 ProblemClassificationScope.CompleteProblem =>
1674 "Complete problem",
1675
1676 ProblemClassificationScope.ProblemRelaxation =>
1677 "Relaxation of the complete problem",
1678
1679 ProblemClassificationScope.SingleItem =>
1680 "Single-item subproblems",
1681
1682 ProblemClassificationScope.ItemSubset =>
1683 "Subset of items",
1684
1685 ProblemClassificationScope.Plant =>
1686 "Plant-level subproblem",
1687
1688 ProblemClassificationScope.WorkCenter =>
1689 "Work-center subproblem",
1690
1691 ProblemClassificationScope.Warehouse =>
1692 "Warehouse subproblem",
1693
1694 ProblemClassificationScope.TransportResource =>
1695 "Transport-resource subproblem",
1696
1697 ProblemClassificationScope.TransportLane =>
1698 "Transport-lane subproblem",
1699
1700 ProblemClassificationScope.Supplier =>
1701 "Supplier subproblem",
1702
1703 ProblemClassificationScope.DistributionCenter =>
1704 "Distribution-center subproblem",
1705
1706 ProblemClassificationScope.SupplyChainSegment =>
1707 "Supply-chain segment",
1708
1709 ProblemClassificationScope.CustomSubset =>
1710 "Custom problem subset",
1711
1712 _ =>
1713 string.Empty
1714 };
1715 }
1716
1717 private static string CreateSummary(
1718 SolutionMethodRecommendation recommendation,
1719 LotSizingProblemClassification classification)
1720 {
1721 string familyDescription =
1722 classification.HasPrimaryProblemType
1723 ? $" for family " +
1724 $"'{classification.PrimaryProblemTypeCode}'"
1725 : string.Empty;
1726
1727 return recommendation.CompatibilityLevel switch
1728 {
1729 MethodCompatibilityLevel.Recommended =>
1730 "The method is recommended for the complete " +
1731 $"problem{familyDescription}.",
1732
1733 MethodCompatibilityLevel.Compatible =>
1734 "The method is compatible with the complete " +
1735 $"problem{familyDescription}.",
1736
1737 MethodCompatibilityLevel.PartiallyCompatible =>
1738 $"The method is partially compatible and " +
1739 $"should be applied to: " +
1740 $"{recommendation.ScopeDescription}.",
1741
1742 MethodCompatibilityLevel.Incompatible =>
1743 CreateIncompatibilitySummary(
1744 recommendation),
1745
1746 _ =>
1747 "The method compatibility has not been " +
1748 "evaluated."
1749 };
1750 }
1751
1752 private static string CreateIncompatibilitySummary(
1753 SolutionMethodRecommendation recommendation)
1754 {
1755 MethodCompatibilityEvidence? principalMismatch =
1756 recommendation.Evidence
1757 .FirstOrDefault(
1758 item =>
1759 item is not null &&
1760 item.IsBlockingMismatch)
1761 ??
1762 recommendation.Evidence
1763 .FirstOrDefault(
1764 item =>
1765 item is not null &&
1766 item.IsRequiredMismatch);
1767
1768 return principalMismatch is null
1769 ? "The method is incompatible with the complete " +
1770 "problem."
1771 : "The method is incompatible with the complete " +
1772 $"problem: {principalMismatch.Description}";
1773 }
1774
1775 private static void AddClassificationStatusWarnings(
1776 LotSizingProblemClassification classification,
1777 ICollection<string> warnings)
1778 {
1779 switch (classification.Status)
1780 {
1781 case ProblemClassificationStatus.NotAnalyzed:
1782 warnings.Add(
1783 "The problem has not been automatically " +
1784 "classified.");
1785 break;
1786
1788 .PartiallyClassified:
1789 warnings.Add(
1790 "The problem classification is partial.");
1791 break;
1792
1793 case ProblemClassificationStatus.Ambiguous:
1794 warnings.Add(
1795 "The problem-family classification is " +
1796 "ambiguous.");
1797 break;
1798
1799 case ProblemClassificationStatus.Unclassified:
1800 warnings.Add(
1801 "No known problem family was identified.");
1802 break;
1803
1804 case ProblemClassificationStatus.Invalid:
1805 warnings.Add(
1806 "The problem classification is invalid.");
1807 break;
1808
1809 case ProblemClassificationStatus.Outdated:
1810 warnings.Add(
1811 "The problem classification is outdated.");
1812 break;
1813 }
1814
1815 foreach (string classificationWarning
1816 in classification.Warnings)
1817 {
1818 if (!string.IsNullOrWhiteSpace(
1819 classificationWarning))
1820 {
1821 warnings.Add(
1822 "Classifier: " +
1823 classificationWarning.Trim());
1824 }
1825 }
1826 }
1827
1828 private static IReadOnlyList<string>
1829 GetCandidateProblemTypeCodes(
1830 LotSizingProblemClassification classification)
1831 {
1832 if (classification.HasPrimaryProblemType)
1833 {
1834 return new[]
1835 {
1836 classification.PrimaryProblemTypeCode
1837 };
1838 }
1839
1840 return classification.Matches
1841 .Where(
1842 match =>
1843 match is not null &&
1844 match.IsDirectMatch &&
1845 match.AppliesToCompleteProblem &&
1846 !string.IsNullOrWhiteSpace(
1847 match.ProblemTypeCode))
1848 .Select(
1849 match =>
1850 match.ProblemTypeCode.Trim())
1851 .Distinct(
1852 StringComparer.OrdinalIgnoreCase)
1853 .OrderBy(
1854 code =>
1855 code,
1856 StringComparer.OrdinalIgnoreCase)
1857 .ToArray();
1858 }
1859
1860 private static string
1861 GetObservedProblemFamilyDescription(
1862 LotSizingProblemClassification classification)
1863 {
1864 IReadOnlyList<string> codes =
1865 GetCandidateProblemTypeCodes(
1866 classification);
1867
1868 return codes.Count > 0
1869 ? string.Join(
1870 ";",
1871 codes)
1872 : classification.Status.ToString();
1873 }
1874
1875 private static MethodCompatibilityEvidence
1876 CreateEvidence(
1877 string criterionCode,
1878 string description,
1879 bool isSatisfied,
1880 bool isRequired,
1881 bool isBlocking,
1882 object? expectedValue,
1883 object? observedValue,
1884 string featureCode = "",
1885 double weight = 1.0)
1886 {
1887 var evidence =
1888 new MethodCompatibilityEvidence(
1889 criterionCode:
1890 criterionCode,
1891
1892 description:
1893 description,
1894
1895 isSatisfied:
1896 isSatisfied,
1897
1898 isRequired:
1899 isRequired,
1900
1901 isBlocking:
1902 isBlocking,
1903
1904 weight:
1905 weight)
1906 {
1907 FeatureCode =
1908 featureCode?.Trim() ??
1909 string.Empty
1910 };
1911
1912 evidence.SetComparedValues(
1913 expectedValue,
1914 observedValue);
1915
1916 return evidence;
1917 }
1918
1919 private static bool TryGetBooleanFeature(
1920 LotSizingProblemFeatures features,
1921 string featureCode,
1922 out bool value)
1923 {
1924 value =
1925 false;
1926
1927 if (string.IsNullOrWhiteSpace(
1928 featureCode))
1929 {
1930 return false;
1931 }
1932
1933 PropertyInfo? property =
1934 typeof(LotSizingProblemFeatures)
1935 .GetProperty(
1936 featureCode.Trim(),
1937 BindingFlags.Instance |
1938 BindingFlags.Public |
1939 BindingFlags.IgnoreCase);
1940
1941 if (property is null ||
1942 !property.CanRead ||
1943 property.GetIndexParameters().Length != 0 ||
1944 property.PropertyType != typeof(bool))
1945 {
1946 return false;
1947 }
1948
1949 object? propertyValue =
1950 property.GetValue(
1951 features);
1952
1953 if (propertyValue is not bool booleanValue)
1954 {
1955 return false;
1956 }
1957
1958 value =
1959 booleanValue;
1960
1961 return true;
1962 }
1963
1964 private static bool TryGetIntegerFeature(
1965 LotSizingProblemFeatures features,
1966 IEnumerable<string> candidatePropertyNames,
1967 out int value,
1968 out string resolvedFeatureCode)
1969 {
1970 value =
1971 0;
1972
1973 resolvedFeatureCode =
1974 string.Empty;
1975
1976 foreach (string candidatePropertyName
1977 in candidatePropertyNames)
1978 {
1979 if (string.IsNullOrWhiteSpace(
1980 candidatePropertyName))
1981 {
1982 continue;
1983 }
1984
1985 PropertyInfo? property =
1986 typeof(LotSizingProblemFeatures)
1987 .GetProperty(
1988 candidatePropertyName.Trim(),
1989 BindingFlags.Instance |
1990 BindingFlags.Public |
1991 BindingFlags.IgnoreCase);
1992
1993 if (property is null ||
1994 !property.CanRead ||
1995 property.GetIndexParameters().Length != 0)
1996 {
1997 continue;
1998 }
1999
2000 object? rawValue =
2001 property.GetValue(
2002 features);
2003
2004 if (!TryConvertToInt32(
2005 rawValue,
2006 out int convertedValue))
2007 {
2008 continue;
2009 }
2010
2011 value =
2012 convertedValue;
2013
2014 resolvedFeatureCode =
2015 property.Name;
2016
2017 return true;
2018 }
2019
2020 return false;
2021 }
2022
2023 private static bool TryConvertToInt32(
2024 object? value,
2025 out int convertedValue)
2026 {
2027 convertedValue =
2028 0;
2029
2030 switch (value)
2031 {
2032 case int integerValue:
2033 convertedValue =
2034 integerValue;
2035 return true;
2036
2037 case short shortValue:
2038 convertedValue =
2039 shortValue;
2040 return true;
2041
2042 case byte byteValue:
2043 convertedValue =
2044 byteValue;
2045 return true;
2046
2047 case long longValue
2048 when longValue >= int.MinValue &&
2049 longValue <= int.MaxValue:
2050 convertedValue =
2051 (int)longValue;
2052 return true;
2053
2054 case uint unsignedIntegerValue
2055 when unsignedIntegerValue <= int.MaxValue:
2056 convertedValue =
2057 (int)unsignedIntegerValue;
2058 return true;
2059
2060 default:
2061 return false;
2062 }
2063 }
2064
2065 private static IReadOnlyList<string> DistinctCodes(
2066 IEnumerable<string> codes)
2067 {
2068 return codes
2069 .Where(
2070 code =>
2071 !string.IsNullOrWhiteSpace(code))
2072 .Select(
2073 code =>
2074 code.Trim())
2075 .Distinct(
2076 StringComparer.OrdinalIgnoreCase)
2077 .OrderBy(
2078 code =>
2079 code,
2080 StringComparer.OrdinalIgnoreCase)
2081 .ToArray();
2082 }
2083
2084 private static string CreateFeatureCriterionCode(
2085 string category,
2086 string featureCode)
2087 {
2088 char[] normalizedCharacters =
2089 featureCode
2090 .Trim()
2091 .ToUpperInvariant()
2092 .Select(
2093 character =>
2094 char.IsLetterOrDigit(character)
2095 ? character
2096 : '_')
2097 .ToArray();
2098
2099 return
2100 $"FEATURE.{category.ToUpperInvariant()}." +
2101 new string(
2102 normalizedCharacters);
2103 }
2104
2105 private static void AssignSequentialRanks(
2106 IReadOnlyList<SolutionMethodRecommendation>
2107 recommendations)
2108 {
2109 for (int index = 0;
2110 index < recommendations.Count;
2111 index++)
2112 {
2113 recommendations[index].Rank =
2114 index + 1;
2115 }
2116 }
2117
2118 private static void
2119 ValidateRecommendedScoreThreshold(
2120 double recommendedScoreThreshold)
2121 {
2122 if (!double.IsFinite(
2123 recommendedScoreThreshold) ||
2124 recommendedScoreThreshold < 0.0 ||
2125 recommendedScoreThreshold > 1.0)
2126 {
2127 throw new ArgumentOutOfRangeException(
2128 nameof(recommendedScoreThreshold),
2129 recommendedScoreThreshold,
2130 "The recommended-score threshold must be " +
2131 "finite and lie between zero and one.");
2132 }
2133 }
2134}
Stores the persistent result of the automatic classification of a lot-sizing problem instance.
LotSizingProblemFeatures Features
Gets or sets the factual lot-sizing problem features used during classification.
List< string > Warnings
Gets the warnings produced during feature extraction or problem classification.
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.
Describes the factual structural and modeling features detected in a lot-sizing supply-chain instance...
ProductStructureType ProductStructureType
Gets or sets the detected product-structure category.
Creates lot-sizing problem instances and initializes their automatically derived metadata.
static string ComputeSupplyChainFingerprint(SupplyChain supplyChain)
Calculates a SHA-256 fingerprint of a supply-chain object using its compact XML serialization.
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.
SupplyChain SupplyChain
Gets or sets the supply-chain model represented by the instance.
Evaluates and ranks solution methods for lot-sizing problem instances.
static IReadOnlyList< SolutionMethodRecommendation > Recommend(LotSizingInstance instance, SolutionMethodCatalog catalog, double recommendedScoreThreshold=DefaultRecommendedScoreThreshold, bool includeIncompatibleMethods=true)
Evaluates and ranks every usable method contained in a solution-method catalog.
static IReadOnlyList< SolutionMethodRecommendation > RankRecommendations(IEnumerable< SolutionMethodRecommendation > recommendations)
Ranks an existing collection of method recommendations.
const string CurrentVersion
Gets the current version of the solution-method advisor evaluation semantics.
const double DefaultRecommendedScoreThreshold
Gets the default score above which a directly compatible method may be marked as recommended.
static SolutionMethodRecommendation EvaluateMethod(LotSizingInstance instance, SolutionMethodDefinition methodDefinition, string methodCatalogName="", string methodCatalogVersion="", double recommendedScoreThreshold=DefaultRecommendedScoreThreshold)
Evaluates one solution method for a lot-sizing problem instance.
Represents an extensible catalog of solution methods that may be evaluated for lot-sizing problem ins...
IReadOnlyList< SolutionMethodDefinition > GetMethodsForEvaluation()
Gets the method definitions that can be evaluated by the solution-method advisor.
void EnsureValid()
Validates the catalog and throws an exception when at least one error is found.
Describes the applicability, capabilities and limitations of a solution method for lot-sizing problem...
bool SupportsSubproblems
Gets or sets a value indicating whether the method can solve subproblems extracted from a complete in...
void EnsureValid()
Validates the method definition and throws an exception when at least one error is found.
Represents the evaluated compatibility of one solution method with a specific lot-sizing problem inst...
bool IsIncompatible
Gets a value indicating whether the method is incompatible with the complete problem.
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.