LotSizingDataModel.Instance 2.0.1
Lot-sizing instance representation, descriptors and problem characterization.
Loading...
Searching...
No Matches
WolseyHistoricalSemanticsAnalyzer.cs
Go to the documentation of this file.
1using System.Reflection;
2using LotSizingDataModel.Core;
3using LotSizingDataModel.Core.DecisionModel.Constraints;
4using LotSizingDataModel.Core.DecisionModel.Scheduling;
5using LotSizingDataModel.Core.PhysicalModel;
6using LotSizingDataModel.Core.Relationships;
8
10
11/// <summary>
12/// Projects generic LotSizingDataModel semantics onto Wolsey's historical dimensions.
13/// </summary>
14/// <remarks>
15/// Detection is deliberately conservative. A source-declared historical code is never
16/// used as detected evidence. IM/VM are preserved only as source labels.
17/// </remarks>
19{
21 LotSizingInstance instance)
22 {
23 ArgumentNullException.ThrowIfNull(instance);
24
25 SupplyChain chain = instance.SupplyChain;
26
27 bool zeroFull =
28 DetectZeroFullProduction(instance);
29
30 InitialInventoryDecisionMode[] modes =
31 chain.Inventories
32 .Select(
33 inventory =>
34 inventory.InitialInventoryDecisionMode)
35 .Distinct()
36 .ToArray();
37
39 DetectProblemVariant(
40 zeroFull,
41 modes);
42
44 {
45 ProblemVariant = problem,
46 CapacityVariant = DetectCapacityVariant(chain),
47 BucketVariant = DetectBucketVariant(chain),
48 NumberOfMachines = chain.WorkCenters.Count(),
49 NumberOfItems = chain.Items.Count,
50 NumberOfPeriods = chain.PlanningHorizon,
51 NumberOfLevels = DetectNumberOfLevels(chain),
52 HasSalesOption = chain.SalesOptions.Count > 0,
53 HasSetupTimes =
54 chain.ProductionCharacteristics.Any(
55 characteristic =>
56 characteristic.SetupTime is not null),
57 DeclaredMachineLabel =
58 instance.HistoricalSemantics?
61 };
62 }
63
64 public static IReadOnlyList<string> ValidateHistoricalSemantics(
65 LotSizingInstance instance)
66 {
67 ArgumentNullException.ThrowIfNull(instance);
68
69 var issues = new List<string>();
70 SupplyChain chain = instance.SupplyChain;
71
72 foreach (Inventory inventory in chain.Inventories)
73 {
74 if (!inventory.HasValidInitialInventoryDecisionSemantics)
75 {
76 issues.Add(
77 $"Inventory item {inventory.ItemId} has a non-zero fixed " +
78 "InitialInventory while its mode requires zero.");
79 }
80 }
81
82 foreach (SalesOption salesOption in chain.SalesOptions)
83 {
84 if (!salesOption.IsInternallyValid)
85 {
86 issues.Add(
87 $"SalesOption item {salesOption.ItemId} / distribution center " +
88 $"{salesOption.DistributionCenterId} is internally invalid.");
89 }
90
91 if (!chain.Items.Any(item => item.Id == salesOption.ItemId))
92 {
93 issues.Add(
94 $"SalesOption references unknown item {salesOption.ItemId}.");
95 }
96
97 if (!chain.DistributionCenters.Any(
98 center =>
99 center.Id ==
100 salesOption.DistributionCenterId))
101 {
102 issues.Add(
103 "SalesOption references unknown distribution center " +
104 $"{salesOption.DistributionCenterId}.");
105 }
106 }
107
108 return issues;
109 }
110
111 private static WolseyDetectedProblemVariant DetectProblemVariant(
112 bool zeroFull,
113 IReadOnlyCollection<InitialInventoryDecisionMode> modes)
114 {
115 if (!zeroFull || modes.Count != 1)
116 {
117 return WolseyDetectedProblemVariant.Undetermined;
118 }
119
120 return modes.Single() switch
121 {
122 InitialInventoryDecisionMode.VariableDecision =>
124
125 InitialInventoryDecisionMode.AbsentFixedZero =>
127
128 _ =>
129 WolseyDetectedProblemVariant.Undetermined
130 };
131 }
132
133 private static bool DetectZeroFullProduction(
134 LotSizingInstance instance)
135 {
136 bool schedulingEvidence =
137 instance.SupplyChain.WorkCenters.Any(
138 workCenter =>
139 workCenter.SchedulingProfile is not null &&
140 workCenter.SchedulingProfile.SmallBucketProductionMode ==
141 SmallBucketProductionMode.AllOrNothing);
142
143 if (schedulingEvidence)
144 {
145 return true;
146 }
147
148 // Reuse existing generic feature semantics without a compile-time
149 // dependency on the exact alpha.14 property name.
150 object features =
151 LotSizingProblemFeatureExtractor.Extract(
152 instance.SupplyChain);
153
154 string[] acceptedPropertyNames =
155 [
156 "HasZeroFullProduction",
157 "HasZeroFullCapacityProduction",
158 "UsesZeroFullProduction",
159 "UsesZeroFullCapacityProduction"
160 ];
161
162 Type type = features.GetType();
163
164 foreach (string propertyName in acceptedPropertyNames)
165 {
166 PropertyInfo? property =
167 type.GetProperty(
168 propertyName,
169 BindingFlags.Instance |
170 BindingFlags.Public);
171
172 if (property?.PropertyType == typeof(bool) &&
173 property.GetValue(features) is true)
174 {
175 return true;
176 }
177 }
178
179 return false;
180 }
181
182 private static WolseyDetectedCapacityVariant DetectCapacityVariant(
183 SupplyChain chain)
184 {
185 WorkCenter[] workCenters =
186 chain.WorkCenters.ToArray();
187
188 if (workCenters.Length == 0)
189 {
190 return WolseyDetectedCapacityVariant.Undetermined;
191 }
192
193 CapacityConstraint[] capacities =
194 workCenters
195 .Select(
196 workCenter =>
197 workCenter.CapacityConstraint)
198 .Where(
199 capacity =>
200 capacity is not null)
201 .Cast<CapacityConstraint>()
202 .ToArray();
203
204 if (capacities.Length == 0)
205 {
207 }
208
209 if (capacities.Length != workCenters.Length)
210 {
211 return WolseyDetectedCapacityVariant.Undetermined;
212 }
213
214 bool allConstant = true;
215
216 foreach (CapacityConstraint capacity in capacities)
217 {
218 if (capacity.PlanningHorizon != chain.PlanningHorizon)
219 {
220 return WolseyDetectedCapacityVariant.Undetermined;
221 }
222
223 if (chain.PlanningHorizon <= 1)
224 {
225 continue;
226 }
227
228 double first = capacity[1];
229
230 for (int period = 2;
231 period <= chain.PlanningHorizon;
232 period++)
233 {
234 if (!capacity[period].Equals(first))
235 {
236 allConstant = false;
237 break;
238 }
239 }
240 }
241
242 return allConstant
243 ? WolseyDetectedCapacityVariant.CC
245 }
246
247 private static WolseyDetectedBucketVariant DetectBucketVariant(
248 SupplyChain chain)
249 {
250 WorkCenter[] workCenters =
251 chain.WorkCenters.ToArray();
252
253 bool jointCapacity =
254 workCenters.Any(
255 workCenter =>
256 workCenter.CapacityConstraint is not null &&
257 CountItemsUsingWorkCenter(
258 chain,
259 workCenter) > 1);
260
261 ProductionSchedulingProfile[] profiles =
262 workCenters
263 .Where(
264 workCenter =>
265 workCenter.SchedulingProfile is not null)
266 .Select(
267 workCenter =>
268 workCenter.SchedulingProfile!)
269 .ToArray();
270
271 if (profiles.Length == 0)
272 {
273 return WolseyDetectedBucketVariant.Undetermined;
274 }
275
276 if (profiles.All(
277 profile =>
278 profile.BucketMode ==
279 SchedulingBucketMode.SmallBucket) &&
280 profiles.All(
281 profile =>
282 profile.MaximumSetupCount is not null))
283 {
284 int maximumSetupCount = 0;
285
286 foreach (ProductionSchedulingProfile profile in profiles)
287 {
288 if (profile.MaximumSetupCount!.PlanningHorizon !=
289 chain.PlanningHorizon)
290 {
291 return WolseyDetectedBucketVariant.Undetermined;
292 }
293
294 for (int period = 1;
295 period <= chain.PlanningHorizon;
296 period++)
297 {
298 maximumSetupCount =
299 Math.Max(
300 maximumSetupCount,
301 profile.MaximumSetupCount.GetCount(period));
302 }
303 }
304
305 if (maximumSetupCount <= 1)
306 {
308 }
309
310 if (maximumSetupCount <= 2)
311 {
313 }
314
315 return jointCapacity
316 ? WolseyDetectedBucketVariant.BB
317 : WolseyDetectedBucketVariant.Undetermined;
318 }
319
320 if (profiles.Any(
321 profile =>
322 profile.BucketMode ==
323 SchedulingBucketMode.BigBucket) &&
324 jointCapacity)
325 {
327 }
328
329 return WolseyDetectedBucketVariant.Undetermined;
330 }
331
332 private static int CountItemsUsingWorkCenter(
333 SupplyChain chain,
334 WorkCenter workCenter)
335 {
336 return chain.ProductionRoutings
337 .Where(
338 routing =>
339 routing.WorkCenters.Any(
340 reference =>
341 reference.WorkCenterId ==
342 workCenter.Id))
343 .Select(
344 routing =>
345 routing.ItemId)
346 .Distinct()
347 .Count();
348 }
349
350 private static int? DetectNumberOfLevels(
351 SupplyChain chain)
352 {
353 if (chain.Items.Count == 0)
354 {
355 return 0;
356 }
357
358 if (chain.ComponentRequirements.Count == 0)
359 {
360 return 1;
361 }
362
363 var children =
364 chain.ComponentRequirements
365 .GroupBy(
366 requirement =>
367 requirement.ParentItemId)
368 .ToDictionary(
369 group =>
370 group.Key,
371 group =>
372 group
373 .Select(
374 requirement =>
375 requirement.ComponentItemId)
376 .Distinct()
377 .ToArray());
378
379 var state = new Dictionary<int, int>();
380 var memo = new Dictionary<int, int>();
381
382 int? Visit(int itemId)
383 {
384 if (memo.TryGetValue(itemId, out int cached))
385 {
386 return cached;
387 }
388
389 if (state.TryGetValue(itemId, out int current) &&
390 current == 1)
391 {
392 return null;
393 }
394
395 state[itemId] = 1;
396 int depth = 1;
397
398 if (children.TryGetValue(itemId, out int[]? childIds))
399 {
400 foreach (int childId in childIds)
401 {
402 int? childDepth = Visit(childId);
403
404 if (childDepth is null)
405 {
406 return null;
407 }
408
409 depth =
410 Math.Max(
411 depth,
412 1 + childDepth.Value);
413 }
414 }
415
416 state[itemId] = 2;
417 memo[itemId] = depth;
418 return depth;
419 }
420
421 int maximum = 1;
422
423 foreach (var item in chain.Items)
424 {
425 int? depth = Visit(item.Id);
426
427 if (depth is null)
428 {
429 return null;
430 }
431
432 maximum = Math.Max(maximum, depth.Value);
433 }
434
435 return maximum;
436 }
437}
Projects generic LotSizingDataModel semantics onto Wolsey's historical dimensions.
Adds explicit closed-loop return streams to a lot-sizing instance without changing the historical Sup...
SupplyChain SupplyChain
Gets or sets the supply-chain model represented by the instance.
HistoricalSemanticsMetadata? HistoricalSemantics
Gets or sets historical source metadata.
@ WorkCenter
The classification match applies to the decisions associated with one work center.