LotSizingDataModel.Checker 2.0.1
Reusable validation engine for structural, mathematical and objective consistency checks.
Loading...
Searching...
No Matches
SolutionVariableDomainChecker.cs
Go to the documentation of this file.
5using LotSizingDataModel.Core.Common;
6using LotSizingDataModel.Solution;
7using LotSizingDataModel.Solution.Decisions;
8
10
11/// <summary>
12/// Performs generic numerical-domain validation of all decision values
13/// stored in a <see cref="LotSizingSolution"/>.
14/// </summary>
15/// <remarks>
16/// <para>
17/// This checker does not verify mathematical constraints. It verifies only
18/// the intrinsic numerical domain of each solution decision: finite values,
19/// non-negativity, binary values, and non-negative integer counters.
20/// </para>
21/// <para>
22/// Small negative floating-point residuals within
23/// <see cref="SolutionCheckOptions.ZeroTolerance"/> are accepted as numerical
24/// zero. Binary integer series are accepted only when their values are 0 or 1.
25/// </para>
26/// </remarks>
27public sealed class SolutionVariableDomainChecker :
29{
30 /// <inheritdoc/>
32 LotSizingSolution solution,
34 {
35 ArgumentNullException.ThrowIfNull(solution);
36 ArgumentNullException.ThrowIfNull(options);
37
38 options.EnsureValid();
39
40 var result =
42 {
43 Level =
44 SolutionCheckLevel.Feasibility,
45 IsStructurallyValid =
46 true,
47 AreVariableDomainsValid =
48 true,
49 IsFeasible =
50 false,
51 IsObjectiveConsistent =
52 false
53 };
54
55 try
56 {
57 CheckProductionDecisions(
58 solution,
59 options,
60 result);
61
62 CheckInventoryDecisions(
63 solution,
64 options,
65 result);
66
67 CheckTransportDecisions(
68 solution,
69 options,
70 result);
71
72 CheckPurchaseDecisions(
73 solution,
74 options,
75 result);
76
77 CheckDistributionDecisions(
78 solution,
79 options,
80 result);
81
82 CheckWorkCenterCapacityDecisions(
83 solution,
84 options,
85 result);
86
87 CheckWarehouseCapacityDecisions(
88 solution,
89 options,
90 result);
91
92 CheckTransportResourceCapacityDecisions(
93 solution,
94 options,
95 result);
96 }
97 catch (Exception exception)
98 {
99 result.AddIssue(
101 {
102 Severity =
104 Kind =
105 SolutionCheckIssueKind.CheckFailure,
106 Message =
107 "Variable-domain checking failed: " +
108 exception.Message
109 });
110 }
111
112 result.AreVariableDomainsValid =
113 !result.Issues.Any(
114 issue =>
115 issue.Severity ==
117
118 return result;
119 }
120
121 private static void CheckProductionDecisions(
122 LotSizingSolution solution,
123 SolutionCheckOptions options,
124 SolutionCheckResult result)
125 {
126 foreach (
127 ProductionDecision decision
128 in solution.ProductionDecisions)
129 {
130 string key =
131 $"production|routing={decision.RoutingId}";
132
133 CheckNonNegativeFiniteSeries(
134 decision.Quantities,
135 key,
136 "quantities",
137 options,
138 result);
139
140 CheckBinarySeries(
141 decision.Setups,
142 key,
143 "setups",
144 result);
145
146 CheckNonNegativeIntegerSeries(
147 decision.LotMultipleCounts,
148 key,
149 "lotMultipleCounts",
150 result);
151 }
152 }
153
154 private static void CheckInventoryDecisions(
155 LotSizingSolution solution,
156 SolutionCheckOptions options,
157 SolutionCheckResult result)
158 {
159 foreach (
160 InventoryDecision decision
161 in solution.InventoryDecisions)
162 {
163 string key =
164 $"inventory|item={decision.ItemId}";
165
166 CheckNonNegativeFiniteSeries(
167 decision.Levels,
168 key,
169 "levels",
170 options,
171 result);
172
173 CheckNonNegativeFiniteSeries(
174 decision.SafetyStockViolations,
175 key,
176 "safetyStockViolations",
177 options,
178 result);
179
180 CheckBinarySeries(
181 decision.Setups,
182 key,
183 "setups",
184 result);
185
186 CheckNonNegativeFiniteSeries(
187 decision.AdditionalCapacityUsed,
188 key,
189 "additionalCapacityUsed",
190 options,
191 result);
192 }
193 }
194
195 private static void CheckTransportDecisions(
196 LotSizingSolution solution,
197 SolutionCheckOptions options,
198 SolutionCheckResult result)
199 {
200 foreach (
201 TransportDecision decision
202 in solution.TransportDecisions)
203 {
204 string key =
205 $"transport|item={decision.ItemId}" +
206 $"|resource={decision.TransportResourceId}";
207
208 CheckNonNegativeFiniteSeries(
209 decision.TransportedQuantities,
210 key,
211 "transportedQuantities",
212 options,
213 result);
214
215 CheckBinarySeries(
216 decision.Setups,
217 key,
218 "setups",
219 result);
220
221 CheckNonNegativeFiniteSeries(
222 decision.AdditionalCapacityUsed,
223 key,
224 "additionalCapacityUsed",
225 options,
226 result);
227 }
228 }
229
230 private static void CheckPurchaseDecisions(
231 LotSizingSolution solution,
232 SolutionCheckOptions options,
233 SolutionCheckResult result)
234 {
235 foreach (
236 PurchaseDecision decision
237 in solution.PurchaseDecisions)
238 {
239 string key =
240 $"purchase|supplier={decision.SupplierId}" +
241 $"|item={decision.ItemId}";
242
243 CheckNonNegativeFiniteSeries(
244 decision.PurchasedQuantities,
245 key,
246 "purchasedQuantities",
247 options,
248 result);
249 }
250 }
251
252 private static void CheckDistributionDecisions(
253 LotSizingSolution solution,
254 SolutionCheckOptions options,
255 SolutionCheckResult result)
256 {
257 foreach (
258 DistributionDecision decision
259 in solution.DistributionDecisions)
260 {
261 string key =
262 $"distribution|center={decision.DistributionCenterId}" +
263 $"|item={decision.ItemId}";
264
265 CheckNonNegativeFiniteSeries(
266 decision.DeliveredQuantities,
267 key,
268 "deliveredQuantities",
269 options,
270 result);
271
272 CheckNonNegativeFiniteSeries(
273 decision.BacklogLevels,
274 key,
275 "backlogLevels",
276 options,
277 result);
278
279 CheckNonNegativeFiniteSeries(
280 decision.ShortageQuantities,
281 key,
282 "shortageQuantities",
283 options,
284 result);
285 }
286 }
287
288 private static void CheckWorkCenterCapacityDecisions(
289 LotSizingSolution solution,
290 SolutionCheckOptions options,
291 SolutionCheckResult result)
292 {
293 foreach (
294 WorkCenterCapacityDecision decision
295 in solution.WorkCenterCapacityDecisions)
296 {
297 string key =
298 $"workCenterCapacity" +
299 $"|plant={decision.WorkCenter.PlantId}" +
300 $"|workCenter={decision.WorkCenter.WorkCenterId}";
301
302 CheckBinarySeries(
303 decision.Activations,
304 key,
305 "activations",
306 result);
307
308 CheckNonNegativeFiniteSeries(
309 decision.AdditionalCapacityUsed,
310 key,
311 "additionalCapacityUsed",
312 options,
313 result);
314 }
315 }
316
317 private static void CheckWarehouseCapacityDecisions(
318 LotSizingSolution solution,
319 SolutionCheckOptions options,
320 SolutionCheckResult result)
321 {
322 foreach (
323 WarehouseCapacityDecision decision
324 in solution.WarehouseCapacityDecisions)
325 {
326 string key =
327 "warehouseCapacity";
328
329 CheckBinarySeries(
330 decision.Activations,
331 key,
332 "activations",
333 result);
334
335 CheckNonNegativeFiniteSeries(
336 decision.AdditionalCapacityUsed,
337 key,
338 "additionalCapacityUsed",
339 options,
340 result);
341 }
342 }
343
344 private static void CheckTransportResourceCapacityDecisions(
345 LotSizingSolution solution,
346 SolutionCheckOptions options,
347 SolutionCheckResult result)
348 {
349 foreach (
350 TransportResourceCapacityDecision decision
351 in solution.TransportResourceCapacityDecisions)
352 {
353 string key =
354 $"transportResourceCapacity" +
355 $"|resource={decision.TransportResourceId}";
356
357 CheckBinarySeries(
358 decision.Activations,
359 key,
360 "activations",
361 result);
362
363 CheckNonNegativeFiniteSeries(
364 decision.AdditionalCapacityUsed,
365 key,
366 "additionalCapacityUsed",
367 options,
368 result);
369 }
370 }
371
372 private static void CheckNonNegativeFiniteSeries(
373 DoubleTimeSeries series,
374 string domainKey,
375 string seriesName,
376 SolutionCheckOptions options,
377 SolutionCheckResult result)
378 {
379 ArgumentNullException.ThrowIfNull(series);
380
381 for (int period = 1;
382 period <= series.PeriodCount;
383 period++)
384 {
385 double value =
386 series[period];
387
388 if (!double.IsFinite(value))
389 {
390 AddDomainError(
391 domainKey,
392 seriesName,
393 period,
394 value,
395 "Value must be finite.",
396 result);
397
398 continue;
399 }
400
401 if (value < -options.ZeroTolerance)
402 {
403 AddDomainError(
404 domainKey,
405 seriesName,
406 period,
407 value,
408 "Value must be non-negative.",
409 result);
410 }
411 }
412 }
413
414 private static void CheckBinarySeries(
415 IntegerTimeSeries series,
416 string domainKey,
417 string seriesName,
418 SolutionCheckResult result)
419 {
420 ArgumentNullException.ThrowIfNull(series);
421
422 for (int period = 1;
423 period <= series.PeriodCount;
424 period++)
425 {
426 int value =
427 series[period];
428
429 if (value is not 0 and not 1)
430 {
431 AddDomainError(
432 domainKey,
433 seriesName,
434 period,
435 value,
436 "Binary value must be 0 or 1.",
437 result);
438 }
439 }
440 }
441
442 private static void CheckNonNegativeIntegerSeries(
443 IntegerTimeSeries series,
444 string domainKey,
445 string seriesName,
446 SolutionCheckResult result)
447 {
448 ArgumentNullException.ThrowIfNull(series);
449
450 for (int period = 1;
451 period <= series.PeriodCount;
452 period++)
453 {
454 int value =
455 series[period];
456
457 if (value < 0)
458 {
459 AddDomainError(
460 domainKey,
461 seriesName,
462 period,
463 value,
464 "Integer value must be non-negative.",
465 result);
466 }
467 }
468 }
469
470 private static void AddDomainError(
471 string domainKey,
472 string seriesName,
473 int period,
474 double actualValue,
475 string explanation,
476 SolutionCheckResult result)
477 {
478 result.AddIssue(
479 new SolutionCheckIssue
480 {
481 Severity =
483
484 Kind =
485 SolutionCheckIssueKind.VariableDomain,
486
487 DomainKey =
488 $"{domainKey}|period={period}",
489
490 ActualValue =
491 actualValue,
492
493 Message =
494 $"{seriesName}[{period}] = " +
495 $"{actualValue:G17}. {explanation}"
496 });
497 }
498}
Configures generic lot-sizing solution checking.
double ZeroTolerance
Gets or sets the absolute tolerance used to identify numerical zero.
Performs generic numerical-domain validation of all decision values stored in a LotSizingSolution.
SolutionCheckResult Check(LotSizingSolution solution, SolutionCheckOptions options)
Checks all numerical decision values contained in a candidate solution.Detailed variable-domain check...
Describes one issue detected by a lot-sizing solution checker.
Contains the complete result of checking one lot-sizing solution.
IReadOnlyList< SolutionCheckIssue > Issues
Gets all checker diagnostics.
void AddIssue(SolutionCheckIssue issue)
Adds one issue to the result.
Defines numerical-domain validation for lot-sizing solution decisions.
SolutionCheckLevel
Defines how deeply a lot-sizing solution must be checked.
SolutionCheckSeverity
Defines the severity of a solution-check issue.
SolutionCheckIssueKind
Identifies the category of an issue detected while checking a solution.