31 if (
string.IsNullOrWhiteSpace(name))
33 throw new ArgumentException(
34 "A solver name is required.",
42 throw new ArgumentNullException(
43 nameof(formulationBuilder));
56 public string Name {
get; }
64 _formulationBuilder.Kind;
70 ArgumentNullException.ThrowIfNull(problem);
72 return _formulationBuilder.IsApplicable(
79 CancellationToken cancellationToken =
default)
81 ArgumentNullException.ThrowIfNull(problem);
82 cancellationToken.ThrowIfCancellationRequested();
92 .ConfigureAwait(
false),
93 CancellationToken.None)
101 CancellationToken cancellationToken =
default)
103 ArgumentNullException.ThrowIfNull(problem);
104 cancellationToken.ThrowIfCancellationRequested();
108 throw new NotSupportedException(
109 $
"{Name} is not applicable to the supplied ULS cost structure.");
113 _formulationBuilder.Build(
121 await _modelSolver.SolveAsync(
125 .ConfigureAwait(
false);
128 await TryRecoverRejectedCandidateAsync(
133 .ConfigureAwait(
false);
139 MapStatusWithoutSolution(
174 "The mathematical model returned a valid native " +
175 "solution, but ULS-domain reconstruction failed: " +
194 $
"Portable-model objective " +
195 $
"{execution.ObjectiveValue.Value:G17} differs from " +
196 $
"reconstructed ULS objective " +
197 $
"{solution.TotalCost:G17}.");
215 catch (Exception exception)
216 when (exception is not OperationCanceledException)
225 $
"ULS solution reconstruction failed: {exception.Message}");
229 private async ValueTask<LinearModelSolveResult>
230 TryRecoverRejectedCandidateAsync(
234 CancellationToken cancellationToken)
237 !ShouldAttemptFixedIntegerPolishing(
250 BuildFixedIntegerPolishingModel(
255 catch (Exception exception)
256 when (exception is not OperationCanceledException)
258 return AppendDiagnostic(
260 "Fixed-integer polishing could not build the recovery model: " +
265 CloneOptions(options);
268 polishOptions.ExportModelPath =
276 await _modelSolver.SolveAsync(
280 .ConfigureAwait(
false);
282 catch (OperationCanceledException)
286 catch (Exception exception)
288 return AppendDiagnostic(
290 "Fixed-integer polishing failed during continuous re-optimization: " +
296 return AppendDiagnostic(
298 "Fixed-integer polishing did not recover an independently " +
299 $
"feasible solution (status {polished.Status}; native " +
300 $
"'{polished.NativeStatus}').");
304 execution.SolverReportedStatus ==
305 LinearModelSolveStatus.Optimal
306 ? LinearModelSolveStatus.Optimal
310 execution.Diagnostics
313 "Fixed-integer polishing recovered the candidate by " +
314 "fixing normalized integer decisions and re-optimizing " +
315 "the remaining continuous model."
320 string nativeStatus =
322 ?
"fixed-integer polish: " + polished.NativeStatus
323 : execution.NativeStatus +
324 " | fixed-integer polish: " +
327 return new LinearModelSolveResult(
342 SolverReportedStatus =
343 execution.SolverReportedStatus
347 private static bool ShouldAttemptFixedIntegerPolishing(
349 LinearModelSolveResult execution,
350 LinearModelSolveOptions options)
363 foreach (LinearVariable variable
in model.
Variables)
373 !
double.IsFinite(value))
381 MidpointRounding.AwayFromZero);
383 if (Math.Abs(value - rounded) >
393 private static LinearModel BuildFixedIntegerPolishingModel(
394 LinearModel original,
395 IReadOnlyDictionary<int, double> candidateValues,
396 double integralityTolerance)
405 LinearVariable variable =
417 if (!candidateValues.TryGetValue(
419 out
double candidate) ||
420 !
double.IsFinite(candidate))
422 throw new InvalidOperationException(
423 $
"No finite candidate value exists for integer variable " +
424 $
"'{variable.Name}'.");
430 MidpointRounding.AwayFromZero);
432 if (Math.Abs(candidate - fixedValue) >
433 integralityTolerance)
435 throw new InvalidOperationException(
436 $
"Integer variable '{variable.Name}' is too fractional " +
437 "for fixed-integer polishing.");
442 integralityTolerance ||
445 integralityTolerance)
447 throw new InvalidOperationException(
448 $
"Rounded integer value {fixedValue:G17} for " +
449 $
"'{variable.Name}' lies outside its original bounds.");
463 return new LinearModel(
464 original.
Name +
"-FixedIntegerPolish",
470 private static LinearModelSolveResult AppendDiagnostic(
471 LinearModelSolveResult execution,
474 return new LinearModelSolveResult(
485 SolverReportedStatus =
486 execution.SolverReportedStatus
494 LinearModelSolveStatus.Infeasible =>
497 LinearModelSolveStatus.Cancelled or
498 LinearModelSolveStatus.SolverUnavailable or
499 LinearModelSolveStatus.Unknown =>
502 LinearModelSolveStatus.Unbounded or
503 LinearModelSolveStatus.InfeasibleOrUnbounded or
504 LinearModelSolveStatus.Failed =>
507 LinearModelSolveStatus.Optimal or
508 LinearModelSolveStatus.Feasible =>
512 UlsSolveStatus.Failed
516 private static bool ObjectivesAgree(
517 double modelObjective,
525 Math.Abs(modelObjective),
526 Math.Abs(ulsObjective)));
534 private static string BuildMessage(
535 LinearModelSolveResult execution)
540 if (execution.
Solver is not
null)
543 $
"Optimization engine: " +
544 $
"{execution.Solver.SolverName} " +
545 $
"{execution.Solver.SolverVersion}".Trim());
548 if (!
string.IsNullOrWhiteSpace(
552 $
"Native status: {execution.NativeStatus}");
565 !
string.IsNullOrWhiteSpace(part)));
568 private static LinearModelSolveOptions CloneOptions(
569 LinearModelSolveOptions source)
573 return new LinearModelSolveOptions
576 AllowFallbackWhenExplicit =
578 FeasibilityTolerance =
582 IntegralityTolerance =
584 NearIntegerTolerance =
586 EnableFixedIntegerPolishing =
593 source.TemporaryRootPath
Represents a validated classical uncapacitated lot-sizing problem.
bool IsFeasible
Gets whether all checked requirements passed.
Configures one solver-backed execution of a portable linear model.
SolverKind Solver
Gets or sets the requested solver. The default is automatic selection.
bool AllowFallbackWhenExplicit
Gets or sets whether an explicitly requested solver may fall back to another solver when unavailable.
double NearIntegerTolerance
Gets or sets the tolerance used to clean continuous values that are numerically indistinguishable fro...
double ZeroTolerance
Gets or sets the tolerance used to identify numerical zero before validation and objective reconstruc...
double IntegralityTolerance
Gets or sets the integrality tolerance used both for normalization and the independent solution check...
bool KeepTemporaryFiles
Gets or sets whether temporary model/solution/log artifacts are retained.
bool EnableFixedIntegerPolishing
Gets or sets whether a solver candidate rejected only after numerical normalization may be recovered ...
void EnsureValid()
Validates this option set.
string ExportModelPath
Gets or sets an optional path receiving the exact LP model submitted to the selected solver.
double FeasibilityTolerance
Gets or sets the feasibility tolerance used by the independent solution checker.
Result of executing a solver-independent linear or mixed-integer model.
SolverExecutionInfo? Solver
Gets selected-solver provenance, when a solver was selected.
LinearModelSolutionValidation? Validation
Gets the independent validation result, when a solution exists.
bool HasFeasibleSolution
Gets whether the result contains an independently valid solution.
TimeSpan SolveDuration
Gets elapsed solver execution time.
string NativeStatus
Gets the provider-native status/log summary.
string ArtifactDirectory
Gets the retained temporary artifact directory when KeepTemporaryFiles was enabled.
IReadOnlyList< string > Diagnostics
Gets execution and validation diagnostics.
IReadOnlyDictionary< int, double > VariableValues
Gets variable values keyed by portable variable id.
LinearModelSolveStatus SolverReportedStatus
Gets the status reported by the optimization engine before independent portable-model validation can ...
string ModelName
Gets the submitted model name.
LinearModelSolveStatus Status
Gets the normalized solve status after independent validation.
double? ObjectiveValue
Gets the independently recomputed objective value.
High-level solver-independent execution service for portable linear models.
Immutable solver-independent linear or mixed-integer linear model.
IReadOnlyList< LinearVariable > Variables
Gets all variables.
LinearObjective Objective
Gets the minimization objective.
IReadOnlyList< LinearConstraint > Constraints
Gets all constraints.
bool IsMixedInteger
Gets whether the model contains integer or binary variables.
string Name
Gets the model name.
int VariableCount
Gets the number of variables.
double UpperBound
Gets the upper bound.
LinearVariableType Type
Gets the variable domain.
string Name
Gets the solver-independent variable name.
int Id
Gets the stable zero-based variable identifier.
double LowerBound
Gets the lower bound.
ULS solve result enriched with mathematical-formulation and optimization engine provenance.
Represents a feasible production plan for a ULS problem.
double TotalCost
Gets the complete objective value.
Represents the outcome returned by a ULS solution strategy.
Independent ULS-domain validation report for one production plan.
IReadOnlyList< string > Diagnostics
Gets validation diagnostics.
bool IsFeasible
Gets whether the plan passed every ULS-domain check.
Independently verifies a ULS production plan against the original UlsProblem.
static UlsSolutionValidationResult Validate(UlsProblem problem, UlsSolution solution, double tolerance=1.0e-7)
Validates inventory balance, nonnegativity, setup linking, final inventory and all objective componen...
Optional asynchronous companion contract for ULS strategies whose implementation delegates work to an...
Defines the common strategy contract implemented by every ULS solver.
UlsSolverKind
Identifies the broad family of a ULS solution strategy.
LinearModelSolveStatus
Describes the termination state of a solver-backed portable linear model.
@ Optimal
The model was solved to proven optimality.
LinearVariableType
Identifies the domain of a variable in a portable linear mathematical model.
UlsSolveStatus
Describes the mathematical status of a ULS solve.
@ Optimal
A globally optimal solution has been found.