LotSizingDataModel.Solver 2.0.1
Solver-independent modeling, execution, monitoring and adapter infrastructure.
Loading...
Searching...
No Matches
MultiObjectiveLotSizingSolverService.cs
Go to the documentation of this file.
1using System.Diagnostics;
2using LotSizingDataModel.Core.DecisionModel.Objectives;
9
11
12/// <summary>
13/// High-level solver service executing Single, WeightedSum and exact
14/// Lexicographic objective policies.
15/// </summary>
16/// <remarks>
17/// Lexicographic execution requires each completed stage to be proven optimal
18/// before its objective level can be frozen for the next stage.
19/// </remarks>
22{
23 private readonly IMathematicalModelBuildService _modelBuildService;
24 private readonly IMathematicalModelSolverExecutionService _solverExecutionService;
25 private readonly IMathematicalSolutionMappingService _solutionMappingService;
26 private readonly MathematicalModelFormulationRegistry _formulationRegistry;
27 private readonly SolverAdapterRegistry _solverAdapterRegistry;
28 private readonly IReadOnlyList<SolverAvailabilityInfo> _availabilityInformation;
29 private readonly SolverSelectionOptions _solverSelectionOptions;
30 private readonly MathematicalSolutionMappingOptions _mappingOptions;
31
33 MathematicalModelFormulationRegistry formulationRegistry,
34 SolverAdapterRegistry solverAdapterRegistry,
35 IEnumerable<SolverAvailabilityInfo> availabilityInformation)
36 : this(
41 formulationRegistry,
42 solverAdapterRegistry,
43 availabilityInformation,
46 {
47 }
48
50 IMathematicalModelBuildService modelBuildService,
51 IMathematicalModelSolverExecutionService solverExecutionService,
52 IMathematicalSolutionMappingService solutionMappingService,
53 MathematicalModelFormulationRegistry formulationRegistry,
54 SolverAdapterRegistry solverAdapterRegistry,
55 IEnumerable<SolverAvailabilityInfo> availabilityInformation,
56 SolverSelectionOptions solverSelectionOptions,
58 {
59 ArgumentNullException.ThrowIfNull(modelBuildService);
60 ArgumentNullException.ThrowIfNull(solverExecutionService);
61 ArgumentNullException.ThrowIfNull(solutionMappingService);
62 ArgumentNullException.ThrowIfNull(formulationRegistry);
63 ArgumentNullException.ThrowIfNull(solverAdapterRegistry);
64 ArgumentNullException.ThrowIfNull(availabilityInformation);
65 ArgumentNullException.ThrowIfNull(solverSelectionOptions);
66 ArgumentNullException.ThrowIfNull(mappingOptions);
67
68 _modelBuildService = modelBuildService;
69 _solverExecutionService = solverExecutionService;
70 _solutionMappingService = solutionMappingService;
71 _formulationRegistry = formulationRegistry;
72 _solverAdapterRegistry = solverAdapterRegistry;
73 _availabilityInformation = availabilityInformation.ToArray();
74 _solverSelectionOptions = solverSelectionOptions;
75 _mappingOptions = mappingOptions;
76 }
77
78 public async ValueTask<SolverRunResult> SolveAsync(
79 SolverRequest request,
80 CancellationToken cancellationToken = default)
81 {
82 ArgumentNullException.ThrowIfNull(request);
83 request.EnsureValid();
84
85 DateTime startedAtUtc = DateTime.UtcNow;
86 var stopwatch = Stopwatch.StartNew();
87
88 try
89 {
90 var buildOptions =
92 {
93 RequestedFormulationId =
94 request.FormulationName ?? string.Empty,
95 AllowFallback =
96 string.IsNullOrWhiteSpace(
97 request.FormulationName),
98 ValidateGeneratedModel = true,
99 CloneGeneratedModel = false
100 };
101
102 MathematicalModelBuildResult buildResult =
103 await _modelBuildService.BuildAsync(
104 request.Instance!,
105 _formulationRegistry,
106 buildOptions,
107 cancellationToken);
108
109 if (!buildResult.IsSuccessful ||
110 buildResult.Model is null)
111 {
112 stopwatch.Stop();
113
114 return Failure(
115 request,
116 startedAtUtc,
117 stopwatch.Elapsed,
118 SolverTerminationReason.ModelError,
119 buildResult.FailureMessage,
120 buildResult.Diagnostics);
121 }
122
123 FinancialExecutionModelContext financialContext =
125 request.Instance!,
126 buildResult.Model);
127
128 IReadOnlyList<ExecutableObjectiveCriterion> criteria =
130 request.Instance!,
131 financialContext);
132
133 ObjectiveAggregationMode aggregationMode =
134 request.Instance!.SupplyChain.ObjectivePolicy?
135 .AggregationMode ??
136 ObjectiveAggregationMode.Single;
137
138 MathematicalModel finalModel;
140 var stageDiagnostics = new List<string>();
141
142 if (aggregationMode ==
143 ObjectiveAggregationMode.WeightedSum)
144 {
145 finalModel =
147 financialContext,
148 criteria);
149
150 finalSolve =
151 await SolveModelAsync(
152 request,
153 buildResult.SelectedFormulationId,
154 finalModel,
155 cancellationToken);
156
157 if (!finalSolve.HasFeasibleSolution)
158 {
159 stopwatch.Stop();
160
161 return Failure(
162 request,
163 startedAtUtc,
164 stopwatch.Elapsed,
165 finalSolve.TerminationReason,
166 "WeightedSum solve did not produce a feasible solution.",
167 finalSolve.Diagnostics);
168 }
169
170 stageDiagnostics.Add(
171 $"WeightedSum solved with {criteria.Count} explicit criteria.");
172 }
173 else if (aggregationMode ==
174 ObjectiveAggregationMode.Lexicographic)
175 {
176 var preserved =
177 new List<(ExecutableObjectiveCriterion Criterion, double Value)>();
178
179 MathematicalModel? currentModel = null;
180 MathematicalModelSolveResult? currentSolve = null;
181 int stageIndex = 0;
182
183 foreach (ExecutableObjectiveCriterion criterion in criteria)
184 {
185 stageIndex++;
186
187 currentModel =
189 financialContext,
190 criterion,
191 preserved);
192
193 currentSolve =
194 await SolveModelAsync(
195 request,
196 buildResult.SelectedFormulationId,
197 currentModel,
198 cancellationToken);
199
200 if (!currentSolve.HasFeasibleSolution ||
201 !currentSolve.IsOptimal ||
202 !currentSolve.ObjectiveValue.HasValue)
203 {
204 stopwatch.Stop();
205
206 return Failure(
207 request,
208 startedAtUtc,
209 stopwatch.Elapsed,
210 currentSolve.TerminationReason,
211 $"Lexicographic stage {stageIndex} ({criterion.Kind}) must be proven optimal before the next stage.",
212 currentSolve.Diagnostics);
213 }
214
215 double value =
216 currentSolve.ObjectiveValue.Value;
217
218 preserved.Add(
219 (criterion, value));
220
221 stageDiagnostics.Add(
222 $"Lexicographic stage {stageIndex}: kind={criterion.Kind}; value={value:R}; tolerance={criterion.AbsoluteTolerance:R}; solver={currentSolve.SolverName} {currentSolve.SolverVersion}.");
223 }
224
225 if (currentModel is null ||
226 currentSolve is null)
227 {
228 throw new InvalidOperationException(
229 "Lexicographic execution produced no stage.");
230 }
231
232 finalModel = currentModel;
233 finalSolve = currentSolve;
234 }
235 else
236 {
238 criteria.Single();
239
240 finalModel =
242 financialContext,
243 criterion,
244 Array.Empty<(ExecutableObjectiveCriterion, double)>());
245
246 finalSolve =
247 await SolveModelAsync(
248 request,
249 buildResult.SelectedFormulationId,
250 finalModel,
251 cancellationToken);
252
253 if (!finalSolve.HasFeasibleSolution)
254 {
255 stopwatch.Stop();
256
257 return Failure(
258 request,
259 startedAtUtc,
260 stopwatch.Elapsed,
261 finalSolve.TerminationReason,
262 $"Single objective '{criterion.Kind}' did not produce a feasible solution.",
263 finalSolve.Diagnostics);
264 }
265 }
266
268 await _solutionMappingService.MapAsync(
269 request.Instance!,
270 finalModel,
271 finalSolve,
272 _mappingOptions,
273 cancellationToken);
274
275 stopwatch.Stop();
276
277 SolverRunResult result =
279 finalSolve,
280 mappingResult,
281 startedAtUtc,
282 stopwatch.Elapsed);
283
284 foreach (string diagnostic in buildResult.Diagnostics)
285 {
286 result.AddDiagnostic(diagnostic);
287 }
288
289 foreach (string diagnostic in stageDiagnostics)
290 {
291 result.AddDiagnostic(diagnostic);
292 }
293
294 if (mappingResult.IsSuccessful &&
295 mappingResult.Solution is not null)
296 {
299 finalModel,
300 finalSolve,
301 _mappingOptions);
302
303 result.RecomputedObjectiveValue =
304 objectiveCheck.RecomputedObjectiveValue;
305
306 result.ObjectiveDifference =
307 objectiveCheck.AbsoluteDifference;
308
309 result.ObjectiveVerificationStatus =
310 objectiveCheck.Status;
311
312 foreach (string diagnostic in objectiveCheck.Diagnostics)
313 {
314 result.AddDiagnostic(diagnostic);
315 }
316 }
317
318 return result;
319 }
320 catch (OperationCanceledException)
321 {
322 stopwatch.Stop();
323
324 return Failure(
325 request,
326 startedAtUtc,
327 stopwatch.Elapsed,
328 SolverTerminationReason.UserInterrupted,
329 "The multiobjective solve workflow was cancelled.",
330 null);
331 }
332 catch (Exception exception)
333 {
334 stopwatch.Stop();
335
336 return Failure(
337 request,
338 startedAtUtc,
339 stopwatch.Elapsed,
340 SolverTerminationReason.InternalError,
341 exception.Message,
342 new[] { exception.ToString() });
343 }
344 }
345
346 public void RequestStop()
347 {
348 _solverExecutionService.RequestStop();
349 }
350
351 private async ValueTask<MathematicalModelSolveResult> SolveModelAsync(
352 SolverRequest request,
353 string formulationId,
354 MathematicalModel model,
355 CancellationToken cancellationToken)
356 {
357 var mathematicalRequest =
359 {
360 Model = model,
361 RunName = request.RunName ?? string.Empty,
362 FormulationId = formulationId,
363 Parameters = request.Parameters
364 };
365
366 foreach (var observer in request.ProgressObservers)
367 {
368 mathematicalRequest.ProgressObservers.Add(observer);
369 }
370
371 mathematicalRequest.EnsureValid();
372
373 return await _solverExecutionService.SolveAsync(
374 mathematicalRequest,
375 request.PreferredSolver,
376 _solverAdapterRegistry,
377 _availabilityInformation,
378 _solverSelectionOptions,
379 cancellationToken);
380 }
381
382 private static SolverRunResult Failure(
383 SolverRequest request,
384 DateTime startedAtUtc,
385 TimeSpan elapsed,
387 string? message,
388 IEnumerable<string>? diagnostics)
389 {
390 var result =
391 new SolverRunResult
392 {
393 RunName = request.RunName ?? string.Empty,
394 SolverKind = request.PreferredSolver,
395 FormulationName =
396 request.FormulationName ?? string.Empty,
397 StartedAtUtc = startedAtUtc,
398 CompletedAtUtc = startedAtUtc + elapsed,
399 ElapsedSeconds = elapsed.TotalSeconds,
400 TerminationReason = reason,
401 SolutionCount = 0,
402 Solution = null
403 };
404
405 if (!string.IsNullOrWhiteSpace(message))
406 {
407 result.AddDiagnostic(message);
408 }
409
410 if (diagnostics is not null)
411 {
412 foreach (string diagnostic in diagnostics)
413 {
414 result.AddDiagnostic(diagnostic);
415 }
416 }
417
418 return result;
419 }
420}
Stores solver adapters that were loaded successfully and provides lookup services by adapter identifi...
Configures how a solver adapter is selected when automatic solver selection is requested.
double? AbsoluteDifference
Gets or sets the absolute difference between the two objective values.
double? RecomputedObjectiveValue
Gets or sets the objective value recomputed from normalized mathematical-variable values.
Recomputes the objective from the exact normalized numerical representation used by the business solu...
static MathematicalObjectiveRecalculationResult Recalculate(MathematicalModel model, MathematicalModelSolveResult solveResult, MathematicalSolutionMappingOptions mappingOptions)
Recomputes and verifies the objective value.
Adds complete-period OPEX coverage and optional cash-flow constraints to a built economic mathematica...
static FinancialExecutionModelContext Decorate(LotSizingInstance instance, MathematicalModel source)
Describes a request to solve an already built, solver-independent mathematical model.
void EnsureValid()
Validates the mathematical-model solve request.
Stores the solver result for a solver-independent mathematical model.
SolverTerminationReason TerminationReason
Gets or sets the termination reason.
bool HasFeasibleSolution
Gets or sets a value indicating whether the solver produced at least one feasible solution.
List< string > Diagnostics
Gets diagnostic messages produced while solving.
Selects a compatible mathematical-model solver adapter and executes an already built solver-independe...
Creates the public solver-run result from the generic mathematical-model result and the normalized so...
static SolverRunResult Create(MathematicalModelSolveResult solveResult, MathematicalSolutionMappingResult mappingResult, DateTime startedAtUtc, TimeSpan elapsed)
Creates a normalized solver-run result.
async ValueTask< SolverRunResult > SolveAsync(SolverRequest request, CancellationToken cancellationToken=default)
Solves a lot-sizing instance described by the supplied request.
void RequestStop()
Requests interruption of the currently running solve operation, when supported by the selected solver...
MultiObjectiveLotSizingSolverService(IMathematicalModelBuildService modelBuildService, IMathematicalModelSolverExecutionService solverExecutionService, IMathematicalSolutionMappingService solutionMappingService, MathematicalModelFormulationRegistry formulationRegistry, SolverAdapterRegistry solverAdapterRegistry, IEnumerable< SolverAvailabilityInfo > availabilityInformation, SolverSelectionOptions solverSelectionOptions, MathematicalSolutionMappingOptions mappingOptions)
MultiObjectiveLotSizingSolverService(MathematicalModelFormulationRegistry formulationRegistry, SolverAdapterRegistry solverAdapterRegistry, IEnumerable< SolverAvailabilityInfo > availabilityInformation)
Resolves executable objective criteria and constructs weighted/lexicographic mathematical models.
static IReadOnlyList< ExecutableObjectiveCriterion > ResolveCriteria(LotSizingInstance instance, FinancialExecutionModelContext context)
static MathematicalModel CreateLexicographicStageModel(FinancialExecutionModelContext context, ExecutableObjectiveCriterion criterion, IEnumerable<(ExecutableObjectiveCriterion Criterion, double Value)> preserved)
static MathematicalModel CreateWeightedSumModel(FinancialExecutionModelContext context, IReadOnlyList< ExecutableObjectiveCriterion > criteria)
Describes a request to solve a lot-sizing instance.
string FormulationName
Gets or sets the formulation name to use.
void EnsureValid()
Validates the solver request.
IList< ISolverProgressObserver > ProgressObservers
Gets the progress observers attached to this request.
SolverKind PreferredSolver
Gets or sets the preferred solver.
LotSizingInstance? Instance
Gets or sets the lot-sizing instance to solve.
Represents the normalized result of a solver execution.
Defines the options used to select a formulation and build a solver-independent mathematical model.
Describes the result of building a solver-independent mathematical model from a lot-sizing instance.
List< string > Diagnostics
Gets the diagnostic messages produced during selection and model construction.
string SelectedFormulationId
Gets or sets the selected formulation identifier.
MathematicalModel? Model
Gets or sets the generated mathematical model.
bool IsSuccessful
Gets or sets a value indicating whether model construction succeeded.
Selects a compatible formulation and builds the associated solver-independent mathematical model.
Creates preconfigured mathematical decision-mapper registries for the standard lot-sizing solution mo...
Defines the options used when mapping a mathematical solver result back to a normalized lot-sizing so...
Describes the result of mapping a mathematical solver result back to a normalized lot-sizing solution...
Maps a generic mathematical solver result to a normalized lot-sizing solution and reports mapping dia...
Represents a solver-independent mathematical optimization model.
Defines the high-level service used to solve a complete lot-sizing instance from a normalized solver ...
Defines a service that selects a compatible solver adapter and executes an already built solver-indep...
Defines a service that selects a formulation and builds a solver-independent mathematical model.
Defines a service that maps a generic mathematical solver result to a normalized lot-sizing solution.
SolverTerminationReason
Describes why a solver execution terminated.
SolverKind
Identifies a supported mathematical optimization solver.
Definition SolverKind.cs:12