ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
CoinOrCbcLinearModelExecutor.cs
Go to the documentation of this file.
1using System.Diagnostics;
3
5
6/// <summary>
7/// Executes portable LP/MILP models through the stand-alone COIN-OR CBC
8/// executable.
9/// </summary>
10public sealed class CoinOrCbcLinearModelExecutor :
12{
13 private readonly ExternalSolverProcessRunner _runner =
14 new();
15
16 /// <inheritdoc />
18 SolverKind.CoinOrCbc;
19
20 /// <inheritdoc />
21 public async ValueTask<LinearModelSolveResult> SolveAsync(
22 LinearModel model,
23 SolverSelectionResult selection,
25 CancellationToken cancellationToken = default)
26 {
27 ArgumentNullException.ThrowIfNull(model);
28 ArgumentNullException.ThrowIfNull(selection);
29 ArgumentNullException.ThrowIfNull(options);
30
31 if (selection.SelectedSolver != SolverKind.CoinOrCbc ||
32 selection.Availability is null)
33 {
34 throw new ArgumentException(
35 "The supplied selection does not target COIN-OR CBC.",
36 nameof(selection));
37 }
38
39 string executable =
41
42 if (string.IsNullOrWhiteSpace(executable) ||
43 !File.Exists(executable))
44 {
45 return Failure(
46 model,
47 selection,
48 "The selected CBC executable is no longer available.");
49 }
50
51 string artifacts =
54 options);
55
56 var stopwatch =
57 Stopwatch.StartNew();
58
59 try
60 {
61 string modelPath =
62 Path.Combine(
63 artifacts,
64 "model.lp");
65
66 string solutionPath =
67 Path.Combine(
68 artifacts,
69 "solution.sol");
70
72 model,
73 modelPath);
74
76 modelPath,
77 options);
78
80 await _runner.RunAsync(
81 executable,
82 [
83 modelPath,
84 "-solve",
85 "-solu",
86 solutionPath,
87 "-quit"
88 ],
89 artifacts,
90 standardInput: null,
91 cancellationToken);
92
93 stopwatch.Stop();
94
95 string output =
96 process.CombinedOutput;
97
98 string solutionHeader =
99 ReadFirstLine(solutionPath);
100
101 bool solutionExists =
102 File.Exists(solutionPath) &&
103 !ContainsNoSolution(
104 solutionHeader);
105
106 IReadOnlyDictionary<int, double> values =
108 solutionPath);
109
111 MapStatus(
112 output,
113 solutionHeader,
114 solutionExists,
115 process.ExitCode);
116
118 model,
119 selection,
120 options,
121 status,
122 values,
123 solutionExists,
124 stopwatch.Elapsed,
125 !string.IsNullOrWhiteSpace(solutionHeader)
126 ? solutionHeader
128 [
129 $"cbc exit code: {process.ExitCode}.",
130 !string.IsNullOrWhiteSpace(solutionHeader)
131 ? solutionHeader
133 ],
134 artifacts);
135 }
136 catch (OperationCanceledException)
137 {
138 stopwatch.Stop();
139
140 return new LinearModelSolveResult(
141 model.Name,
142 LinearModelSolveStatus.Cancelled,
143 new SolverExecutionInfo(selection),
144 null,
145 null,
146 stopwatch.Elapsed,
147 "Cancelled",
148 ["CBC execution was cancelled by the caller."],
149 options.KeepTemporaryFiles
150 ? artifacts
151 : string.Empty);
152 }
153 catch (Exception exception)
154 {
155 stopwatch.Stop();
156
157 return new LinearModelSolveResult(
158 model.Name,
160 new SolverExecutionInfo(selection),
161 null,
162 null,
163 stopwatch.Elapsed,
164 exception.GetType().Name,
165 [exception.Message],
166 options.KeepTemporaryFiles
167 ? artifacts
168 : string.Empty);
169 }
170 finally
171 {
173 artifacts,
174 options);
175 }
176 }
177
178 /// <summary>
179 /// Normalizes the status reported by CBC.
180 /// </summary>
181 /// <remarks>
182 /// CBC can emit transient simplex/presolve diagnostics containing words
183 /// such as "infeasible" before subsequently producing an optimal solution.
184 /// The solution-file header is therefore authoritative when it contains a
185 /// terminal status. A valid candidate solution also takes precedence over
186 /// generic diagnostic text from earlier phases of the solve.
187 /// </remarks>
189 string output,
190 string solutionHeader,
191 bool solutionExists,
192 int exitCode)
193 {
194 if (solutionHeader.StartsWith(
195 "Optimal",
196 StringComparison.OrdinalIgnoreCase))
197 {
198 return LinearModelSolveStatus.Optimal;
199 }
200
201 if (solutionHeader.Contains(
202 "infeasible or unbounded",
203 StringComparison.OrdinalIgnoreCase))
204 {
205 return LinearModelSolveStatus.InfeasibleOrUnbounded;
206 }
207
208 if (solutionHeader.Contains(
209 "infeasible",
210 StringComparison.OrdinalIgnoreCase))
211 {
212 return LinearModelSolveStatus.Infeasible;
213 }
214
215 if (solutionHeader.Contains(
216 "unbounded",
217 StringComparison.OrdinalIgnoreCase))
218 {
219 return LinearModelSolveStatus.Unbounded;
220 }
221
222 if (solutionExists)
223 {
224 if (output.Contains(
225 "optimal solution found",
226 StringComparison.OrdinalIgnoreCase))
227 {
228 return LinearModelSolveStatus.Optimal;
229 }
230
231 return LinearModelSolveStatus.Feasible;
232 }
233
234 if (output.Contains(
235 "infeasible or unbounded",
236 StringComparison.OrdinalIgnoreCase))
237 {
238 return LinearModelSolveStatus.InfeasibleOrUnbounded;
239 }
240
241 if (output.Contains(
242 "infeasible",
243 StringComparison.OrdinalIgnoreCase))
244 {
245 return LinearModelSolveStatus.Infeasible;
246 }
247
248 if (output.Contains(
249 "unbounded",
250 StringComparison.OrdinalIgnoreCase))
251 {
252 return LinearModelSolveStatus.Unbounded;
253 }
254
255 if (output.Contains(
256 "optimal solution found",
257 StringComparison.OrdinalIgnoreCase))
258 {
259 return LinearModelSolveStatus.Optimal;
260 }
261
262 return exitCode == 0
264 : LinearModelSolveStatus.Failed;
265 }
266
267 private static bool ContainsNoSolution(
268 string header)
269 {
270 return header.Contains(
271 "no integer solution",
272 StringComparison.OrdinalIgnoreCase) ||
273 header.Contains(
274 "infeasible",
275 StringComparison.OrdinalIgnoreCase) ||
276 header.Contains(
277 "unbounded",
278 StringComparison.OrdinalIgnoreCase);
279 }
280
281 private static string ReadFirstLine(
282 string path)
283 {
284 if (!File.Exists(path))
285 {
286 return string.Empty;
287 }
288
289 using var reader =
290 new StreamReader(path);
291
292 return reader.ReadLine() ??
293 string.Empty;
294 }
295
296 private static LinearModelSolveResult Failure(
297 LinearModel model,
298 SolverSelectionResult selection,
299 string diagnostic)
300 {
301 return new LinearModelSolveResult(
302 model.Name,
304 new SolverExecutionInfo(selection),
305 null,
306 null,
307 TimeSpan.Zero,
308 "Executable unavailable",
309 [diagnostic]);
310 }
311}
Runs a solver command-line process with cancellation and captured output.
Configures one solver-backed execution of a portable linear model.
bool KeepTemporaryFiles
Gets or sets whether temporary model/solution/log artifacts are retained.
Result of executing a solver-independent linear or mixed-integer model.
Parses solver text solutions using portable variable names v_<id>.
static IReadOnlyDictionary< int, double > ParseFile(string path)
Parses a text solution file.
Writes a conservative LP representation understood by all four supported execution backends.
void Write(LinearModel model, string path)
Writes the supplied model to an LP file.
Executes portable LP/MILP models through the stand-alone COIN-OR CBC executable.
static LinearModelSolveStatus MapStatus(string output, string solutionHeader, bool solutionExists, int exitCode)
Normalizes the status reported by CBC.
SolverKind SolverKind
Gets the concrete solver implemented by this executor.
async ValueTask< LinearModelSolveResult > SolveAsync(LinearModel model, SolverSelectionResult selection, LinearModelSolveOptions options, CancellationToken cancellationToken=default)
Executes one portable model using an already selected solver.
static LinearModelSolveResult BuildSolutionResult(LinearModel model, SolverSelectionResult selection, LinearModelSolveOptions options, LinearModelSolveStatus proposedStatus, IReadOnlyDictionary< int, double > returnedValues, bool hasCandidateSolution, TimeSpan duration, string nativeStatus, IEnumerable< string > diagnostics, string artifactDirectory)
static string CreateArtifactDirectory(SolverKind solverKind, LinearModelSolveOptions options)
static void DeleteArtifactsUnlessRetained(string directory, LinearModelSolveOptions options)
static void ExportModelIfRequested(string generatedModelPath, LinearModelSolveOptions options)
Immutable solver-independent linear or mixed-integer linear model.
Definition LinearModel.cs:7
string NativeLibraryPath
Gets the native library or executable path, when applicable.
Serializable-style immutable snapshot of the solver selected for one solver-backed ULS execution.
Describes the outcome of optimization-solver selection.
SolverKind SelectedSolver
Gets the selected solver kind, or Unknown when none was selected.
SolverAvailabilityInfo? Availability
Gets machine-availability information for the selected solver.
Executes the portable LinearModel with one concrete solver.
LinearModelSolveStatus
Describes the termination state of a solver-backed portable linear model.
record ExternalSolverProcessResult(int ExitCode, string StandardOutput, string StandardError)