ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
GurobiSolverAdapter.cs
Go to the documentation of this file.
1using System.Text.RegularExpressions;
3
5
6/// <summary>
7/// Detects and validates Gurobi through the official gurobi_cl executable.
8/// </summary>
9public sealed partial class GurobiSolverAdapter :
11{
12 private readonly ExternalSolverProcessProbe _probe =
13 new();
14
15 /// <summary>Initializes the Gurobi adapter.</summary>
26
27 /// <inheritdoc />
28 public override string AdapterId =>
29 "ULSAlgorithms.Solver.Gurobi";
30
31 /// <inheritdoc />
32 public override string AdapterName =>
33 "ULSAlgorithms Gurobi Adapter";
34
35 /// <inheritdoc />
36 public override SolverKind SolverKind =>
37 SolverKind.Gurobi;
38
39 /// <inheritdoc />
40 public override async ValueTask<SolverAvailabilityInfo>
42 CancellationToken cancellationToken = default)
43 {
44 cancellationToken.ThrowIfCancellationRequested();
45
46 string executablePath =
47 ResolveExecutablePath();
48
49 if (string.IsNullOrWhiteSpace(executablePath))
50 {
51 return new SolverAvailabilityInfo(
52 SolverKind.Gurobi,
53 SolverAvailabilityStatus.NotInstalled,
54 solverName: "Gurobi Optimizer",
55 diagnostics:
56 [
57 "gurobi_cl was not found. Put it on PATH, define " +
58 "GUROBI_HOME, or set ULSALGORITHMS_GUROBI_EXECUTABLE."
59 ]);
60 }
61
62 try
63 {
65 await _probe.RunAsync(
66 executablePath,
67 ["--version"],
68 cancellationToken);
69
70 string versionOutput =
71 versionResult.CombinedOutput;
72
73 if (versionResult.ExitCode != 0)
74 {
75 return Failure(
76 executablePath,
77 SolverAvailabilityStatus.LoadFailure,
78 FirstMeaningfulLine(versionOutput) ??
79 $"gurobi_cl exited with code {versionResult.ExitCode}.");
80 }
81
82 string version =
83 ParseVersion(versionOutput);
84
86 await _probe.RunAsync(
87 executablePath,
88 ["--license"],
89 cancellationToken);
90
91 string licenseOutput =
92 licenseResult.CombinedOutput;
93
94 if (licenseResult.ExitCode != 0 ||
95 LooksLikeLicenseFailure(licenseOutput))
96 {
97 return new SolverAvailabilityInfo(
98 SolverKind.Gurobi,
99 SolverAvailabilityStatus.LicenseUnavailable,
100 solverName: "Gurobi Optimizer",
101 solverVersion: version,
102 installationPath:
103 Path.GetDirectoryName(executablePath) ??
104 string.Empty,
105 nativeLibraryPath: executablePath,
106 licenseInformation:
107 FirstMeaningfulLine(licenseOutput) ??
108 "Gurobi license check failed.",
109 diagnostics:
110 [
111 FirstMeaningfulLine(licenseOutput) ??
112 "gurobi_cl --license did not report a usable license."
113 ]);
114 }
115
116 return new SolverAvailabilityInfo(
117 SolverKind.Gurobi,
118 SolverAvailabilityStatus.Available,
119 solverName: "Gurobi Optimizer",
120 solverVersion: version,
121 installationPath:
122 Path.GetDirectoryName(executablePath) ??
123 string.Empty,
124 nativeLibraryPath: executablePath,
125 licenseInformation:
126 FirstMeaningfulLine(licenseOutput) ??
127 "Gurobi license information resolved.",
128 diagnostics:
129 [
130 $"Gurobi command-line executable detected at " +
131 $"'{executablePath}'.",
132 FirstMeaningfulLine(versionOutput) ??
133 "Gurobi version command completed successfully."
134 ]);
135 }
136 catch (OperationCanceledException)
137 {
138 throw;
139 }
140 catch (Exception exception)
141 {
142 return Failure(
143 executablePath,
144 ClassifyFailure(exception),
145 exception.Message);
146 }
147 }
148
149 private static SolverAvailabilityInfo Failure(
150 string executablePath,
152 string diagnostic)
153 {
154 return new SolverAvailabilityInfo(
155 SolverKind.Gurobi,
156 status,
157 solverName: "Gurobi Optimizer",
158 installationPath:
159 Path.GetDirectoryName(executablePath) ??
160 string.Empty,
161 nativeLibraryPath: executablePath,
162 diagnostics: [diagnostic]);
163 }
164
165 private static string ResolveExecutablePath()
166 {
168 [
169 "ULSALGORITHMS_GUROBI_EXECUTABLE",
170 "LOTSIZING_GUROBI_EXECUTABLE"
171 ],
172 ["GUROBI_HOME"],
173 [
174 Path.Combine("bin", "gurobi_cl.exe"),
175 Path.Combine("bin", "gurobi_cl")
176 ],
177 ["gurobi_cl.exe", "gurobi_cl"],
178 EnumerateCommonWindowsCandidates());
179 }
180
181 private static IEnumerable<string>
182 EnumerateCommonWindowsCandidates()
183 {
184 if (!OperatingSystem.IsWindows())
185 {
186 yield break;
187 }
188
189 string systemDrive =
190 Path.GetPathRoot(
191 Environment.GetFolderPath(
192 Environment.SpecialFolder.System)) ??
193 @"C:\";
194
195 IEnumerable<string> roots;
196
197 try
198 {
199 roots =
200 Directory
201 .EnumerateDirectories(
202 systemDrive,
203 "gurobi*",
204 SearchOption.TopDirectoryOnly)
205 .OrderByDescending(
206 static path =>
207 Path.GetFileName(path),
208 StringComparer.OrdinalIgnoreCase)
209 .ToArray();
210 }
211 catch
212 {
213 yield break;
214 }
215
216 foreach (string root in roots)
217 {
218 yield return
219 Path.Combine(
220 root,
221 "win64",
222 "bin",
223 "gurobi_cl.exe");
224
225 yield return
226 Path.Combine(
227 root,
228 "bin",
229 "gurobi_cl.exe");
230 }
231 }
232
233 private static bool LooksLikeLicenseFailure(
234 string text)
235 {
236 string[] fragments =
237 [
238 "no license",
239 "license not found",
240 "unable to open license",
241 "unable to retrieve license",
242 "license expired",
243 "error 10009",
244 "not licensed"
245 ];
246
247 return fragments.Any(
248 fragment =>
249 text.Contains(
250 fragment,
251 StringComparison.OrdinalIgnoreCase));
252 }
253
254 private static string ParseVersion(
255 string text)
256 {
257 Match match =
258 VersionRegex().Match(text ?? string.Empty);
259
260 return match.Success
261 ? match.Groups[1].Value
262 : string.Empty;
263 }
264
265 private static string? FirstMeaningfulLine(
266 string text)
267 {
268 return text
269 .Split(
270 ['\r', '\n'],
271 StringSplitOptions.RemoveEmptyEntries |
272 StringSplitOptions.TrimEntries)
273 .FirstOrDefault();
274 }
275
276 [GeneratedRegex(
277 @"(?:Gurobi(?:\s+Optimizer)?\s+version\s+|Gurobi\s+)(\d+(?:\.\d+){1,3})",
278 RegexOptions.IgnoreCase |
279 RegexOptions.CultureInvariant)]
280 private static partial Regex VersionRegex();
281}
override string AdapterName
Gets the adapter display name.
override SolverKind SolverKind
Gets the concrete solver targeted by this adapter.
override async ValueTask< SolverAvailabilityInfo > CheckAvailabilityAsync(CancellationToken cancellationToken=default)
Performs the adapter-specific machine discovery / load / license check.
override string AdapterId
Gets the stable adapter identifier.
static SolverAvailabilityStatus ClassifyFailure(Exception exception)
Maps an exception to a stable availability state.
OptimizationSolverAdapterBase(params SolverCapability[] capabilities)
Initializes an adapter with its supported capabilities.
Locates optional native-solver command-line executables without requiring their SDKs at compile time.
static string Resolve(IEnumerable< string > explicitExecutableEnvironmentVariables, IEnumerable< string > homeEnvironmentVariables, IEnumerable< string > relativeExecutablePaths, IEnumerable< string > pathExecutableNames, IEnumerable< string >? directCandidates=null)
Resolves an executable from explicit environment variables, installation-home hints,...
Executes short solver command-line probes and captures their output.
Describes solver availability and the installation selected by an adapter.
SolverAvailabilityStatus
Describes whether a solver can currently be used on the current computer.
SolverCapability
Identifies an optional capability exposed by an optimization-solver adapter.
@ LinearProgramming
Continuous linear programming.
@ OptimalityGapReporting
Best-bound and optimality-gap reporting.
@ MixedIntegerLinearProgramming
Mixed-integer linear programming.
@ SearchStatistics
Search-node and iteration statistics.
SolverKind
Identifies a mathematical optimization solver that can be used by solver-backed ULS algorithms.
Definition SolverKind.cs:15