LotSizingDataModel.Checker 2.0.1
Reusable validation engine for structural, mathematical and objective consistency checks.
Loading...
Searching...
No Matches
MathematicalFeasibilityChecker.cs
Go to the documentation of this file.
6using LotSizingDataModel.Solution;
7using LotSizingDataModel.Solver.Modeling;
8
10
11/// <summary>
12/// Checks the mathematical feasibility 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 constraint logic.
19/// Candidate business values are first projected to mathematical
20/// variables by <see cref="IMathematicalSolutionValueProjector"/>,
21/// then the constraints already present in the mathematical model
22/// are evaluated directly.
23/// </para>
24/// <para>
25/// This makes the mathematical formulation itself the single source
26/// of truth for feasibility.
27/// </para>
28/// </remarks>
29public sealed class MathematicalFeasibilityChecker :
31{
32 private readonly IMathematicalSolutionValueProjector _projector;
33
34 /// <summary>
35 /// Initializes the checker with the default mathematical
36 /// solution projector.
37 /// </summary>
43
44 /// <summary>
45 /// Initializes the checker with an explicit projector.
46 /// </summary>
47 /// <param name="projector">
48 /// Component used to map business decisions to mathematical variables.
49 /// </param>
52 {
53 _projector =
54 projector ??
55 throw new ArgumentNullException(
56 nameof(projector));
57 }
58
59 /// <inheritdoc/>
61 MathematicalModel model,
62 LotSizingSolution solution,
64 {
65 ArgumentNullException.ThrowIfNull(model);
66 ArgumentNullException.ThrowIfNull(solution);
67 ArgumentNullException.ThrowIfNull(options);
68
69 model.EnsureValid();
70 options.EnsureValid();
71
72 var result =
74 {
75 Level =
76 SolutionCheckLevel.Feasibility,
77 IsStructurallyValid =
78 true,
79 AreVariableDomainsValid =
80 true,
81 IsFeasible =
82 true,
83 IsObjectiveConsistent =
84 false
85 };
86
88 _projector.Project(
89 model,
90 solution);
91
92 AddProjectionIssues(
93 projection,
94 result);
95
96 if (!projection.IsSuccessful)
97 {
98 result.IsFeasible =
99 false;
100
101 return result;
102 }
103
104 foreach (
105 LinearConstraint constraint
106 in model.Constraints)
107 {
108 if (options.IgnoreDisabledConstraints &&
109 !constraint.IsEnabled)
110 {
111 continue;
112 }
113
115 EvaluateConstraint(
116 constraint,
117 projection,
118 options.FeasibilityTolerance);
119
120 if (detail.IsSatisfied)
121 {
122 continue;
123 }
124
125 result.ViolatedConstraintCount++;
126
127 result.TotalConstraintViolation +=
128 detail.Violation;
129
130 result.MaximumConstraintViolation =
131 Math.Max(
132 result.MaximumConstraintViolation,
133 detail.Violation);
134
135 result.AddIssue(
136 CreateConstraintViolationIssue(
137 detail));
138 }
139
140 result.IsFeasible =
141 result.ViolatedConstraintCount == 0 &&
142 !result.Issues.Any(
143 issue =>
144 issue.Severity ==
145 SolutionCheckSeverity.Error &&
146 issue.Kind ==
147 SolutionCheckIssueKind.CheckFailure);
148
149 return result;
150 }
151
152 private static void AddProjectionIssues(
154 SolutionCheckResult result)
155 {
156 foreach (
158 in projection.Issues)
159 {
160 result.AddIssue(
162 {
163 Severity =
165
166 Kind =
167 SolutionCheckIssueKind.MissingVariableValue,
168
169 DomainKey =
170 issue.DomainKey,
171
172 Message =
173 $"Variable projection failed" +
174 $"{FormatVariable(issue)}: " +
175 issue.Message
176 });
177 }
178 }
179
180 private static MathematicalConstraintCheckDetail EvaluateConstraint(
181 LinearConstraint constraint,
183 double tolerance)
184 {
185 double leftHandSide =
186 constraint.LeftHandSide.Constant;
187
188 foreach (
189 LinearTerm term
190 in constraint.LeftHandSide.Terms)
191 {
192 if (!projection.TryGetValue(
193 term.VariableId,
194 out double variableValue))
195 {
196 throw new InvalidOperationException(
197 $"No projected value exists for mathematical " +
198 $"variable '{term.VariableId}' while evaluating " +
199 $"constraint '{constraint.Name}'.");
200 }
201
202 leftHandSide +=
203 term.Coefficient *
204 variableValue;
205 }
206
207 if (!double.IsFinite(leftHandSide))
208 {
209 throw new InvalidOperationException(
210 $"Constraint '{constraint.Name}' evaluated to a " +
211 "non-finite left-hand-side value.");
212 }
213
214 double violation =
215 ComputeViolation(
216 leftHandSide,
217 constraint.Sense,
218 constraint.RightHandSide);
219
220 return new MathematicalConstraintCheckDetail
221 {
222 ConstraintId =
223 constraint.Id,
224
225 ConstraintName =
226 constraint.Name,
227
228 DomainKey =
229 string.IsNullOrWhiteSpace(
230 constraint.DomainKey)
231 ? null
232 : constraint.DomainKey,
233
234 LeftHandSideValue =
235 leftHandSide,
236
237 Sense =
238 constraint.Sense,
239
240 RightHandSideValue =
241 constraint.RightHandSide,
242
243 Violation =
244 violation,
245
246 IsSatisfied =
247 violation <= tolerance
248 };
249 }
250
251 private static double ComputeViolation(
252 double leftHandSide,
253 MathematicalConstraintSense sense,
254 double rightHandSide)
255 {
256 if (!double.IsFinite(rightHandSide))
257 {
258 throw new InvalidOperationException(
259 "A mathematical constraint right-hand side must be finite.");
260 }
261
262 return sense switch
263 {
264 MathematicalConstraintSense.LessThanOrEqual =>
265 Math.Max(
266 0.0,
267 leftHandSide - rightHandSide),
268
269 MathematicalConstraintSense.Equal =>
270 Math.Abs(
271 leftHandSide - rightHandSide),
272
273 MathematicalConstraintSense.GreaterThanOrEqual =>
274 Math.Max(
275 0.0,
276 rightHandSide - leftHandSide),
277
278 _ =>
279 throw new NotSupportedException(
280 $"Mathematical constraint sense '{sense}' " +
281 "is not supported by the feasibility checker.")
282 };
283 }
284
285 private static SolutionCheckIssue CreateConstraintViolationIssue(
286 MathematicalConstraintCheckDetail detail)
287 {
288 return new SolutionCheckIssue
289 {
290 Severity =
292
293 Kind =
294 SolutionCheckIssueKind.ConstraintViolation,
295
296 DomainKey =
297 detail.DomainKey,
298
299 ConstraintName =
300 detail.ConstraintName,
301
302 ActualValue =
303 detail.LeftHandSideValue,
304
305 ExpectedValue =
306 detail.RightHandSideValue,
307
308 Violation =
309 detail.Violation,
310
311 Message =
312 $"Constraint '{detail.ConstraintName}' is violated. " +
313 $"LHS={detail.LeftHandSideValue:G17}; " +
314 $"sense={FormatSense(detail.Sense)}; " +
315 $"RHS={detail.RightHandSideValue:G17}; " +
316 $"violation={detail.Violation:G17}."
317 };
318 }
319
320 private static string FormatVariable(
321 MathematicalSolutionProjectionIssue issue)
322 {
323 if (issue.VariableId.HasValue &&
324 !string.IsNullOrWhiteSpace(
325 issue.VariableName))
326 {
327 return
328 $" for variable '{issue.VariableName}' " +
329 $"(id={issue.VariableId.Value})";
330 }
331
332 if (issue.VariableId.HasValue)
333 {
334 return
335 $" for variable id={issue.VariableId.Value}";
336 }
337
338 return string.Empty;
339 }
340
341 private static string FormatSense(
342 MathematicalConstraintSense sense)
343 {
344 return sense switch
345 {
346 MathematicalConstraintSense.LessThanOrEqual =>
347 "<=",
348
349 MathematicalConstraintSense.Equal =>
350 "=",
351
352 MathematicalConstraintSense.GreaterThanOrEqual =>
353 ">=",
354
355 _ =>
356 sense.ToString()
357 };
358 }
359}
Configures generic lot-sizing solution checking.
Describes the numerical evaluation of one mathematical constraint.
bool IsSatisfied
Gets or sets whether the constraint is feasible within tolerance.
SolutionCheckResult Check(MathematicalModel model, LotSizingSolution solution, SolutionCheckOptions options)
Projects the candidate solution onto the mathematical model, evaluates all enabled constraints,...
MathematicalFeasibilityChecker(IMathematicalSolutionValueProjector projector)
Initializes the checker with an explicit projector.
MathematicalFeasibilityChecker()
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.
void AddIssue(SolutionCheckIssue issue)
Adds one issue to the result.
Defines mathematical-feasibility validation of a candidate lot-sizing solution against a mathematical...
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.