LotSizingDataModel.Instance 2.0.1
Lot-sizing instance representation, descriptors and problem characterization.
Loading...
Searching...
No Matches
SolutionMethodRecommendation.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.Linq;
5using System.Xml.Serialization;
6using LotSizingDataModel.Core.Common;
8using LotSizingDataModel.Solution.Common;
9
11
12/// <summary>
13/// Represents the evaluated compatibility of one solution
14/// method with a specific lot-sizing problem instance.
15/// </summary>
16/// <remarks>
17/// A recommendation records:
18/// <list type="bullet">
19/// <item>
20/// <description>
21/// the evaluated solution method;
22/// </description>
23/// </item>
24/// <item>
25/// <description>
26/// the compatibility level and numerical score;
27/// </description>
28/// </item>
29/// <item>
30/// <description>
31/// the scope to which the method can be applied;
32/// </description>
33/// </item>
34/// <item>
35/// <description>
36/// evidence supporting or limiting compatibility;
37/// </description>
38/// </item>
39/// <item>
40/// <description>
41/// adaptations required before the method can be used;
42/// </description>
43/// </item>
44/// <item>
45/// <description>
46/// the catalog, advisor and supply-chain versions used during
47/// evaluation.
48/// </description>
49/// </item>
50/// </list>
51///
52/// The recommendation describes technical compatibility. It
53/// does not guarantee that the method will outperform other
54/// compatible methods.
55/// </remarks>
56[Serializable]
57[XmlType(TypeName = "solutionMethodRecommendation")]
58public sealed class SolutionMethodRecommendation : ModelObject
59{
60 private string _methodCode =
61 string.Empty;
62
63 private string _methodName =
64 string.Empty;
65
66 private string _methodVersion =
67 string.Empty;
68
69 private SolutionMethodKind _methodKind =
70 default;
71
72 private MethodCompatibilityLevel _compatibilityLevel =
73 MethodCompatibilityLevel.NotEvaluated;
74
75 private ProblemClassificationScope _scope =
77
78 private string _scopeDescription =
79 string.Empty;
80
81 private double _score;
82
83 private int? _rank;
84
85 private DateTime? _evaluatedAtUtc;
86
87 private string _advisorVersion =
88 string.Empty;
89
90 private string _methodCatalogName =
91 string.Empty;
92
93 private string _methodCatalogVersion =
94 string.Empty;
95
96 private string _supplyChainFingerprint =
97 string.Empty;
98
99 private string _summary =
100 string.Empty;
101
102 private string _comment =
103 string.Empty;
104
105 /// <summary>
106 /// Initializes an empty solution-method recommendation.
107 /// </summary>
108 /// <remarks>
109 /// This constructor is required for XML serialization.
110 /// </remarks>
112 {
113 }
114
115 /// <summary>
116 /// Initializes a recommendation for a solution method.
117 /// </summary>
118 /// <param name="methodCode">
119 /// Stable code identifying the method.
120 /// </param>
121 /// <param name="methodName">
122 /// Human-readable method name.
123 /// </param>
124 /// <param name="methodKind">
125 /// General category of the solution method.
126 /// </param>
127 /// <exception cref="ArgumentException">
128 /// Thrown when <paramref name="methodCode"/> or
129 /// <paramref name="methodName"/> is empty.
130 /// </exception>
132 string methodCode,
133 string methodName,
134 SolutionMethodKind methodKind)
135 {
136 if (string.IsNullOrWhiteSpace(methodCode))
137 {
138 throw new ArgumentException(
139 "A solution-method code is required.",
140 nameof(methodCode));
141 }
142
143 if (string.IsNullOrWhiteSpace(methodName))
144 {
145 throw new ArgumentException(
146 "A solution-method name is required.",
147 nameof(methodName));
148 }
149
150 MethodCode =
151 methodCode;
152
153 MethodName =
154 methodName;
155
156 MethodKind =
157 methodKind;
158 }
159
160 /// <summary>
161 /// Initializes a recommendation from a method definition.
162 /// </summary>
163 /// <param name="methodDefinition">
164 /// Method definition to evaluate.
165 /// </param>
166 /// <exception cref="ArgumentNullException">
167 /// Thrown when <paramref name="methodDefinition"/> is
168 /// <see langword="null"/>.
169 /// </exception>
171 SolutionMethodDefinition methodDefinition)
172 {
173 ArgumentNullException.ThrowIfNull(
174 methodDefinition);
175
176 MethodCode =
177 methodDefinition.MethodCode;
178
179 MethodName =
180 methodDefinition.Name;
181
183 methodDefinition.MethodVersion;
184
185 MethodKind =
186 methodDefinition.MethodKind;
187 }
188
189 /// <summary>
190 /// Gets or sets the stable code identifying the evaluated
191 /// solution method.
192 /// </summary>
193 [XmlAttribute("methodCode")]
194 public string MethodCode
195 {
196 get => _methodCode;
197 set
198 {
199 if (SetProperty(
200 ref _methodCode,
201 NormalizeCode(value)))
202 {
203 OnPropertyChanged(
204 nameof(HasMethodCode));
205
206 OnPropertyChanged(
207 nameof(IsValidRecommendation));
208 }
209 }
210 }
211
212 /// <summary>
213 /// Gets or sets the human-readable name of the evaluated
214 /// solution method.
215 /// </summary>
216 [XmlAttribute("methodName")]
217 public string MethodName
218 {
219 get => _methodName;
220 set
221 {
222 if (SetProperty(
223 ref _methodName,
224 value?.Trim() ?? string.Empty))
225 {
226 OnPropertyChanged(
227 nameof(HasMethodName));
228
229 OnPropertyChanged(
230 nameof(IsValidRecommendation));
231 }
232 }
233 }
234
235 /// <summary>
236 /// Gets or sets the version of the evaluated method
237 /// definition.
238 /// </summary>
239 [XmlAttribute("methodVersion")]
240 public string MethodVersion
241 {
242 get => _methodVersion;
243 set
244 {
245 if (SetProperty(
246 ref _methodVersion,
247 value?.Trim() ?? string.Empty))
248 {
249 OnPropertyChanged(
250 nameof(HasMethodVersion));
251 }
252 }
253 }
254
255 /// <summary>
256 /// Gets or sets the general category of the evaluated
257 /// solution method.
258 /// </summary>
259 [XmlAttribute("methodKind")]
260 public SolutionMethodKind MethodKind
261 {
262 get => _methodKind;
263 set => SetProperty(
264 ref _methodKind,
265 value);
266 }
267
268 /// <summary>
269 /// Gets or sets the evaluated compatibility level.
270 /// </summary>
271 [XmlAttribute("compatibilityLevel")]
273 {
274 get => _compatibilityLevel;
275 set
276 {
277 if (SetProperty(
278 ref _compatibilityLevel,
279 value))
280 {
281 NotifyCompatibilityProperties();
282 }
283 }
284 }
285
286 /// <summary>
287 /// Gets or sets the problem scope to which the method can
288 /// be applied.
289 /// </summary>
290 /// <remarks>
291 /// A method may be applicable to the complete problem, a
292 /// relaxation, an item subset or another subproblem.
293 /// </remarks>
294 [XmlAttribute("scope")]
296 {
297 get => _scope;
298 set
299 {
300 if (SetProperty(
301 ref _scope,
302 value))
303 {
304 NotifyScopeProperties();
305 }
306 }
307 }
308
309 /// <summary>
310 /// Gets or sets a human-readable explanation of the
311 /// applicable problem scope.
312 /// </summary>
313 /// <remarks>
314 /// Examples include:
315 /// <list type="bullet">
316 /// <item>
317 /// <description>
318 /// <c>Complete problem</c>;
319 /// </description>
320 /// </item>
321 /// <item>
322 /// <description>
323 /// <c>Single-item subproblems after capacity
324 /// relaxation</c>;
325 /// </description>
326 /// </item>
327 /// <item>
328 /// <description>
329 /// <c>Production-planning subproblem without
330 /// transportation</c>.
331 /// </description>
332 /// </item>
333 /// </list>
334 /// </remarks>
335 [XmlElement("scopeDescription")]
336 public string ScopeDescription
337 {
338 get => _scopeDescription;
339 set
340 {
341 if (SetProperty(
342 ref _scopeDescription,
343 value?.Trim() ?? string.Empty))
344 {
345 OnPropertyChanged(
346 nameof(HasScopeDescription));
347 }
348 }
349 }
350
351 /// <summary>
352 /// Gets or sets the normalized compatibility score.
353 /// </summary>
354 /// <remarks>
355 /// The value must lie between zero and one:
356 /// <list type="bullet">
357 /// <item>
358 /// <description>
359 /// zero indicates that none of the weighted criteria are
360 /// satisfied;
361 /// </description>
362 /// </item>
363 /// <item>
364 /// <description>
365 /// one indicates that all weighted criteria are
366 /// satisfied.
367 /// </description>
368 /// </item>
369 /// </list>
370 ///
371 /// Blocking evidence remains blocking independently of
372 /// this numerical score.
373 /// </remarks>
374 /// <exception cref="ArgumentOutOfRangeException">
375 /// Thrown when the supplied score is not finite or does
376 /// not lie between zero and one.
377 /// </exception>
378 [XmlAttribute("score")]
379 public double Score
380 {
381 get => _score;
382 set
383 {
384 if (!double.IsFinite(value) ||
385 value < 0.0 ||
386 value > 1.0)
387 {
388 throw new ArgumentOutOfRangeException(
389 nameof(value),
390 value,
391 "The compatibility score must be finite " +
392 "and lie between zero and one.");
393 }
394
395 if (SetProperty(
396 ref _score,
397 value))
398 {
399 OnPropertyChanged(
400 nameof(IsValidRecommendation));
401 }
402 }
403 }
404
405 /// <summary>
406 /// Gets or sets the rank assigned to the recommendation.
407 /// </summary>
408 /// <remarks>
409 /// A null value means that the recommendation has not yet
410 /// been ranked against other methods.
411 /// </remarks>
412 /// <exception cref="ArgumentOutOfRangeException">
413 /// Thrown when the supplied rank is not strictly positive.
414 /// </exception>
415 [XmlElement("rank", IsNullable = true)]
416 public int? Rank
417 {
418 get => _rank;
419 set
420 {
421 if (value.HasValue &&
422 value.Value <= 0)
423 {
424 throw new ArgumentOutOfRangeException(
425 nameof(value),
426 value,
427 "A recommendation rank must be strictly " +
428 "positive.");
429 }
430
431 if (SetProperty(
432 ref _rank,
433 value))
434 {
435 OnPropertyChanged(
436 nameof(HasRank));
437 }
438 }
439 }
440
441 /// <summary>
442 /// Gets or sets the UTC date and time at which the
443 /// recommendation was evaluated.
444 /// </summary>
445 [XmlElement("evaluatedAtUtc", IsNullable = true)]
446 public DateTime? EvaluatedAtUtc
447 {
448 get => _evaluatedAtUtc;
449 set
450 {
451 DateTime? normalizedValue =
452 value.HasValue
453 ? ConvertToUtc(value.Value)
454 : null;
455
456 if (SetProperty(
457 ref _evaluatedAtUtc,
458 normalizedValue))
459 {
460 OnPropertyChanged(
461 nameof(HasEvaluationDate));
462 }
463 }
464 }
465
466 /// <summary>
467 /// Gets or sets the version of the method advisor used to
468 /// produce the recommendation.
469 /// </summary>
470 [XmlAttribute("advisorVersion")]
471 public string AdvisorVersion
472 {
473 get => _advisorVersion;
474 set
475 {
476 if (SetProperty(
477 ref _advisorVersion,
478 value?.Trim() ?? string.Empty))
479 {
480 OnPropertyChanged(
481 nameof(HasAdvisorVersion));
482 }
483 }
484 }
485
486 /// <summary>
487 /// Gets or sets the name of the method catalog used during
488 /// evaluation.
489 /// </summary>
490 [XmlAttribute("methodCatalogName")]
491 public string MethodCatalogName
492 {
493 get => _methodCatalogName;
494 set
495 {
496 if (SetProperty(
497 ref _methodCatalogName,
498 value?.Trim() ?? string.Empty))
499 {
500 OnPropertyChanged(
502 }
503 }
504 }
505
506 /// <summary>
507 /// Gets or sets the version of the method catalog used
508 /// during evaluation.
509 /// </summary>
510 [XmlAttribute("methodCatalogVersion")]
512 {
513 get => _methodCatalogVersion;
514 set
515 {
516 if (SetProperty(
517 ref _methodCatalogVersion,
518 value?.Trim() ?? string.Empty))
519 {
520 OnPropertyChanged(
522 }
523 }
524 }
525
526 /// <summary>
527 /// Gets or sets the fingerprint of the supply-chain data
528 /// evaluated by the method advisor.
529 /// </summary>
530 [XmlAttribute("supplyChainFingerprint")]
532 {
533 get => _supplyChainFingerprint;
534 set
535 {
536 if (SetProperty(
537 ref _supplyChainFingerprint,
538 value?.Trim() ?? string.Empty))
539 {
540 OnPropertyChanged(
542 }
543 }
544 }
545
546 /// <summary>
547 /// Gets or sets a concise human-readable summary of the
548 /// recommendation.
549 /// </summary>
550 /// <remarks>
551 /// Examples include:
552 /// <list type="bullet">
553 /// <item>
554 /// <description>
555 /// <c>Recommended exact method for this MLLP
556 /// instance.</c>;
557 /// </description>
558 /// </item>
559 /// <item>
560 /// <description>
561 /// <c>Applicable only after relaxing production
562 /// capacity.</c>;
563 /// </description>
564 /// </item>
565 /// <item>
566 /// <description>
567 /// <c>Incompatible because transportation decisions are
568 /// unsupported.</c>.
569 /// </description>
570 /// </item>
571 /// </list>
572 /// </remarks>
573 [XmlElement("summary")]
574 public string Summary
575 {
576 get => _summary;
577 set
578 {
579 if (SetProperty(
580 ref _summary,
581 value?.Trim() ?? string.Empty))
582 {
583 OnPropertyChanged(
584 nameof(HasSummary));
585 }
586 }
587 }
588
589 /// <summary>
590 /// Gets the evidence used to evaluate the compatibility
591 /// of the method.
592 /// </summary>
593 [XmlArray("evidence")]
594 [XmlArrayItem("criterion")]
595 public List<MethodCompatibilityEvidence> Evidence
596 {
597 get;
598 } = new();
599
600 /// <summary>
601 /// Gets the adaptations required before the method can be
602 /// applied.
603 /// </summary>
604 /// <remarks>
605 /// Examples include:
606 /// <list type="bullet">
607 /// <item>
608 /// <description>
609 /// relaxing shared capacity constraints;
610 /// </description>
611 /// </item>
612 /// <item>
613 /// <description>
614 /// decomposing the instance by item;
615 /// </description>
616 /// </item>
617 /// <item>
618 /// <description>
619 /// extending the method to represent backlogging;
620 /// </description>
621 /// </item>
622 /// <item>
623 /// <description>
624 /// embedding the method inside a matheuristic.
625 /// </description>
626 /// </item>
627 /// </list>
628 /// </remarks>
629 [XmlArray("requiredAdaptations")]
630 [XmlArrayItem("adaptation")]
631 public List<string> RequiredAdaptations { get; } =
632 new();
633
634 /// <summary>
635 /// Gets the non-fatal warnings produced during method
636 /// evaluation.
637 /// </summary>
638 [XmlArray("warnings")]
639 [XmlArrayItem("warning")]
640 public List<string> Warnings { get; } =
641 new();
642
643 /// <summary>
644 /// Gets or sets an optional explanatory comment.
645 /// </summary>
646 [XmlElement("comment")]
647 public string Comment
648 {
649 get => _comment;
650 set
651 {
652 if (SetProperty(
653 ref _comment,
654 value?.Trim() ?? string.Empty))
655 {
656 OnPropertyChanged(
657 nameof(HasComment));
658 }
659 }
660 }
661
662 /// <summary>
663 /// Gets a value indicating whether a stable method code
664 /// has been recorded.
665 /// </summary>
666 [XmlIgnore]
667 public bool HasMethodCode =>
668 !string.IsNullOrWhiteSpace(
669 MethodCode);
670
671 /// <summary>
672 /// Gets a value indicating whether a human-readable method
673 /// name has been recorded.
674 /// </summary>
675 [XmlIgnore]
676 public bool HasMethodName =>
677 !string.IsNullOrWhiteSpace(
678 MethodName);
679
680 /// <summary>
681 /// Gets a value indicating whether a method version has
682 /// been recorded.
683 /// </summary>
684 [XmlIgnore]
685 public bool HasMethodVersion =>
686 !string.IsNullOrWhiteSpace(
688
689 /// <summary>
690 /// Gets a value indicating whether the method has been
691 /// evaluated.
692 /// </summary>
693 [XmlIgnore]
694 public bool HasBeenEvaluated =>
696 MethodCompatibilityLevel.NotEvaluated;
697
698 /// <summary>
699 /// Gets a value indicating whether an applicable problem
700 /// scope has been recorded.
701 /// </summary>
702 [XmlIgnore]
703 public bool HasScope =>
704 Scope !=
706
707 /// <summary>
708 /// Gets a value indicating whether the recommendation
709 /// applies to the complete problem.
710 /// </summary>
711 [XmlIgnore]
713 Scope ==
714 ProblemClassificationScope.CompleteProblem;
715
716 /// <summary>
717 /// Gets a value indicating whether a human-readable scope
718 /// description has been recorded.
719 /// </summary>
720 [XmlIgnore]
721 public bool HasScopeDescription =>
722 !string.IsNullOrWhiteSpace(
724
725 /// <summary>
726 /// Gets a value indicating whether the recommendation has
727 /// been ranked.
728 /// </summary>
729 [XmlIgnore]
730 public bool HasRank =>
731 Rank.HasValue;
732
733 /// <summary>
734 /// Gets a value indicating whether an evaluation date has
735 /// been recorded.
736 /// </summary>
737 [XmlIgnore]
738 public bool HasEvaluationDate =>
739 EvaluatedAtUtc.HasValue;
740
741 /// <summary>
742 /// Gets a value indicating whether an advisor version has
743 /// been recorded.
744 /// </summary>
745 [XmlIgnore]
746 public bool HasAdvisorVersion =>
747 !string.IsNullOrWhiteSpace(
749
750 /// <summary>
751 /// Gets a value indicating whether method-catalog
752 /// information has been recorded.
753 /// </summary>
754 [XmlIgnore]
756 !string.IsNullOrWhiteSpace(
758 !string.IsNullOrWhiteSpace(
760
761 /// <summary>
762 /// Gets a value indicating whether a supply-chain
763 /// fingerprint has been recorded.
764 /// </summary>
765 [XmlIgnore]
767 !string.IsNullOrWhiteSpace(
769
770 /// <summary>
771 /// Gets a value indicating whether a human-readable
772 /// summary has been recorded.
773 /// </summary>
774 [XmlIgnore]
775 public bool HasSummary =>
776 !string.IsNullOrWhiteSpace(
777 Summary);
778
779 /// <summary>
780 /// Gets a value indicating whether compatibility evidence
781 /// has been recorded.
782 /// </summary>
783 [XmlIgnore]
784 public bool HasEvidence =>
785 Evidence.Count > 0;
786
787 /// <summary>
788 /// Gets the number of compatibility-evidence records.
789 /// </summary>
790 [XmlIgnore]
791 public int EvidenceCount =>
792 Evidence.Count;
793
794 /// <summary>
795 /// Gets the number of satisfied compatibility criteria.
796 /// </summary>
797 [XmlIgnore]
799 Evidence.Count(
800 criterion =>
801 criterion is not null &&
802 criterion.IsSatisfied);
803
804 /// <summary>
805 /// Gets the number of unsatisfied compatibility criteria.
806 /// </summary>
807 [XmlIgnore]
808 public int MismatchCount =>
809 Evidence.Count(
810 criterion =>
811 criterion is not null &&
812 criterion.IsMismatch);
813
814 /// <summary>
815 /// Gets the number of blocking incompatibilities.
816 /// </summary>
817 [XmlIgnore]
819 Evidence.Count(
820 criterion =>
821 criterion is not null &&
822 criterion.IsBlockingMismatch);
823
824 /// <summary>
825 /// Gets the number of unsatisfied required criteria.
826 /// </summary>
827 [XmlIgnore]
829 Evidence.Count(
830 criterion =>
831 criterion is not null &&
832 criterion.IsRequiredMismatch);
833
834 /// <summary>
835 /// Gets the number of unsatisfied optional criteria.
836 /// </summary>
837 [XmlIgnore]
839 Evidence.Count(
840 criterion =>
841 criterion is not null &&
842 criterion.IsOptionalMismatch);
843
844 /// <summary>
845 /// Gets a value indicating whether at least one blocking
846 /// incompatibility has been found.
847 /// </summary>
848 [XmlIgnore]
851
852 /// <summary>
853 /// Gets a value indicating whether at least one required
854 /// compatibility criterion is not satisfied.
855 /// </summary>
856 [XmlIgnore]
859
860 /// <summary>
861 /// Gets a value indicating whether at least one optional
862 /// criterion is not satisfied.
863 /// </summary>
864 [XmlIgnore]
867
868 /// <summary>
869 /// Gets a value indicating whether at least one adaptation
870 /// is required before applying the method.
871 /// </summary>
872 [XmlIgnore]
874 RequiredAdaptations.Count > 0;
875
876 /// <summary>
877 /// Gets a value indicating whether at least one warning
878 /// has been recorded.
879 /// </summary>
880 [XmlIgnore]
881 public bool HasWarnings =>
882 Warnings.Count > 0;
883
884 /// <summary>
885 /// Gets a value indicating whether an explanatory comment
886 /// has been recorded.
887 /// </summary>
888 [XmlIgnore]
889 public bool HasComment =>
890 !string.IsNullOrWhiteSpace(
891 Comment);
892
893 /// <summary>
894 /// Gets a value indicating whether the method is
895 /// incompatible with the complete problem.
896 /// </summary>
897 [XmlIgnore]
898 public bool IsIncompatible =>
900 MethodCompatibilityLevel.Incompatible;
901
902 /// <summary>
903 /// Gets a value indicating whether the method is only
904 /// partially compatible.
905 /// </summary>
906 [XmlIgnore]
909 MethodCompatibilityLevel.PartiallyCompatible;
910
911 /// <summary>
912 /// Gets a value indicating whether the method is directly
913 /// compatible.
914 /// </summary>
915 [XmlIgnore]
916 public bool IsCompatible =>
920 MethodCompatibilityLevel.Recommended;
921
922 /// <summary>
923 /// Gets a value indicating whether the method is
924 /// recommended.
925 /// </summary>
926 [XmlIgnore]
927 public bool IsRecommended =>
929 MethodCompatibilityLevel.Recommended;
930
931 /// <summary>
932 /// Gets a value indicating whether the method may be
933 /// applied directly to the complete problem.
934 /// </summary>
935 [XmlIgnore]
938 IsCompatible &&
941
942 /// <summary>
943 /// Gets a value indicating whether decomposition,
944 /// relaxation or another adaptation is necessary.
945 /// </summary>
946 [XmlIgnore]
947 public bool RequiresAdaptation =>
951
952 /// <summary>
953 /// Gets a value indicating whether the recommendation
954 /// contains the minimum coherent information required for
955 /// presentation or ranking.
956 /// </summary>
957 [XmlIgnore]
961 double.IsFinite(Score) &&
962 Score >= 0.0 &&
963 Score <= 1.0 &&
964 (
967 );
968
969 /// <summary>
970 /// Adds one compatibility-evidence record.
971 /// </summary>
972 /// <param name="evidence">
973 /// Evidence to add.
974 /// </param>
975 /// <exception cref="ArgumentNullException">
976 /// Thrown when <paramref name="evidence"/> is
977 /// <see langword="null"/>.
978 /// </exception>
979 /// <exception cref="ArgumentException">
980 /// Thrown when the evidence is structurally invalid.
981 /// </exception>
982 public void AddEvidence(
984 {
985 ArgumentNullException.ThrowIfNull(
986 evidence);
987
988 if (!evidence.IsValidEvidence)
989 {
990 throw new ArgumentException(
991 "The compatibility evidence is invalid.",
992 nameof(evidence));
993 }
994
995 Evidence.Add(
996 evidence);
997
998 NotifyEvidenceProperties();
999 }
1000
1001 /// <summary>
1002 /// Replaces all compatibility-evidence records.
1003 /// </summary>
1004 /// <param name="evidence">
1005 /// New evidence collection.
1006 /// </param>
1007 /// <exception cref="ArgumentNullException">
1008 /// Thrown when <paramref name="evidence"/> is
1009 /// <see langword="null"/>.
1010 /// </exception>
1011 /// <exception cref="ArgumentException">
1012 /// Thrown when the collection contains a null or invalid
1013 /// evidence record.
1014 /// </exception>
1015 public void ReplaceEvidence(
1016 IEnumerable<MethodCompatibilityEvidence> evidence)
1017 {
1018 ArgumentNullException.ThrowIfNull(
1019 evidence);
1020
1021 var normalizedEvidence =
1022 new List<MethodCompatibilityEvidence>();
1023
1024 foreach (MethodCompatibilityEvidence? criterion
1025 in evidence)
1026 {
1027 if (criterion is null)
1028 {
1029 throw new ArgumentException(
1030 "The evidence collection cannot contain " +
1031 "a null element.",
1032 nameof(evidence));
1033 }
1034
1035 if (!criterion.IsValidEvidence)
1036 {
1037 throw new ArgumentException(
1038 $"Compatibility evidence " +
1039 $"'{criterion.CriterionCode}' is invalid.",
1040 nameof(evidence));
1041 }
1042
1043 normalizedEvidence.Add(
1044 criterion);
1045 }
1046
1047 Evidence.Clear();
1048
1049 Evidence.AddRange(
1050 normalizedEvidence);
1051
1052 NotifyEvidenceProperties();
1053 }
1054
1055 /// <summary>
1056 /// Removes all compatibility evidence.
1057 /// </summary>
1058 public void ClearEvidence()
1059 {
1060 Evidence.Clear();
1061
1062 NotifyEvidenceProperties();
1063 }
1064
1065 /// <summary>
1066 /// Replaces the adaptations required before applying the
1067 /// method.
1068 /// </summary>
1069 /// <param name="adaptations">
1070 /// New adaptation descriptions.
1071 /// </param>
1073 IEnumerable<string> adaptations)
1074 {
1075 ReplaceNormalizedStrings(
1077 adaptations,
1078 nameof(adaptations));
1079
1080 OnPropertyChanged(
1081 nameof(RequiredAdaptations));
1082
1083 OnPropertyChanged(
1084 nameof(HasRequiredAdaptations));
1085
1086 OnPropertyChanged(
1087 nameof(RequiresAdaptation));
1088 }
1089
1090 /// <summary>
1091 /// Replaces the warnings associated with this
1092 /// recommendation.
1093 /// </summary>
1094 /// <param name="warnings">
1095 /// New warning messages.
1096 /// </param>
1097 public void ReplaceWarnings(
1098 IEnumerable<string> warnings)
1099 {
1100 ReplaceNormalizedStrings(
1101 Warnings,
1102 warnings,
1103 nameof(warnings));
1104
1105 OnPropertyChanged(
1106 nameof(Warnings));
1107
1108 OnPropertyChanged(
1109 nameof(HasWarnings));
1110 }
1111
1112 /// <summary>
1113 /// Calculates a normalized score from the current
1114 /// compatibility evidence.
1115 /// </summary>
1116 /// <returns>
1117 /// Weighted proportion of satisfied criteria, between zero
1118 /// and one.
1119 /// </returns>
1120 /// <remarks>
1121 /// Evidence with a zero weight is retained for
1122 /// traceability but does not influence the score.
1123 ///
1124 /// Blocking mismatches are not given special numerical
1125 /// treatment by this method. They are handled when the
1126 /// compatibility level is determined.
1127 /// </remarks>
1128 public double CalculateScore()
1129 {
1130 MethodCompatibilityEvidence[] weightedEvidence =
1131 Evidence
1132 .Where(
1133 criterion =>
1134 criterion is not null &&
1135 criterion.IsValidEvidence &&
1136 criterion.Weight > 0.0)
1137 .ToArray();
1138
1139 double totalWeight =
1140 weightedEvidence.Sum(
1141 criterion =>
1142 criterion.Weight);
1143
1144 if (totalWeight <= 0.0)
1145 {
1146 return 0.0;
1147 }
1148
1149 double satisfiedWeight =
1150 weightedEvidence
1151 .Where(
1152 criterion =>
1153 criterion.IsSatisfied)
1154 .Sum(
1155 criterion =>
1156 criterion.Weight);
1157
1158 double calculatedScore =
1159 satisfiedWeight /
1160 totalWeight;
1161
1162 return Math.Clamp(
1163 calculatedScore,
1164 0.0,
1165 1.0);
1166 }
1167
1168 /// <summary>
1169 /// Recalculates and stores the compatibility score.
1170 /// </summary>
1171 /// <returns>
1172 /// Newly calculated score.
1173 /// </returns>
1174 public double UpdateScore()
1175 {
1176 double calculatedScore =
1178
1179 Score =
1180 calculatedScore;
1181
1182 return calculatedScore;
1183 }
1184
1185 /// <summary>
1186 /// Determines the compatibility level from the current
1187 /// evidence, scope and score.
1188 /// </summary>
1189 /// <param name="recommendedScoreThreshold">
1190 /// Minimum score required for a directly compatible method
1191 /// to be marked as recommended.
1192 /// </param>
1193 /// <param name="updateEvaluationDate">
1194 /// Value indicating whether the evaluation date must be
1195 /// set to the current UTC date and time.
1196 /// </param>
1197 /// <returns>
1198 /// Newly assigned compatibility level.
1199 /// </returns>
1200 /// <exception cref="ArgumentOutOfRangeException">
1201 /// Thrown when
1202 /// <paramref name="recommendedScoreThreshold"/> is not
1203 /// finite or does not lie between zero and one.
1204 /// </exception>
1207 double recommendedScoreThreshold = 0.85,
1208 bool updateEvaluationDate = true)
1209 {
1210 if (!double.IsFinite(
1211 recommendedScoreThreshold) ||
1212 recommendedScoreThreshold < 0.0 ||
1213 recommendedScoreThreshold > 1.0)
1214 {
1215 throw new ArgumentOutOfRangeException(
1216 nameof(recommendedScoreThreshold),
1217 recommendedScoreThreshold,
1218 "The recommended-score threshold must be " +
1219 "finite and lie between zero and one.");
1220 }
1221
1222 UpdateScore();
1223
1225
1226 if (!HasEvidence ||
1227 Scope ==
1229 {
1230 level =
1231 MethodCompatibilityLevel.NotEvaluated;
1232 }
1233 else if (HasBlockingMismatches)
1234 {
1235 level =
1236 MethodCompatibilityLevel.Incompatible;
1237 }
1238 else if (HasRequiredMismatches ||
1241 {
1242 level =
1244 .PartiallyCompatible;
1245 }
1246 else if (Score >=
1247 recommendedScoreThreshold)
1248 {
1249 level =
1250 MethodCompatibilityLevel.Recommended;
1251 }
1252 else
1253 {
1254 level =
1255 MethodCompatibilityLevel.Compatible;
1256 }
1257
1259 level;
1260
1261 if (updateEvaluationDate &&
1262 level !=
1263 MethodCompatibilityLevel.NotEvaluated)
1264 {
1266 DateTime.UtcNow;
1267 }
1268
1269 return level;
1270 }
1271
1272 /// <summary>
1273 /// Determines whether the recommendation was produced for
1274 /// the supplied supply-chain fingerprint.
1275 /// </summary>
1276 /// <param name="currentFingerprint">
1277 /// Current supply-chain fingerprint.
1278 /// </param>
1279 /// <returns>
1280 /// <see langword="true"/> when both fingerprints are
1281 /// present and equal; otherwise, <see langword="false"/>.
1282 /// </returns>
1284 string currentFingerprint)
1285 {
1287 string.IsNullOrWhiteSpace(
1288 currentFingerprint))
1289 {
1290 return false;
1291 }
1292
1293 return string.Equals(
1295 currentFingerprint.Trim(),
1296 StringComparison.Ordinal);
1297 }
1298
1299 /// <summary>
1300 /// Clears the evaluated compatibility while preserving the
1301 /// identity of the solution method.
1302 /// </summary>
1303 public void ClearEvaluation()
1304 {
1306 MethodCompatibilityLevel.NotEvaluated;
1307
1308 Scope =
1310
1312 string.Empty;
1313
1314 Score =
1315 0.0;
1316
1317 Rank =
1318 null;
1319
1321 null;
1322
1323 Summary =
1324 string.Empty;
1325
1326 Evidence.Clear();
1327 RequiredAdaptations.Clear();
1328 Warnings.Clear();
1329
1330 NotifyEvidenceProperties();
1331
1332 OnPropertyChanged(
1333 nameof(RequiredAdaptations));
1334
1335 OnPropertyChanged(
1336 nameof(HasRequiredAdaptations));
1337
1338 OnPropertyChanged(
1339 nameof(Warnings));
1340
1341 OnPropertyChanged(
1342 nameof(HasWarnings));
1343 }
1344
1345 /// <inheritdoc/>
1346 public override string ToString()
1347 {
1348 string scoreDescription =
1349 Score.ToString(
1350 "0.000",
1351 CultureInfo.InvariantCulture);
1352
1353 string rankDescription =
1354 Rank is int rank
1355 ? $"; rank {rank}"
1356 : string.Empty;
1357
1358 return
1359 $"{MethodCode} — {MethodName}; " +
1360 $"{CompatibilityLevel}; " +
1361 $"score {scoreDescription}" +
1362 rankDescription;
1363 }
1364
1365 private static string NormalizeCode(
1366 string? value)
1367 {
1368 return string.IsNullOrWhiteSpace(value)
1369 ? string.Empty
1370 : value.Trim().ToUpperInvariant();
1371 }
1372
1373 private static DateTime ConvertToUtc(
1374 DateTime value)
1375 {
1376 return value.Kind switch
1377 {
1378 DateTimeKind.Utc =>
1379 value,
1380
1381 DateTimeKind.Local =>
1382 value.ToUniversalTime(),
1383
1384 _ =>
1385 DateTime.SpecifyKind(
1386 value,
1387 DateTimeKind.Utc)
1388 };
1389 }
1390
1391 private static void ReplaceNormalizedStrings(
1392 ICollection<string> destination,
1393 IEnumerable<string> source,
1394 string parameterName)
1395 {
1396 ArgumentNullException.ThrowIfNull(
1397 source,
1398 parameterName);
1399
1400 string[] normalizedValues =
1401 source
1402 .Where(
1403 value =>
1404 !string.IsNullOrWhiteSpace(
1405 value))
1406 .Select(
1407 value =>
1408 value.Trim())
1409 .Distinct(
1410 StringComparer.OrdinalIgnoreCase)
1411 .OrderBy(
1412 value =>
1413 value,
1414 StringComparer.OrdinalIgnoreCase)
1415 .ToArray();
1416
1417 destination.Clear();
1418
1419 foreach (string value in normalizedValues)
1420 {
1421 destination.Add(
1422 value);
1423 }
1424 }
1425
1426 private void NotifyCompatibilityProperties()
1427 {
1428 OnPropertyChanged(
1429 nameof(HasBeenEvaluated));
1430
1431 OnPropertyChanged(
1432 nameof(IsIncompatible));
1433
1434 OnPropertyChanged(
1435 nameof(IsPartiallyCompatible));
1436
1437 OnPropertyChanged(
1438 nameof(IsCompatible));
1439
1440 OnPropertyChanged(
1441 nameof(IsRecommended));
1442
1443 OnPropertyChanged(
1445
1446 OnPropertyChanged(
1447 nameof(RequiresAdaptation));
1448
1449 OnPropertyChanged(
1450 nameof(IsValidRecommendation));
1451 }
1452
1453 private void NotifyScopeProperties()
1454 {
1455 OnPropertyChanged(
1456 nameof(HasScope));
1457
1458 OnPropertyChanged(
1459 nameof(AppliesToCompleteProblem));
1460
1461 OnPropertyChanged(
1463
1464 OnPropertyChanged(
1465 nameof(RequiresAdaptation));
1466
1467 OnPropertyChanged(
1468 nameof(IsValidRecommendation));
1469 }
1470
1471 private void NotifyEvidenceProperties()
1472 {
1473 OnPropertyChanged(
1474 nameof(Evidence));
1475
1476 OnPropertyChanged(
1477 nameof(HasEvidence));
1478
1479 OnPropertyChanged(
1480 nameof(EvidenceCount));
1481
1482 OnPropertyChanged(
1483 nameof(SatisfiedEvidenceCount));
1484
1485 OnPropertyChanged(
1486 nameof(MismatchCount));
1487
1488 OnPropertyChanged(
1489 nameof(BlockingMismatchCount));
1490
1491 OnPropertyChanged(
1492 nameof(RequiredMismatchCount));
1493
1494 OnPropertyChanged(
1495 nameof(OptionalMismatchCount));
1496
1497 OnPropertyChanged(
1498 nameof(HasBlockingMismatches));
1499
1500 OnPropertyChanged(
1501 nameof(HasRequiredMismatches));
1502
1503 OnPropertyChanged(
1504 nameof(HasOptionalMismatches));
1505
1506 OnPropertyChanged(
1508 }
1509}
Represents one piece of evidence used to evaluate the compatibility between a solution method and a l...
bool IsSatisfied
Gets or sets a value indicating whether the compatibility criterion is satisfied.
double Weight
Gets or sets the non-negative importance assigned to the criterion.
bool IsValidEvidence
Gets a value indicating whether this evidence contains the minimum information required for use by th...
Describes the applicability, capabilities and limitations of a solution method for lot-sizing problem...
string MethodCode
Gets or sets the stable code identifying the solution method.
SolutionMethodKind MethodKind
Gets or sets the general category of the solution method.
string Name
Gets or sets the human-readable name of the method.
string MethodVersion
Gets or sets the version of the method definition.
string ScopeDescription
Gets or sets a human-readable explanation of the applicable problem scope.
bool HasOptionalMismatches
Gets a value indicating whether at least one optional criterion is not satisfied.
string SupplyChainFingerprint
Gets or sets the fingerprint of the supply-chain data evaluated by the method advisor.
List< MethodCompatibilityEvidence > Evidence
Gets the evidence used to evaluate the compatibility of the method.
List< string > RequiredAdaptations
Gets the adaptations required before the method can be applied.
bool HasMethodCode
Gets a value indicating whether a stable method code has been recorded.
bool HasRank
Gets a value indicating whether the recommendation has been ranked.
bool HasScope
Gets a value indicating whether an applicable problem scope has been recorded.
string MethodVersion
Gets or sets the version of the evaluated method definition.
bool AppliesToCompleteProblem
Gets a value indicating whether the recommendation applies to the complete problem.
void ReplaceRequiredAdaptations(IEnumerable< string > adaptations)
Replaces the adaptations required before applying the method.
int SatisfiedEvidenceCount
Gets the number of satisfied compatibility criteria.
bool HasEvaluationDate
Gets a value indicating whether an evaluation date has been recorded.
SolutionMethodRecommendation(string methodCode, string methodName, SolutionMethodKind methodKind)
Initializes a recommendation for a solution method.
bool HasComment
Gets a value indicating whether an explanatory comment has been recorded.
int MismatchCount
Gets the number of unsatisfied compatibility criteria.
bool HasMethodName
Gets a value indicating whether a human-readable method name has been recorded.
bool RequiresAdaptation
Gets a value indicating whether decomposition, relaxation or another adaptation is necessary.
SolutionMethodKind MethodKind
Gets or sets the general category of the evaluated solution method.
string Summary
Gets or sets a concise human-readable summary of the recommendation.
bool IsValidRecommendation
Gets a value indicating whether the recommendation contains the minimum coherent information required...
MethodCompatibilityLevel UpdateCompatibilityFromEvidence(double recommendedScoreThreshold=0.85, bool updateEvaluationDate=true)
Determines the compatibility level from the current evidence, scope and score.
string AdvisorVersion
Gets or sets the version of the method advisor used to produce the recommendation.
bool HasSummary
Gets a value indicating whether a human-readable summary has been recorded.
bool HasMethodVersion
Gets a value indicating whether a method version has been recorded.
void ReplaceWarnings(IEnumerable< string > warnings)
Replaces the warnings associated with this recommendation.
List< string > Warnings
Gets the non-fatal warnings produced during method evaluation.
bool HasEvidence
Gets a value indicating whether compatibility evidence has been recorded.
string MethodCatalogVersion
Gets or sets the version of the method catalog used during evaluation.
MethodCompatibilityLevel CompatibilityLevel
Gets or sets the evaluated compatibility level.
void ClearEvaluation()
Clears the evaluated compatibility while preserving the identity of the solution method.
bool HasMethodCatalogInformation
Gets a value indicating whether method-catalog information has been recorded.
double CalculateScore()
Calculates a normalized score from the current compatibility evidence.
void ReplaceEvidence(IEnumerable< MethodCompatibilityEvidence > evidence)
Replaces all compatibility-evidence records.
bool HasBlockingMismatches
Gets a value indicating whether at least one blocking incompatibility has been found.
string MethodCatalogName
Gets or sets the name of the method catalog used during evaluation.
bool HasAdvisorVersion
Gets a value indicating whether an advisor version has been recorded.
bool HasSupplyChainFingerprint
Gets a value indicating whether a supply-chain fingerprint has been recorded.
bool MatchesSupplyChainFingerprint(string currentFingerprint)
Determines whether the recommendation was produced for the supplied supply-chain fingerprint.
void AddEvidence(MethodCompatibilityEvidence evidence)
Adds one compatibility-evidence record.
bool IsIncompatible
Gets a value indicating whether the method is incompatible with the complete problem.
string MethodCode
Gets or sets the stable code identifying the evaluated solution method.
bool IsCompatible
Gets a value indicating whether the method is directly compatible.
bool IsRecommended
Gets a value indicating whether the method is recommended.
bool CanSolveCompleteProblemDirectly
Gets a value indicating whether the method may be applied directly to the complete problem.
bool HasScopeDescription
Gets a value indicating whether a human-readable scope description has been recorded.
DateTime? EvaluatedAtUtc
Gets or sets the UTC date and time at which the recommendation was evaluated.
SolutionMethodRecommendation(SolutionMethodDefinition methodDefinition)
Initializes a recommendation from a method definition.
bool HasBeenEvaluated
Gets a value indicating whether the method has been evaluated.
bool HasRequiredMismatches
Gets a value indicating whether at least one required compatibility criterion is not satisfied.
bool HasWarnings
Gets a value indicating whether at least one warning has been recorded.
bool IsPartiallyCompatible
Gets a value indicating whether the method is only partially compatible.
SolutionMethodRecommendation()
Initializes an empty solution-method recommendation.
string MethodName
Gets or sets the human-readable name of the evaluated solution method.
ProblemClassificationScope Scope
Gets or sets the problem scope to which the method can be applied.
bool HasRequiredAdaptations
Gets a value indicating whether at least one adaptation is required before applying the method.
ProblemClassificationScope
Identifies the part of a lot-sizing instance to which a known-problem-family match applies.
MethodCompatibilityLevel
Indicates the compatibility level between a solution method and a lot-sizing problem instance.
@ Compatible
The method supports the complete problem instance and none of its hard assumptions are violated.