LotSizingDataModel.Instance 2.0.1
Lot-sizing instance representation, descriptors and problem characterization.
Loading...
Searching...
No Matches
LotSizingInstance.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;
7using LotSizingDataModel.Core.Common;
11
13
14/// <summary>
15/// Represents a complete lot-sizing problem instance,
16/// including its supply-chain data, metadata, structural
17/// analysis, automatic classification and known results.
18/// </summary>
19/// <remarks>
20/// The supply chain contains the source problem data.
21///
22/// Product-structure information, problem classification and
23/// solution-method recommendations are derived information.
24/// They may therefore become outdated when the supply chain is
25/// replaced or modified.
26/// </remarks>
27[Serializable]
28[XmlRoot("lotSizingInstance")]
29[XmlType(TypeName = "lotSizingInstance")]
30public sealed partial class LotSizingInstance : ModelObject
31{
32 private string _instanceId =
33 Guid.NewGuid().ToString("D");
34
35 private string _name =
36 string.Empty;
37
38 private string _description =
39 string.Empty;
40
41 private string _sourceInformation =
42 string.Empty;
43
44 private string _createdBy =
45 string.Empty;
46
47 private string _formatVersion =
48 "1.0";
49
50 private DateTime? _createdAtUtc =
51 DateTime.UtcNow;
52
53 private DateTime? _modifiedAtUtc =
54 DateTime.UtcNow;
55
56 private SupplyChain _supplyChain =
57 new();
58
59 private ProductStructureDescriptor
60 _productStructure =
61 new();
62
63 private LotSizingProblemClassification
64 _problemClassification =
65 new();
66
67 private string _bestKnownResultId =
68 string.Empty;
69
70 private string _comment =
71 string.Empty;
72
73 /// <summary>
74 /// Initializes an empty lot-sizing problem instance.
75 /// </summary>
76 /// <remarks>
77 /// This constructor is required for XML serialization.
78 /// </remarks>
80 {
81 }
82
83 /// <summary>
84 /// Initializes a lot-sizing problem instance from a
85 /// supply-chain model.
86 /// </summary>
87 /// <param name="supplyChain">
88 /// Supply-chain model represented by the instance.
89 /// </param>
90 /// <exception cref="ArgumentNullException">
91 /// Thrown when <paramref name="supplyChain"/> is
92 /// <see langword="null"/>.
93 /// </exception>
95 SupplyChain supplyChain)
96 {
97 ArgumentNullException.ThrowIfNull(
98 supplyChain);
99
100 _supplyChain =
101 supplyChain;
102 }
103
104 /// <summary>
105 /// Initializes a named lot-sizing problem instance.
106 /// </summary>
107 /// <param name="supplyChain">
108 /// Supply-chain model represented by the instance.
109 /// </param>
110 /// <param name="name">
111 /// Human-readable name of the instance.
112 /// </param>
113 /// <exception cref="ArgumentNullException">
114 /// Thrown when <paramref name="supplyChain"/> is
115 /// <see langword="null"/>.
116 /// </exception>
118 SupplyChain supplyChain,
119 string name)
120 : this(
121 Guid.NewGuid().ToString("D"),
122 supplyChain,
123 name)
124 {
125 }
126
127 /// <summary>
128 /// Initializes an identified lot-sizing problem instance.
129 /// </summary>
130 /// <param name="instanceId">
131 /// Stable identifier of the instance.
132 /// </param>
133 /// <param name="supplyChain">
134 /// Supply-chain model represented by the instance.
135 /// </param>
136 /// <param name="name">
137 /// Optional human-readable name of the instance.
138 /// </param>
139 /// <exception cref="ArgumentException">
140 /// Thrown when <paramref name="instanceId"/> is empty.
141 /// </exception>
142 /// <exception cref="ArgumentNullException">
143 /// Thrown when <paramref name="supplyChain"/> is
144 /// <see langword="null"/>.
145 /// </exception>
147 string instanceId,
148 SupplyChain supplyChain,
149 string name = "")
150 {
151 if (string.IsNullOrWhiteSpace(instanceId))
152 {
153 throw new ArgumentException(
154 "A lot-sizing instance identifier is required.",
155 nameof(instanceId));
156 }
157
158 ArgumentNullException.ThrowIfNull(
159 supplyChain);
160
161 _instanceId =
162 instanceId.Trim();
163
164 _supplyChain =
165 supplyChain;
166
167 _name =
168 name?.Trim() ?? string.Empty;
169
170 DateTime currentDateUtc =
171 DateTime.UtcNow;
172
173 _createdAtUtc =
174 currentDateUtc;
175
176 _modifiedAtUtc =
177 currentDateUtc;
178 }
179
180 /// <summary>
181 /// Gets or sets the stable identifier of the instance.
182 /// </summary>
183 /// <remarks>
184 /// The identifier distinguishes the instance from other
185 /// instances independently from its human-readable name.
186 /// </remarks>
187 [XmlAttribute("instanceId")]
188 public string InstanceId
189 {
190 get => _instanceId;
191
192 set
193 {
194 if (SetProperty(
195 ref _instanceId,
196 value?.Trim() ?? string.Empty))
197 {
198 OnPropertyChanged(
199 nameof(HasInstanceId));
200 }
201 }
202 }
203
204 /// <summary>
205 /// Gets or sets the human-readable name of the instance.
206 /// </summary>
207 [XmlAttribute("name")]
208 public string Name
209 {
210 get => _name;
211
212 set
213 {
214 if (SetProperty(
215 ref _name,
216 value?.Trim() ?? string.Empty))
217 {
218 OnPropertyChanged(
219 nameof(HasName));
220 }
221 }
222 }
223
224 /// <summary>
225 /// Gets or sets the human-readable description of the
226 /// problem instance.
227 /// </summary>
228 [XmlElement("description")]
229 public string Description
230 {
231 get => _description;
232
233 set
234 {
235 if (SetProperty(
236 ref _description,
237 value?.Trim() ?? string.Empty))
238 {
239 OnPropertyChanged(
240 nameof(HasDescription));
241 }
242 }
243 }
244
245 /// <summary>
246 /// Gets or sets information identifying the source of the
247 /// instance.
248 /// </summary>
249 /// <remarks>
250 /// Examples include a publication, benchmark collection,
251 /// industrial data set or instance generator.
252 /// </remarks>
253 [XmlElement("sourceInformation")]
254 public string SourceInformation
255 {
256 get => _sourceInformation;
257
258 set
259 {
260 if (SetProperty(
261 ref _sourceInformation,
262 value?.Trim() ?? string.Empty))
263 {
264 OnPropertyChanged(
265 nameof(HasSourceInformation));
266 }
267 }
268 }
269
270 /// <summary>
271 /// Gets or sets the person, organization or software
272 /// component that created the instance.
273 /// </summary>
274 [XmlAttribute("createdBy")]
275 public string CreatedBy
276 {
277 get => _createdBy;
278
279 set
280 {
281 if (SetProperty(
282 ref _createdBy,
283 value?.Trim() ?? string.Empty))
284 {
285 OnPropertyChanged(
286 nameof(HasCreatedBy));
287 }
288 }
289 }
290
291 /// <summary>
292 /// Gets or sets the format version of the serialized
293 /// instance.
294 /// </summary>
295 [XmlAttribute("formatVersion")]
296 public string FormatVersion
297 {
298 get => _formatVersion;
299
300 set
301 {
302 if (SetProperty(
303 ref _formatVersion,
304 value?.Trim() ?? string.Empty))
305 {
306 OnPropertyChanged(
307 nameof(HasFormatVersion));
308 }
309 }
310 }
311
312 /// <summary>
313 /// Gets or sets the UTC date and time at which the
314 /// instance was created.
315 /// </summary>
316 [XmlElement("createdAtUtc", IsNullable = true)]
317 public DateTime? CreatedAtUtc
318 {
319 get => _createdAtUtc;
320
321 set
322 {
323 if (SetProperty(
324 ref _createdAtUtc,
325 ConvertToUtc(value)))
326 {
327 OnPropertyChanged(
328 nameof(HasCreationDate));
329 }
330 }
331 }
332
333 /// <summary>
334 /// Gets or sets the UTC date and time at which the
335 /// instance was last modified.
336 /// </summary>
337 [XmlElement("modifiedAtUtc", IsNullable = true)]
338 public DateTime? ModifiedAtUtc
339 {
340 get => _modifiedAtUtc;
341
342 set
343 {
344 if (SetProperty(
345 ref _modifiedAtUtc,
346 ConvertToUtc(value)))
347 {
348 OnPropertyChanged(
349 nameof(HasModificationDate));
350
351 OnPropertyChanged(
352 nameof(LastModifiedAtUtc));
353 }
354 }
355 }
356
357 /// <summary>
358 /// Gets the last-modification date of the instance.
359 /// </summary>
360 [XmlIgnore]
361 public DateTime? LastModifiedAtUtc =>
363
364 /// <summary>
365 /// Gets or sets the supply-chain model represented by the
366 /// instance.
367 /// </summary>
368 /// <remarks>
369 /// The property setter is primarily required for XML
370 /// serialization.
371 ///
372 /// Application code should normally use
373 /// <see cref="ReplaceSupplyChain"/> so that derived
374 /// analyses and recommendations are invalidated.
375 /// </remarks>
376 [XmlElement("supplyChain")]
378 {
379 get => _supplyChain;
380
381 set
382 {
383 SupplyChain normalizedValue =
384 value ??
385 new SupplyChain();
386
387 if (SetProperty(
388 ref _supplyChain,
389 normalizedValue))
390 {
391 OnPropertyChanged(
392 nameof(PlanningHorizon));
393 }
394 }
395 }
396
397 /// <summary>
398 /// Gets or sets the product-structure descriptor
399 /// associated with the supply-chain model.
400 /// </summary>
401 [XmlElement("productStructure")]
403 {
404 get => _productStructure;
405
406 set
407 {
408 ProductStructureDescriptor normalizedValue =
409 value ??
411
412 SetProperty(
413 ref _productStructure,
414 normalizedValue);
415 }
416 }
417
418 /// <summary>
419 /// Gets or sets the automatic lot-sizing problem
420 /// classification.
421 /// </summary>
422 [XmlElement("problemClassification")]
425 {
426 get =>
427 _problemClassification;
428
429 set
430 {
431 LotSizingProblemClassification normalizedValue =
432 value ??
434
435 SetProperty(
436 ref _problemClassification,
437 normalizedValue);
438 }
439 }
440
441 /// <summary>
442 /// Gets the known results associated with the instance.
443 /// </summary>
444 /// <remarks>
445 /// A known result may contain an objective value, a
446 /// detailed solution, or both.
447 /// </remarks>
448 [XmlArray("knownResults")]
449 [XmlArrayItem("knownResult")]
450 public List<KnownResult> KnownResults { get; } =
451 new();
452
453 /// <summary>
454 /// Gets or sets the identifier of the result currently
455 /// considered to be the best known result.
456 /// </summary>
457 /// <remarks>
458 /// An empty value means that no best known result has been
459 /// selected.
460 /// </remarks>
461 [XmlAttribute("bestKnownResultId")]
462 public string BestKnownResultId
463 {
464 get => _bestKnownResultId;
465
466 set
467 {
468 if (SetProperty(
469 ref _bestKnownResultId,
470 value?.Trim() ?? string.Empty))
471 {
472 NotifyBestKnownResultProperties();
473 }
474 }
475 }
476
477 /// <summary>
478 /// Gets the descriptive tags associated with the
479 /// instance.
480 /// </summary>
481 [XmlArray("tags")]
482 [XmlArrayItem("tag")]
483 public List<string> Tags { get; } =
484 new();
485
486 /// <summary>
487 /// Gets or sets an optional explanatory comment.
488 /// </summary>
489 [XmlElement("comment")]
490 public string Comment
491 {
492 get => _comment;
493
494 set
495 {
496 if (SetProperty(
497 ref _comment,
498 value?.Trim() ?? string.Empty))
499 {
500 OnPropertyChanged(
501 nameof(HasComment));
502 }
503 }
504 }
505
506 /// <summary>
507 /// Gets a value indicating whether the instance has a
508 /// stable identifier.
509 /// </summary>
510 [XmlIgnore]
511 public bool HasInstanceId =>
512 !string.IsNullOrWhiteSpace(
513 InstanceId);
514
515 /// <summary>
516 /// Gets a value indicating whether the instance has a
517 /// human-readable name.
518 /// </summary>
519 [XmlIgnore]
520 public bool HasName =>
521 !string.IsNullOrWhiteSpace(
522 Name);
523
524 /// <summary>
525 /// Gets a value indicating whether a description has been
526 /// recorded.
527 /// </summary>
528 [XmlIgnore]
529 public bool HasDescription =>
530 !string.IsNullOrWhiteSpace(
532
533 /// <summary>
534 /// Gets a value indicating whether source information has
535 /// been recorded.
536 /// </summary>
537 [XmlIgnore]
538 public bool HasSourceInformation =>
539 !string.IsNullOrWhiteSpace(
541
542 /// <summary>
543 /// Gets a value indicating whether creator information has
544 /// been recorded.
545 /// </summary>
546 [XmlIgnore]
547 public bool HasCreatedBy =>
548 !string.IsNullOrWhiteSpace(
549 CreatedBy);
550
551 /// <summary>
552 /// Gets a value indicating whether a format version has
553 /// been recorded.
554 /// </summary>
555 [XmlIgnore]
556 public bool HasFormatVersion =>
557 !string.IsNullOrWhiteSpace(
559
560 /// <summary>
561 /// Gets a value indicating whether a creation date has
562 /// been recorded.
563 /// </summary>
564 [XmlIgnore]
565 public bool HasCreationDate =>
566 CreatedAtUtc.HasValue;
567
568 /// <summary>
569 /// Gets a value indicating whether a modification date has
570 /// been recorded.
571 /// </summary>
572 [XmlIgnore]
573 public bool HasModificationDate =>
574 ModifiedAtUtc.HasValue;
575
576 /// <summary>
577 /// Gets the planning horizon of the supply-chain model.
578 /// </summary>
579 [XmlIgnore]
580 public int PlanningHorizon =>
581 SupplyChain.PlanningHorizon;
582
583 /// <summary>
584 /// Gets a value indicating whether at least one known
585 /// result has been recorded.
586 /// </summary>
587 [XmlIgnore]
588 public bool HasKnownResults =>
589 KnownResults.Count > 0;
590
591 /// <summary>
592 /// Gets the number of known results associated with the
593 /// instance.
594 /// </summary>
595 [XmlIgnore]
596 public int KnownResultCount =>
597 KnownResults.Count;
598
599 /// <summary>
600 /// Gets a value indicating whether a best known result
601 /// identifier has been selected.
602 /// </summary>
603 [XmlIgnore]
604 public bool HasBestKnownResultId =>
605 !string.IsNullOrWhiteSpace(
607
608 /// <summary>
609 /// Gets the result selected as the best known result.
610 /// </summary>
611 /// <remarks>
612 /// The property returns <see langword="null"/> when no
613 /// result has been selected or when the referenced result
614 /// cannot be found.
615 /// </remarks>
616 [XmlIgnore]
620
621 /// <summary>
622 /// Gets a value indicating whether the selected best known
623 /// result exists in <see cref="KnownResults"/>.
624 /// </summary>
625 [XmlIgnore]
626 public bool HasBestKnownResult =>
627 BestKnownResult is not null;
628
629 /// <summary>
630 /// Gets a value indicating whether the selected best-known
631 /// result is eligible for that role.
632 /// </summary>
633 [XmlIgnore]
635 BestKnownResult?.CanBeSelectedAsBestKnownResult ==
636 true;
637
638 /// <summary>
639 /// Gets a value indicating whether at least one tag has
640 /// been recorded.
641 /// </summary>
642 [XmlIgnore]
643 public bool HasTags =>
644 Tags.Count > 0;
645
646 /// <summary>
647 /// Gets a value indicating whether an explanatory comment
648 /// has been recorded.
649 /// </summary>
650 [XmlIgnore]
651 public bool HasComment =>
652 !string.IsNullOrWhiteSpace(
653 Comment);
654
655 /// <summary>
656 /// Adds a known result to the instance.
657 /// </summary>
658 /// <param name="knownResult">
659 /// Known result to add.
660 /// </param>
661 /// <exception cref="ArgumentNullException">
662 /// Thrown when <paramref name="knownResult"/> is
663 /// <see langword="null"/>.
664 /// </exception>
665 /// <exception cref="ArgumentException">
666 /// Thrown when the result has no identifier or another
667 /// result already uses the same identifier.
668 /// </exception>
669 public void AddKnownResult(
670 KnownResult knownResult)
671 {
672 ArgumentNullException.ThrowIfNull(
673 knownResult);
674
675 if (string.IsNullOrWhiteSpace(
676 knownResult.ResultId))
677 {
678 throw new ArgumentException(
679 "The known result must have a result " +
680 "identifier.",
681 nameof(knownResult));
682 }
683
685 knownResult.ResultId))
686 {
687 throw new ArgumentException(
688 $"Known-result identifier " +
689 $"'{knownResult.ResultId}' is already used.",
690 nameof(knownResult));
691 }
692
693 KnownResults.Add(
694 knownResult);
695
696 NotifyKnownResultProperties();
697
698 Touch();
699 }
700
701 /// <summary>
702 /// Replaces all known results associated with the
703 /// instance.
704 /// </summary>
705 /// <param name="knownResults">
706 /// New known-result collection.
707 /// </param>
708 /// <exception cref="ArgumentNullException">
709 /// Thrown when <paramref name="knownResults"/> is
710 /// <see langword="null"/>.
711 /// </exception>
712 /// <exception cref="ArgumentException">
713 /// Thrown when the collection contains a null result, a
714 /// missing identifier or duplicate identifiers.
715 /// </exception>
717 IEnumerable<KnownResult> knownResults)
718 {
719 ArgumentNullException.ThrowIfNull(
720 knownResults);
721
722 KnownResult[] materializedResults =
723 knownResults.ToArray();
724
725 if (materializedResults.Any(
726 result =>
727 result is null))
728 {
729 throw new ArgumentException(
730 "The known-result collection cannot contain " +
731 "a null element.",
732 nameof(knownResults));
733 }
734
735 KnownResult? resultWithoutIdentifier =
736 materializedResults.FirstOrDefault(
737 result =>
738 string.IsNullOrWhiteSpace(
739 result.ResultId));
740
741 if (resultWithoutIdentifier is not null)
742 {
743 throw new ArgumentException(
744 "Every known result must have a result " +
745 "identifier.",
746 nameof(knownResults));
747 }
748
749 string[] duplicateIdentifiers =
750 materializedResults
751 .GroupBy(
752 result =>
753 result.ResultId,
754 StringComparer.OrdinalIgnoreCase)
755 .Where(
756 group =>
757 group.Count() > 1)
758 .Select(
759 group =>
760 group.Key)
761 .OrderBy(
762 identifier =>
763 identifier,
764 StringComparer.OrdinalIgnoreCase)
765 .ToArray();
766
767 if (duplicateIdentifiers.Length > 0)
768 {
769 throw new ArgumentException(
770 "Duplicate known-result identifiers: " +
771 string.Join(
772 ", ",
773 duplicateIdentifiers) +
774 ".",
775 nameof(knownResults));
776 }
777
778 KnownResults.Clear();
779
780 KnownResults.AddRange(
781 materializedResults);
782
786 {
788 string.Empty;
789 }
790
791 NotifyKnownResultProperties();
792
793 Touch();
794 }
795
796 /// <summary>
797 /// Finds a known result from its identifier.
798 /// </summary>
799 /// <param name="resultId">
800 /// Result identifier to search for.
801 /// </param>
802 /// <returns>
803 /// Matching known result, or <see langword="null"/> when
804 /// no result uses the supplied identifier.
805 /// </returns>
806 /// <exception cref="InvalidOperationException">
807 /// Thrown when several results use the same identifier.
808 /// </exception>
810 string resultId)
811 {
812 if (string.IsNullOrWhiteSpace(
813 resultId))
814 {
815 return null;
816 }
817
818 string normalizedResultId =
819 resultId.Trim();
820
821 KnownResult[] matches =
823 .Where(
824 result =>
825 result is not null &&
826 string.Equals(
827 result.ResultId,
828 normalizedResultId,
829 StringComparison.OrdinalIgnoreCase))
830 .Take(2)
831 .ToArray();
832
833 return matches.Length switch
834 {
835 0 =>
836 null,
837
838 1 =>
839 matches[0],
840
841 _ =>
842 throw new InvalidOperationException(
843 $"Known-result identifier '{resultId}' " +
844 "is ambiguous in the current instance.")
845 };
846 }
847
848 /// <summary>
849 /// Determines whether a known result uses the supplied
850 /// identifier.
851 /// </summary>
852 /// <param name="resultId">
853 /// Result identifier to search for.
854 /// </param>
855 /// <returns>
856 /// <see langword="true"/> when the identifier exists;
857 /// otherwise, <see langword="false"/>.
858 /// </returns>
860 string resultId)
861 {
862 return FindKnownResult(
863 resultId) is not null;
864 }
865
866 /// <summary>
867 /// Removes a known result from the instance.
868 /// </summary>
869 /// <param name="resultId">
870 /// Identifier of the result to remove.
871 /// </param>
872 /// <returns>
873 /// <see langword="true"/> when a result was removed;
874 /// otherwise, <see langword="false"/>.
875 /// </returns>
876 public bool RemoveKnownResult(
877 string resultId)
878 {
879 KnownResult? knownResult =
881 resultId);
882
883 if (knownResult is null)
884 {
885 return false;
886 }
887
888 bool removed =
889 KnownResults.Remove(
890 knownResult);
891
892 if (!removed)
893 {
894 return false;
895 }
896
897 if (string.Equals(
899 knownResult.ResultId,
900 StringComparison.OrdinalIgnoreCase))
901 {
903 string.Empty;
904 }
905
906 NotifyKnownResultProperties();
907
908 Touch();
909
910 return true;
911 }
912
913 /// <summary>
914 /// Removes every known result from the instance.
915 /// </summary>
916 public void ClearKnownResults()
917 {
918 if (KnownResults.Count == 0 &&
920 {
921 return;
922 }
923
924 KnownResults.Clear();
925
927 string.Empty;
928
929 NotifyKnownResultProperties();
930
931 Touch();
932 }
933
934 /// <summary>
935 /// Selects the best known result from its identifier.
936 /// </summary>
937 /// <param name="resultId">
938 /// Identifier of the result to select.
939 /// </param>
940 /// <exception cref="ArgumentException">
941 /// Thrown when the identifier is empty.
942 /// </exception>
943 /// <exception cref="KeyNotFoundException">
944 /// Thrown when no known result uses the supplied
945 /// identifier.
946 /// </exception>
948 string resultId)
949 {
950 if (string.IsNullOrWhiteSpace(
951 resultId))
952 {
953 throw new ArgumentException(
954 "A best-known-result identifier is required.",
955 nameof(resultId));
956 }
957
958 KnownResult? knownResult =
960 resultId);
961
962 if (knownResult is null)
963 {
964 throw new KeyNotFoundException(
965 $"Known result '{resultId}' does not exist.");
966 }
967
969 knownResult.ResultId;
970
971 Touch();
972 }
973
974 /// <summary>
975 /// Selects a known result as the best known result.
976 /// </summary>
977 /// <param name="knownResult">
978 /// Known result to select.
979 /// </param>
980 /// <exception cref="ArgumentNullException">
981 /// Thrown when <paramref name="knownResult"/> is
982 /// <see langword="null"/>.
983 /// </exception>
985 KnownResult knownResult)
986 {
987 ArgumentNullException.ThrowIfNull(
988 knownResult);
989
991 knownResult.ResultId);
992 }
993
994 /// <summary>
995 /// Clears the best-known-result selection.
996 /// </summary>
998 {
1000 {
1001 return;
1002 }
1003
1005 string.Empty;
1006
1007 Touch();
1008 }
1009
1010 /// <summary>
1011 /// Replaces the supply-chain model and invalidates all
1012 /// derived analysis and recommendation information.
1013 /// </summary>
1014 /// <param name="supplyChain">
1015 /// New supply-chain model.
1016 /// </param>
1017 /// <exception cref="ArgumentNullException">
1018 /// Thrown when <paramref name="supplyChain"/> is
1019 /// <see langword="null"/>.
1020 /// </exception>
1022 SupplyChain supplyChain)
1023 {
1024 ArgumentNullException.ThrowIfNull(
1025 supplyChain);
1026
1027 SupplyChain =
1028 supplyChain;
1029
1031 .MarkAsOutdated();
1032
1034 .MarkAsOutdated();
1035
1037
1038 Touch();
1039 }
1040
1041 /// <summary>
1042 /// Clears derived product-structure, classification and
1043 /// solution-method recommendation information.
1044 /// </summary>
1045 /// <remarks>
1046 /// The supply-chain data and known results are preserved.
1047 /// </remarks>
1049 {
1051 .ClearDetectedAnalysis();
1052
1054 .ClearClassificationResult();
1055
1057
1058 Touch();
1059 }
1060
1061 /// <summary>
1062 /// Clears derived product-structure, classification and
1063 /// recommendation information.
1064 /// </summary>
1065 /// <remarks>
1066 /// This method is an alias of
1067 /// <see cref="ClearDerivedAnalysis"/>.
1068 /// </remarks>
1069 public void ClearAnalysis()
1070 {
1072 }
1073
1074 /// <summary>
1075 /// Adds a descriptive tag to the instance.
1076 /// </summary>
1077 /// <param name="tag">
1078 /// Tag to add.
1079 /// </param>
1080 /// <returns>
1081 /// <see langword="true"/> when the tag was added;
1082 /// otherwise, <see langword="false"/>.
1083 /// </returns>
1084 /// <exception cref="ArgumentException">
1085 /// Thrown when <paramref name="tag"/> is empty.
1086 /// </exception>
1087 public bool AddTag(
1088 string tag)
1089 {
1090 if (string.IsNullOrWhiteSpace(
1091 tag))
1092 {
1093 throw new ArgumentException(
1094 "A non-empty tag is required.",
1095 nameof(tag));
1096 }
1097
1098 string normalizedTag =
1099 tag.Trim();
1100
1101 if (Tags.Contains(
1102 normalizedTag,
1103 StringComparer.OrdinalIgnoreCase))
1104 {
1105 return false;
1106 }
1107
1108 Tags.Add(
1109 normalizedTag);
1110
1111 SortTags();
1112
1113 NotifyTagProperties();
1114
1115 Touch();
1116
1117 return true;
1118 }
1119
1120 /// <summary>
1121 /// Removes a descriptive tag from the instance.
1122 /// </summary>
1123 /// <param name="tag">
1124 /// Tag to remove.
1125 /// </param>
1126 /// <returns>
1127 /// <see langword="true"/> when the tag was removed;
1128 /// otherwise, <see langword="false"/>.
1129 /// </returns>
1130 public bool RemoveTag(
1131 string tag)
1132 {
1133 if (string.IsNullOrWhiteSpace(
1134 tag))
1135 {
1136 return false;
1137 }
1138
1139 string? existingTag =
1140 Tags.FirstOrDefault(
1141 candidate =>
1142 string.Equals(
1143 candidate,
1144 tag.Trim(),
1145 StringComparison.OrdinalIgnoreCase));
1146
1147 if (existingTag is null)
1148 {
1149 return false;
1150 }
1151
1152 bool removed =
1153 Tags.Remove(
1154 existingTag);
1155
1156 if (removed)
1157 {
1158 NotifyTagProperties();
1159
1160 Touch();
1161 }
1162
1163 return removed;
1164 }
1165
1166 /// <summary>
1167 /// Replaces every descriptive tag associated with the
1168 /// instance.
1169 /// </summary>
1170 /// <param name="tags">
1171 /// New tag collection.
1172 /// </param>
1173 /// <exception cref="ArgumentNullException">
1174 /// Thrown when <paramref name="tags"/> is
1175 /// <see langword="null"/>.
1176 /// </exception>
1177 public void ReplaceTags(
1178 IEnumerable<string> tags)
1179 {
1180 ArgumentNullException.ThrowIfNull(
1181 tags);
1182
1183 string[] normalizedTags =
1184 tags
1185 .Where(
1186 tag =>
1187 !string.IsNullOrWhiteSpace(
1188 tag))
1189 .Select(
1190 tag =>
1191 tag.Trim())
1192 .Distinct(
1193 StringComparer.OrdinalIgnoreCase)
1194 .OrderBy(
1195 tag =>
1196 tag,
1197 StringComparer.OrdinalIgnoreCase)
1198 .ToArray();
1199
1200 Tags.Clear();
1201
1202 Tags.AddRange(
1203 normalizedTags);
1204
1205 NotifyTagProperties();
1206
1207 Touch();
1208 }
1209
1210 /// <summary>
1211 /// Removes every descriptive tag from the instance.
1212 /// </summary>
1213 public void ClearTags()
1214 {
1215 if (Tags.Count == 0)
1216 {
1217 return;
1218 }
1219
1220 Tags.Clear();
1221
1222 NotifyTagProperties();
1223
1224 Touch();
1225 }
1226
1227 /// <summary>
1228 /// Updates the last-modification date of the instance.
1229 /// </summary>
1230 public void Touch()
1231 {
1233 DateTime.UtcNow;
1234 }
1235
1236 /// <summary>
1237 /// Determines whether the best-known-result identifier
1238 /// must be serialized.
1239 /// </summary>
1240 /// <returns>
1241 /// <see langword="true"/> when an identifier is present;
1242 /// otherwise, <see langword="false"/>.
1243 /// </returns>
1245 {
1246 return HasBestKnownResultId;
1247 }
1248
1249 /// <summary>
1250 /// Determines whether the known-result collection must be
1251 /// serialized.
1252 /// </summary>
1253 /// <returns>
1254 /// <see langword="true"/> when the collection is not
1255 /// empty; otherwise, <see langword="false"/>.
1256 /// </returns>
1258 {
1259 return HasKnownResults;
1260 }
1261
1262 /// <summary>
1263 /// Determines whether the tag collection must be
1264 /// serialized.
1265 /// </summary>
1266 /// <returns>
1267 /// <see langword="true"/> when the collection is not
1268 /// empty; otherwise, <see langword="false"/>.
1269 /// </returns>
1271 {
1272 return HasTags;
1273 }
1274
1275 /// <summary>
1276 /// Determines whether the creation date must be serialized.
1277 /// </summary>
1278 /// <returns>
1279 /// <see langword="true"/> when a creation date exists;
1280 /// otherwise, <see langword="false"/>.
1281 /// </returns>
1283 {
1284 return HasCreationDate;
1285 }
1286
1287 /// <summary>
1288 /// Determines whether the modification date must be
1289 /// serialized.
1290 /// </summary>
1291 /// <returns>
1292 /// <see langword="true"/> when a modification date exists;
1293 /// otherwise, <see langword="false"/>.
1294 /// </returns>
1296 {
1297 return HasModificationDate;
1298 }
1299
1300 /// <inheritdoc/>
1301 public override string ToString()
1302 {
1303 string instanceDescription =
1304 HasName
1305 ? Name
1307 ? InstanceId
1308 : "Unnamed lot-sizing instance";
1309
1310 return
1311 $"{instanceDescription}; " +
1312 $"horizon {PlanningHorizon.ToString(
1313 CultureInfo.InvariantCulture)}; " +
1314 $"{KnownResultCount.ToString(
1315 CultureInfo.InvariantCulture)} known result(s)";
1316 }
1317
1318 private static DateTime? ConvertToUtc(
1319 DateTime? value)
1320 {
1321 if (!value.HasValue)
1322 {
1323 return null;
1324 }
1325
1326 DateTime dateTimeValue =
1327 value.Value;
1328
1329 return dateTimeValue.Kind switch
1330 {
1331 DateTimeKind.Utc =>
1332 dateTimeValue,
1333
1334 DateTimeKind.Local =>
1335 dateTimeValue.ToUniversalTime(),
1336
1337 _ =>
1338 DateTime.SpecifyKind(
1339 dateTimeValue,
1340 DateTimeKind.Utc)
1341 };
1342 }
1343
1344 private void SortTags()
1345 {
1346 string[] orderedTags =
1347 Tags
1348 .OrderBy(
1349 tag =>
1350 tag,
1351 StringComparer.OrdinalIgnoreCase)
1352 .ToArray();
1353
1354 Tags.Clear();
1355
1356 Tags.AddRange(
1357 orderedTags);
1358 }
1359
1360 private void NotifyKnownResultProperties()
1361 {
1362 OnPropertyChanged(
1363 nameof(KnownResults));
1364
1365 OnPropertyChanged(
1366 nameof(HasKnownResults));
1367
1368 OnPropertyChanged(
1369 nameof(KnownResultCount));
1370
1371 NotifyBestKnownResultProperties();
1372 }
1373
1374 private void NotifyBestKnownResultProperties()
1375 {
1376 OnPropertyChanged(
1377 nameof(BestKnownResultId));
1378
1379 OnPropertyChanged(
1380 nameof(HasBestKnownResultId));
1381
1382 OnPropertyChanged(
1383 nameof(BestKnownResult));
1384
1385 OnPropertyChanged(
1386 nameof(HasBestKnownResult));
1387
1388 OnPropertyChanged(
1390 }
1391
1392 private void NotifyTagProperties()
1393 {
1394 OnPropertyChanged(
1395 nameof(Tags));
1396
1397 OnPropertyChanged(
1398 nameof(HasTags));
1399 }
1400}
Stores the persistent result of the automatic classification of a lot-sizing problem instance.
Adds explicit closed-loop return streams to a lot-sizing instance without changing the historical Sup...
void ClearBestKnownResult()
Clears the best-known-result selection.
void SetBestKnownResult(string resultId)
Selects the best known result from its identifier.
bool HasTags
Gets a value indicating whether at least one tag has been recorded.
bool HasDescription
Gets a value indicating whether a description has been recorded.
bool ShouldSerializeKnownResults()
Determines whether the known-result collection must be serialized.
string InstanceId
Gets or sets the stable identifier of the instance.
string Comment
Gets or sets an optional explanatory comment.
string FormatVersion
Gets or sets the format version of the serialized instance.
bool ShouldSerializeCreatedAtUtc()
Determines whether the creation date must be serialized.
void AddKnownResult(KnownResult knownResult)
Adds a known result to the instance.
string Description
Gets or sets the human-readable description of the problem instance.
bool RemoveTag(string tag)
Removes a descriptive tag from the instance.
LotSizingProblemClassification ProblemClassification
Gets or sets the automatic lot-sizing problem classification.
List< string > Tags
Gets the descriptive tags associated with the instance.
bool HasBestKnownResult
Gets a value indicating whether the selected best known result exists in KnownResults.
void ReplaceSupplyChain(SupplyChain supplyChain)
Replaces the supply-chain model and invalidates all derived analysis and recommendation information.
List< KnownResult > KnownResults
Gets the known results associated with the instance.
void ClearSolutionMethodRecommendationReport()
Clears the complete solution-method recommendation report.
void Touch()
Updates the last-modification date of the instance.
KnownResult? FindKnownResult(string resultId)
Finds a known result from its identifier.
bool ShouldSerializeTags()
Determines whether the tag collection must be serialized.
bool HasEligibleBestKnownResult
Gets a value indicating whether the selected best-known result is eligible for that role.
bool HasModificationDate
Gets a value indicating whether a modification date has been recorded.
void ReplaceKnownResults(IEnumerable< KnownResult > knownResults)
Replaces all known results associated with the instance.
void ClearTags()
Removes every descriptive tag from the instance.
bool AddTag(string tag)
Adds a descriptive tag to the instance.
bool HasCreationDate
Gets a value indicating whether a creation date has been recorded.
void ClearAnalysis()
Clears derived product-structure, classification and recommendation information.
bool HasKnownResults
Gets a value indicating whether at least one known result has been recorded.
bool ShouldSerializeModifiedAtUtc()
Determines whether the modification date must be serialized.
KnownResult? BestKnownResult
Gets the result selected as the best known result.
bool HasInstanceId
Gets a value indicating whether the instance has a stable identifier.
void SetBestKnownResult(KnownResult knownResult)
Selects a known result as the best known result.
DateTime? ModifiedAtUtc
Gets or sets the UTC date and time at which the instance was last modified.
void ClearKnownResults()
Removes every known result from the instance.
LotSizingInstance(SupplyChain supplyChain, string name)
Initializes a named lot-sizing problem instance.
bool RemoveKnownResult(string resultId)
Removes a known result from the instance.
DateTime? CreatedAtUtc
Gets or sets the UTC date and time at which the instance was created.
bool ShouldSerializeBestKnownResultId()
Determines whether the best-known-result identifier must be serialized.
bool ContainsKnownResultId(string resultId)
Determines whether a known result uses the supplied identifier.
string SourceInformation
Gets or sets information identifying the source of the instance.
LotSizingInstance(SupplyChain supplyChain)
Initializes a lot-sizing problem instance from a supply-chain model.
void ClearDerivedAnalysis()
Clears derived product-structure, classification and solution-method recommendation information.
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.
void ReplaceTags(IEnumerable< string > tags)
Replaces every descriptive tag associated with the instance.
LotSizingInstance(string instanceId, SupplyChain supplyChain, string name="")
Initializes an identified lot-sizing problem instance.
int PlanningHorizon
Gets the planning horizon of the supply-chain model.
LotSizingInstance()
Initializes an empty lot-sizing problem instance.
bool HasCreatedBy
Gets a value indicating whether creator information has been recorded.
bool HasComment
Gets a value indicating whether an explanatory comment has been recorded.
ProductStructureDescriptor ProductStructure
Gets or sets the product-structure descriptor associated with the supply-chain model.
string CreatedBy
Gets or sets the person, organization or software component that created the instance.
DateTime? LastModifiedAtUtc
Gets the last-modification date of the instance.
string Name
Gets or sets the human-readable name of the instance.
int KnownResultCount
Gets the number of known results associated with the instance.
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.
Describes the declared and automatically detected structure of the product bill-of-materials graph of...
Represents a known result associated with a lot-sizing problem instance.
string ResultId
Gets or sets the stable identifier of this result.