LotSizingDataModel.Checker 2.0.1
Reusable validation engine for structural, mathematical and objective consistency checks.
Loading...
Searching...
No Matches
LotSizingSolutionChecker.cs
Go to the documentation of this file.
10using LotSizingDataModel.Instance;
11using LotSizingDataModel.Solution;
12using LotSizingDataModel.Solver.Formulation;
13using LotSizingDataModel.Solver.Modeling;
14
16
17/// <summary>
18/// Orchestrates the complete, solver-independent validation of a
19/// <see cref="LotSizingSolution"/> against a <see cref="LotSizingInstance"/>.
20/// </summary>
21/// <remarks>
22/// <para>
23/// The checker executes the validation pipeline in increasing order of
24/// dependency: structural consistency, numerical variable domains,
25/// mathematical feasibility, and objective consistency.
26/// </para>
27/// <para>
28/// For mathematical checks, the mathematical model is either generated once
29/// from the instance by the configured formulation registry or supplied by
30/// the caller when it has already been built. No native solver is created or
31/// invoked.
32/// </para>
33/// <para>
34/// Structural errors stop the pipeline by default because subsequent
35/// projection may no longer have a well-defined candidate value for every
36/// mathematical variable. This behavior can be changed through
37/// <see cref="SolutionCheckOptions.ContinueAfterStructuralErrors"/>.
38/// </para>
39/// </remarks>
40public sealed class LotSizingSolutionChecker :
42{
43 private readonly ISolutionStructuralChecker _structuralChecker;
44 private readonly ISolutionVariableDomainChecker _variableDomainChecker;
45 private readonly IMathematicalFeasibilityChecker _feasibilityChecker;
46 private readonly IMathematicalObjectiveChecker _objectiveChecker;
47 private readonly IMathematicalModelBuildService _modelBuildService;
48 private readonly MathematicalModelFormulationRegistry _formulationRegistry;
49 private readonly string _formulationId;
50
51 /// <summary>
52 /// Initializes the checker with the standard LotSizingDataModel
53 /// formulation and the standard checker components.
54 /// </summary>
56 {
57 var projector =
59
60 _structuralChecker =
62
63 _variableDomainChecker =
65
66 _feasibilityChecker =
68 projector);
69
70 _objectiveChecker =
72 projector);
73
74 _modelBuildService =
75 new MathematicalModelBuildService();
76
77 _formulationRegistry =
78 LotSizingFormulationRegistryFactory.CreateDefault();
79
80 _formulationId =
81 string.Empty;
82 }
83
84 /// <summary>
85 /// Initializes the checker with explicitly supplied components.
86 /// </summary>
87 /// <param name="structuralChecker">
88 /// Structural solution checker.
89 /// </param>
90 /// <param name="variableDomainChecker">
91 /// Numerical-domain checker.
92 /// </param>
93 /// <param name="feasibilityChecker">
94 /// Mathematical-feasibility checker.
95 /// </param>
96 /// <param name="objectiveChecker">
97 /// Mathematical-objective checker.
98 /// </param>
99 /// <param name="modelBuildService">
100 /// Solver-independent mathematical-model build service.
101 /// </param>
102 /// <param name="formulationRegistry">
103 /// Registry containing the formulation used for independent checking.
104 /// </param>
105 /// <param name="formulationId">
106 /// Identifier of the formulation to use when constructing the model.
107 /// </param>
109 ISolutionStructuralChecker structuralChecker,
110 ISolutionVariableDomainChecker variableDomainChecker,
111 IMathematicalFeasibilityChecker feasibilityChecker,
112 IMathematicalObjectiveChecker objectiveChecker,
113 IMathematicalModelBuildService modelBuildService,
114 MathematicalModelFormulationRegistry formulationRegistry,
115 string formulationId)
116 {
117 _structuralChecker =
118 structuralChecker ??
119 throw new ArgumentNullException(
120 nameof(structuralChecker));
121
122 _variableDomainChecker =
123 variableDomainChecker ??
124 throw new ArgumentNullException(
125 nameof(variableDomainChecker));
126
127 _feasibilityChecker =
128 feasibilityChecker ??
129 throw new ArgumentNullException(
130 nameof(feasibilityChecker));
131
132 _objectiveChecker =
133 objectiveChecker ??
134 throw new ArgumentNullException(
135 nameof(objectiveChecker));
136
137 _modelBuildService =
138 modelBuildService ??
139 throw new ArgumentNullException(
140 nameof(modelBuildService));
141
142 _formulationRegistry =
143 formulationRegistry ??
144 throw new ArgumentNullException(
145 nameof(formulationRegistry));
146
147 if (string.IsNullOrWhiteSpace(
148 formulationId))
149 {
150 throw new ArgumentException(
151 "The mathematical formulation identifier cannot be empty.",
152 nameof(formulationId));
153 }
154
155 _formulationId =
156 formulationId;
157 }
158
159 /// <inheritdoc/>
160 public Task<SolutionCheckResult> CheckAsync(
161 LotSizingInstance instance,
162 LotSizingSolution solution,
163 SolutionCheckOptions? options = null,
164 CancellationToken cancellationToken = default)
165 {
166 return CheckCoreAsync(
167 instance,
168 solution,
169 mathematicalModel: null,
170 options,
171 cancellationToken);
172 }
173
174 /// <inheritdoc/>
175 public Task<SolutionCheckResult> CheckAsync(
176 LotSizingInstance instance,
177 LotSizingSolution solution,
178 MathematicalModel mathematicalModel,
179 SolutionCheckOptions? options = null,
180 CancellationToken cancellationToken = default)
181 {
182 ArgumentNullException.ThrowIfNull(
183 mathematicalModel);
184
185 return CheckCoreAsync(
186 instance,
187 solution,
188 mathematicalModel,
189 options,
190 cancellationToken);
191 }
192
193 private async Task<SolutionCheckResult> CheckCoreAsync(
194 LotSizingInstance instance,
195 LotSizingSolution solution,
196 MathematicalModel? mathematicalModel,
197 SolutionCheckOptions? options,
198 CancellationToken cancellationToken)
199 {
200 ArgumentNullException.ThrowIfNull(
201 instance);
202
203 ArgumentNullException.ThrowIfNull(
204 solution);
205
206 options ??=
208
209 options.EnsureValid();
210
211 cancellationToken.ThrowIfCancellationRequested();
212
213 var result =
215 {
216 Level =
217 options.Level,
218 IsStructurallyValid =
219 false,
220 AreVariableDomainsValid =
221 false,
222 IsFeasible =
223 false,
224 IsObjectiveConsistent =
225 false
226 };
227
228 try
229 {
230 SolutionCheckResult structuralResult =
231 _structuralChecker.Check(
232 instance,
233 solution,
234 options);
235
236 MergeStructuralResult(
237 structuralResult,
238 result);
239
240 result.StructuralCheckCompleted =
241 true;
242
243 if (options.Level ==
244 SolutionCheckLevel.Structural)
245 {
246 return result;
247 }
248
249 if (!result.IsStructurallyValid &&
251 {
252 return result;
253 }
254
255 cancellationToken.ThrowIfCancellationRequested();
256
257 SolutionCheckResult domainResult =
258 _variableDomainChecker.Check(
259 solution,
260 options);
261
262 MergeVariableDomainResult(
263 domainResult,
264 result);
265
266 result.VariableDomainCheckCompleted =
267 true;
268
269 cancellationToken.ThrowIfCancellationRequested();
270
271 MathematicalModel? effectiveMathematicalModel =
272 mathematicalModel;
273
274 if (effectiveMathematicalModel is null)
275 {
276 effectiveMathematicalModel =
277 await BuildMathematicalModelAsync(
278 instance,
279 result,
280 cancellationToken)
281 .ConfigureAwait(false);
282 }
283
284 if (effectiveMathematicalModel is null)
285 {
286 return result;
287 }
288
289 cancellationToken.ThrowIfCancellationRequested();
290
291 SolutionCheckResult feasibilityResult =
292 _feasibilityChecker.Check(
293 effectiveMathematicalModel,
294 solution,
295 options);
296
297 MergeFeasibilityResult(
298 feasibilityResult,
299 result);
300
301 result.FeasibilityCheckCompleted =
302 true;
303
304 if (options.Level ==
305 SolutionCheckLevel.Feasibility)
306 {
307 return result;
308 }
309
310 if (HasProjectionFailure(
311 feasibilityResult))
312 {
313 return result;
314 }
315
316 cancellationToken.ThrowIfCancellationRequested();
317
318 SolutionCheckResult objectiveResult =
319 _objectiveChecker.Check(
320 effectiveMathematicalModel,
321 solution,
322 options);
323
324 MergeObjectiveResult(
325 objectiveResult,
326 result);
327
328 result.ObjectiveCheckCompleted =
329 true;
330
331 return result;
332 }
333 catch (OperationCanceledException)
334 when (cancellationToken.IsCancellationRequested)
335 {
336 throw;
337 }
338 catch (Exception exception)
339 {
340 result.IsFeasible =
341 false;
342
343 if (options.Level ==
345 {
346 result.IsObjectiveConsistent =
347 false;
348 }
349
350 result.AddIssue(
351 new SolutionCheckIssue
352 {
353 Severity =
355 Kind =
356 SolutionCheckIssueKind.CheckFailure,
357 Message =
358 "Complete lot-sizing solution checking failed: " +
359 exception.Message
360 });
361
362 return result;
363 }
364 }
365
366 private async Task<MathematicalModel?> BuildMathematicalModelAsync(
367 LotSizingInstance instance,
368 SolutionCheckResult result,
369 CancellationToken cancellationToken)
370 {
371 var buildOptions =
372 new MathematicalModelBuildOptions
373 {
374 RequestedFormulationId =
375 _formulationId,
376 AllowFallback =
377 false,
378 ValidateGeneratedModel =
379 true,
380 CloneGeneratedModel =
381 false
382 };
383
384 MathematicalModelBuildResult buildResult =
385 await _modelBuildService.BuildAsync(
386 instance,
387 _formulationRegistry,
388 buildOptions,
389 cancellationToken)
390 .ConfigureAwait(false);
391
392 if (buildResult.IsSuccessful &&
393 buildResult.Model is not null)
394 {
395 return buildResult.Model;
396 }
397
398 string failureMessage =
399 string.IsNullOrWhiteSpace(
400 buildResult.FailureMessage)
401 ? "The mathematical model required for solution checking " +
402 "could not be constructed."
403 : buildResult.FailureMessage;
404
405 result.AddIssue(
406 new SolutionCheckIssue
407 {
408 Severity =
410 Kind =
411 SolutionCheckIssueKind.CheckFailure,
412 Message =
413 "Mathematical model construction failed: " +
414 failureMessage
415 });
416
417 foreach (
418 string diagnostic
419 in buildResult.Diagnostics)
420 {
421 if (string.IsNullOrWhiteSpace(
422 diagnostic))
423 {
424 continue;
425 }
426
427 result.AddIssue(
428 new SolutionCheckIssue
429 {
430 Severity =
431 SolutionCheckSeverity.Warning,
432 Kind =
433 SolutionCheckIssueKind.CheckFailure,
434 Message =
435 "Mathematical model diagnostic: " +
436 diagnostic
437 });
438 }
439
440 return null;
441 }
442
443 private static void MergeStructuralResult(
444 SolutionCheckResult source,
445 SolutionCheckResult target)
446 {
447 target.IsStructurallyValid =
448 source.IsStructurallyValid;
449
450 MergeIssues(
451 source,
452 target);
453 }
454
455 private static void MergeVariableDomainResult(
456 SolutionCheckResult source,
457 SolutionCheckResult target)
458 {
459 target.AreVariableDomainsValid =
461
462 MergeIssues(
463 source,
464 target);
465 }
466
467 private static void MergeFeasibilityResult(
468 SolutionCheckResult source,
469 SolutionCheckResult target)
470 {
471 target.IsFeasible =
472 source.IsFeasible;
473
474 target.MaximumConstraintViolation =
476
477 target.TotalConstraintViolation =
479
480 target.ViolatedConstraintCount =
482
483 MergeIssues(
484 source,
485 target);
486 }
487
488 private static void MergeObjectiveResult(
489 SolutionCheckResult source,
490 SolutionCheckResult target)
491 {
492 target.IsObjectiveConsistent =
494
495 target.RecomputedObjectiveValue =
497
498 target.ReportedObjectiveValue =
500
501 target.ObjectiveDifference =
502 source.ObjectiveDifference;
503
504 target.ObjectiveRelativeDifference =
506
507 target.ObjectiveComparisonTolerance =
509
510 MergeIssues(
511 source,
512 target);
513 }
514
515 private static void MergeIssues(
516 SolutionCheckResult source,
517 SolutionCheckResult target)
518 {
519 foreach (
520 SolutionCheckIssue issue
521 in source.Issues)
522 {
523 target.AddIssue(
524 issue);
525 }
526 }
527
528 private static bool HasProjectionFailure(
529 SolutionCheckResult feasibilityResult)
530 {
531 return
532 feasibilityResult.Issues.Any(
533 issue =>
534 issue.Severity ==
535 SolutionCheckSeverity.Error &&
536 issue.Kind ==
537 SolutionCheckIssueKind.MissingVariableValue);
538 }
539}
Configures generic lot-sizing solution checking.
bool ContinueAfterStructuralErrors
Gets or sets whether checking should continue after a structural error.
SolutionCheckLevel Level
Gets or sets the requested checking depth.
Performs generic numerical-domain validation of all decision values stored in a LotSizingSolution.
Checks the mathematical feasibility of a normalized LotSizingSolution against a MathematicalModel.
Independently evaluates the objective of a normalized LotSizingSolution against a MathematicalModel.
Task< SolutionCheckResult > CheckAsync(LotSizingInstance instance, LotSizingSolution solution, SolutionCheckOptions? options=null, CancellationToken cancellationToken=default)
Checks a candidate solution against its lot-sizing instance.Detailed structural, feasibility,...
LotSizingSolutionChecker()
Initializes the checker with the standard LotSizingDataModel formulation and the standard checker com...
LotSizingSolutionChecker(ISolutionStructuralChecker structuralChecker, ISolutionVariableDomainChecker variableDomainChecker, IMathematicalFeasibilityChecker feasibilityChecker, IMathematicalObjectiveChecker objectiveChecker, IMathematicalModelBuildService modelBuildService, MathematicalModelFormulationRegistry formulationRegistry, string formulationId)
Initializes the checker with explicitly supplied components.
Task< SolutionCheckResult > CheckAsync(LotSizingInstance instance, LotSizingSolution solution, MathematicalModel mathematicalModel, SolutionCheckOptions? options=null, CancellationToken cancellationToken=default)
Checks a candidate solution against its lot-sizing instance while reusing an already constructed math...
Projects a normalized LotSizingSolution onto the variables of a MathematicalModel by using the canoni...
SolutionCheckIssueKind Kind
Gets or sets the issue category.
SolutionCheckSeverity Severity
Gets or sets the issue severity.
Contains the complete result of checking one lot-sizing solution.
IReadOnlyList< SolutionCheckIssue > Issues
Gets all checker diagnostics.
double? ObjectiveRelativeDifference
Gets or sets the relative objective-value difference, scaled by max(1, abs(reported),...
bool AreVariableDomainsValid
Gets or sets whether all checked variable-domain conditions are satisfied.
double TotalConstraintViolation
Gets or sets the sum of mathematical-constraint violations.
bool IsFeasible
Gets or sets whether all checked mathematical constraints are feasible.
double MaximumConstraintViolation
Gets or sets the maximum mathematical-constraint violation.
double? ObjectiveComparisonTolerance
Gets or sets the effective absolute comparison tolerance used for objective-value verification.
double? ReportedObjectiveValue
Gets or sets the objective value reported by the candidate solution.
double? ObjectiveDifference
Gets or sets the absolute objective-value difference.
int ViolatedConstraintCount
Gets or sets the number of violated mathematical constraints.
bool IsStructurallyValid
Gets or sets whether structural validation succeeded.
bool IsObjectiveConsistent
Gets or sets whether objective-value verification succeeded.
void AddIssue(SolutionCheckIssue issue)
Adds one issue to the result.
double? RecomputedObjectiveValue
Gets or sets the objective value independently recomputed by the checker.
Performs solver-independent structural validation of a LotSizingSolution against a LotSizingInstance.
Defines a generic, solver-independent checker for lot-sizing solutions.
Defines mathematical-feasibility validation of a candidate lot-sizing solution against a mathematical...
Defines independent objective-value validation of a candidate lot-sizing solution against a mathemati...
Defines structural validation of a lot-sizing solution against a lot-sizing instance.
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.