LotSizingDataModel.Checker 2.0.1
Reusable validation engine for structural, mathematical and objective consistency checks.
Loading...
Searching...
No Matches
MathematicalObjectiveChecker.cs
Go to the documentation of this file.
6using LotSizingDataModel.Solution;
7using LotSizingDataModel.Solver.Modeling;
8
10
11/// <summary>
12/// Independently evaluates the objective of a normalized
13/// <see cref="LotSizingSolution"/> against a
14/// <see cref="MathematicalModel"/>.
15/// </summary>
16/// <remarks>
17/// <para>
18/// The checker does not duplicate any lot-sizing cost formula.
19/// Candidate business values are projected onto mathematical variables
20/// by <see cref="IMathematicalSolutionValueProjector"/>, then the
21/// objective expression already contained in the mathematical model is
22/// evaluated directly.
23/// </para>
24/// <para>
25/// The recomputed value is compared first with
26/// <see cref="SolutionCheckOptions.ReportedObjectiveValueOverride"/> when
27/// supplied, otherwise with <c>solution.Evaluation.ObjectiveValue</c>. A
28/// missing reported value is not an inconsistency: the independently
29/// recomputed value remains available in the returned result.
30/// </para>
31/// </remarks>
32public sealed class MathematicalObjectiveChecker :
34{
35 private readonly IMathematicalSolutionValueProjector _projector;
36
37 /// <summary>
38 /// Initializes the checker with the default mathematical
39 /// solution projector.
40 /// </summary>
46
47 /// <summary>
48 /// Initializes the checker with an explicit projector.
49 /// </summary>
50 /// <param name="projector">
51 /// Component used to map business decisions to mathematical variables.
52 /// </param>
55 {
56 _projector =
57 projector ??
58 throw new ArgumentNullException(
59 nameof(projector));
60 }
61
62 /// <inheritdoc/>
64 MathematicalModel model,
65 LotSizingSolution solution,
67 {
68 ArgumentNullException.ThrowIfNull(model);
69 ArgumentNullException.ThrowIfNull(solution);
70 ArgumentNullException.ThrowIfNull(options);
71
72 model.EnsureValid();
73 options.EnsureValid();
74
75 var result =
77 {
78 Level =
80 IsStructurallyValid =
81 true,
82 AreVariableDomainsValid =
83 true,
84 IsFeasible =
85 true,
86 IsObjectiveConsistent =
87 false
88 };
89
90 try
91 {
93 _projector.Project(
94 model,
95 solution);
96
97 AddProjectionIssues(
98 projection,
99 result);
100
101 if (!projection.IsSuccessful)
102 {
103 return result;
104 }
105
106 double recomputedObjective =
107 EvaluateObjective(
108 model,
109 projection);
110
111 result.RecomputedObjectiveValue =
112 recomputedObjective;
113
114 double? reportedObjective =
115 options.ReportedObjectiveValueOverride ??
116 solution.Evaluation?.ObjectiveValue;
117
118 result.ReportedObjectiveValue =
119 reportedObjective;
120
121 if (!reportedObjective.HasValue)
122 {
123 result.IsObjectiveConsistent =
124 true;
125
126 return result;
127 }
128
129 if (!double.IsFinite(
130 reportedObjective.Value))
131 {
132 result.AddIssue(
134 {
135 Severity =
137 Kind =
138 SolutionCheckIssueKind.ObjectiveMismatch,
139 Message =
140 "The objective value reported by the " +
141 "candidate solution is not finite."
142 });
143
144 return result;
145 }
146
147 CompareObjectiveValues(
148 reportedObjective.Value,
149 recomputedObjective,
150 options,
151 result);
152
153 return result;
154 }
155 catch (Exception exception)
156 {
157 result.IsObjectiveConsistent =
158 false;
159
160 result.AddIssue(
162 {
163 Severity =
165 Kind =
166 SolutionCheckIssueKind.CheckFailure,
167 Message =
168 "Objective-value checking failed: " +
169 exception.Message
170 });
171
172 return result;
173 }
174 }
175
176 private static void AddProjectionIssues(
178 SolutionCheckResult result)
179 {
180 foreach (
182 in projection.Issues)
183 {
184 result.AddIssue(
186 {
187 Severity =
189 Kind =
190 SolutionCheckIssueKind.MissingVariableValue,
191 DomainKey =
192 issue.DomainKey,
193 Message =
194 $"Variable projection failed" +
195 $"{FormatVariable(issue)}: " +
196 issue.Message
197 });
198 }
199 }
200
201 private static double EvaluateObjective(
202 MathematicalModel model,
204 {
205 double objectiveValue =
206 model.Objective.Expression.Constant;
207
208 foreach (
209 LinearTerm term
210 in model.Objective.Expression.Terms)
211 {
212 if (!projection.TryGetValue(
213 term.VariableId,
214 out double variableValue))
215 {
216 throw new InvalidOperationException(
217 $"No projected value exists for mathematical " +
218 $"variable '{term.VariableId}' while evaluating " +
219 "the objective.");
220 }
221
222 objectiveValue +=
223 term.Coefficient *
224 variableValue;
225 }
226
227 if (!double.IsFinite(
228 objectiveValue))
229 {
230 throw new InvalidOperationException(
231 "The recomputed objective value is not finite.");
232 }
233
234 return objectiveValue;
235 }
236
237 private static void CompareObjectiveValues(
238 double reportedObjective,
239 double recomputedObjective,
240 SolutionCheckOptions options,
241 SolutionCheckResult result)
242 {
243 double absoluteDifference =
244 Math.Abs(
245 reportedObjective -
246 recomputedObjective);
247
248 double scale =
249 Math.Max(
250 1.0,
251 Math.Max(
252 Math.Abs(
253 reportedObjective),
254 Math.Abs(
255 recomputedObjective)));
256
257 double relativeDifference =
258 absoluteDifference /
259 scale;
260
261 double comparisonTolerance =
262 Math.Max(
265 scale);
266
267 result.ObjectiveDifference =
268 absoluteDifference;
269
270 result.ObjectiveRelativeDifference =
271 relativeDifference;
272
273 result.ObjectiveComparisonTolerance =
274 comparisonTolerance;
275
276 result.IsObjectiveConsistent =
277 absoluteDifference <=
278 comparisonTolerance;
279
280 if (result.IsObjectiveConsistent)
281 {
282 return;
283 }
284
285 result.AddIssue(
286 new SolutionCheckIssue
287 {
288 Severity =
290 Kind =
291 SolutionCheckIssueKind.ObjectiveMismatch,
292 ActualValue =
293 recomputedObjective,
294 ExpectedValue =
295 reportedObjective,
296 Violation =
297 absoluteDifference,
298 Message =
299 "Objective values are inconsistent. " +
300 $"Reported={reportedObjective:G17}; " +
301 $"recomputed={recomputedObjective:G17}; " +
302 $"absolute difference={absoluteDifference:G17}; " +
303 $"relative difference={relativeDifference:G17}; " +
304 $"comparison tolerance={comparisonTolerance:G17}."
305 });
306 }
307
308 private static string FormatVariable(
309 MathematicalSolutionProjectionIssue issue)
310 {
311 if (issue.VariableId.HasValue &&
312 !string.IsNullOrWhiteSpace(
313 issue.VariableName))
314 {
315 return
316 $" for variable '{issue.VariableName}' " +
317 $"(id={issue.VariableId.Value})";
318 }
319
320 if (issue.VariableId.HasValue)
321 {
322 return
323 $" for variable id={issue.VariableId.Value}";
324 }
325
326 return string.Empty;
327 }
328}
Configures generic lot-sizing solution checking.
double ObjectiveRelativeTolerance
Gets or sets the relative tolerance used when comparing objective values.
double ObjectiveAbsoluteTolerance
Gets or sets the absolute tolerance used when comparing objective values.
MathematicalObjectiveChecker(IMathematicalSolutionValueProjector projector)
Initializes the checker with an explicit projector.
SolutionCheckResult Check(MathematicalModel model, LotSizingSolution solution, SolutionCheckOptions options)
Projects the candidate solution onto the mathematical model, independently evaluates the model object...
MathematicalObjectiveChecker()
Initializes the checker with the default mathematical solution projector.
Describes one issue encountered while projecting a business solution onto mathematical-model variable...
Contains mathematical-variable values projected from one normalized lot-sizing solution.
bool IsSuccessful
Gets whether every mathematical variable was projected successfully.
IReadOnlyList< MathematicalSolutionProjectionIssue > Issues
Gets all projection diagnostics.
bool TryGetValue(int variableId, out double value)
Attempts to get the projected value of one mathematical variable.
Projects a normalized LotSizingSolution onto the variables of a MathematicalModel by using the canoni...
Describes one issue detected by a lot-sizing solution checker.
Contains the complete result of checking one lot-sizing solution.
bool IsObjectiveConsistent
Gets or sets whether objective-value verification succeeded.
void AddIssue(SolutionCheckIssue issue)
Adds one issue to the result.
Defines independent objective-value validation of a candidate lot-sizing solution against a mathemati...
Defines projection of a normalized lot-sizing solution onto the variables of a solver-independent mat...
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.