LotSizingDataModel.Solver 2.0.1
Solver-independent modeling, execution, monitoring and adapter infrastructure.
Loading...
Searching...
No Matches
PortableLpModelWriter.cs
Go to the documentation of this file.
1using System.Globalization;
2using System.Text;
4
6
7/// <summary>
8/// Writes a solver-independent <see cref="MathematicalModel"/> to a
9/// conservative LP file understood by the supported native solvers.
10/// </summary>
11/// <remarks>
12/// <para>
13/// Stable variable names of the form <c>v_&lt;id&gt;</c> are deliberately used
14/// instead of domain names. This makes solution-file parsing independent of
15/// spaces, punctuation and solver-specific name normalization.
16/// </para>
17/// <para>
18/// Semi-continuous and semi-integer variables are intentionally rejected in
19/// this first portable writer, matching the first CPLEX adapter's current
20/// support level.
21/// </para>
22/// </remarks>
23public sealed class PortableLpModelWriter
24{
25 /// <summary>
26 /// Returns the stable LP name associated with a mathematical variable.
27 /// </summary>
28 /// <param name="variableId">Mathematical variable identifier.</param>
29 /// <returns>Portable variable name.</returns>
30 public static string GetVariableName(
31 int variableId)
32 {
33 return $"v_{variableId}";
34 }
35
36 /// <summary>
37 /// Writes the specified model to an LP file.
38 /// </summary>
39 /// <param name="model">Validated mathematical model.</param>
40 /// <param name="path">Destination LP path.</param>
41 public void Write(
43 string path)
44 {
45 ArgumentNullException.ThrowIfNull(model);
46
47 if (string.IsNullOrWhiteSpace(path))
48 {
49 throw new ArgumentException(
50 "A destination path is required.",
51 nameof(path));
52 }
53
54 model.EnsureValid();
55
56 foreach (MathematicalVariable variable in model.Variables)
57 {
58 if (variable.VariableType is
59 MathematicalVariableType.SemiContinuous or
60 MathematicalVariableType.SemiInteger)
61 {
62 throw new NotSupportedException(
63 $"Portable LP export does not yet support variable " +
64 $"type '{variable.VariableType}'.");
65 }
66 }
67
68 string fullPath =
69 Path.GetFullPath(path);
70
71 string? directory =
72 Path.GetDirectoryName(fullPath);
73
74 if (!string.IsNullOrWhiteSpace(directory))
75 {
76 Directory.CreateDirectory(directory);
77 }
78
79 using var writer =
80 new StreamWriter(
81 fullPath,
82 false,
83 new UTF8Encoding(false));
84
85 writer.WriteLine(
86 "\\ Generated by LotSizingDataModel portable solver adapter");
87
88 writer.WriteLine(
89 model.Objective.Sense == ObjectiveSense.Minimize
90 ? "Minimize"
91 : "Maximize");
92
93 writer.Write(" obj: ");
94 WriteExpression(
95 writer,
96 model.Objective.Expression,
97 includeConstant: true);
98 writer.WriteLine();
99
100 writer.WriteLine("Subject To");
101
102 int constraintIndex = 0;
103
104 foreach (LinearConstraint constraint in model.Constraints)
105 {
106 if (!constraint.IsEnabled)
107 {
108 continue;
109 }
110
111 writer.Write(
112 $" c_{constraintIndex}: ");
113
114 WriteExpression(
115 writer,
116 constraint.LeftHandSide,
117 includeConstant: false);
118
119 double adjustedRightHandSide =
120 constraint.RightHandSide -
121 constraint.LeftHandSide.Constant;
122
123 writer.Write(
124 constraint.Sense switch
125 {
126 MathematicalConstraintSense.LessThanOrEqual =>
127 " <= ",
128 MathematicalConstraintSense.Equal =>
129 " = ",
130 MathematicalConstraintSense.GreaterThanOrEqual =>
131 " >= ",
132 _ =>
133 throw new NotSupportedException(
134 $"Unsupported mathematical constraint sense " +
135 $"'{constraint.Sense}'.")
136 });
137
138 writer.WriteLine(
139 FormatNumber(adjustedRightHandSide));
140
141 constraintIndex++;
142 }
143
144 writer.WriteLine("Bounds");
145
146 foreach (MathematicalVariable variable in model.Variables)
147 {
148 WriteBounds(
149 writer,
150 variable);
151 }
152
153 MathematicalVariable[] integerVariables =
154 model.Variables
155 .Where(
156 variable =>
157 variable.VariableType ==
159 .ToArray();
160
161 if (integerVariables.Length > 0)
162 {
163 writer.WriteLine("Generals");
164
165 foreach (MathematicalVariable variable in integerVariables)
166 {
167 writer.WriteLine(
168 $" {GetVariableName(variable.Id)}");
169 }
170 }
171
172 MathematicalVariable[] binaryVariables =
173 model.Variables
174 .Where(
175 variable =>
176 variable.VariableType ==
178 .ToArray();
179
180 if (binaryVariables.Length > 0)
181 {
182 writer.WriteLine("Binaries");
183
184 foreach (MathematicalVariable variable in binaryVariables)
185 {
186 writer.WriteLine(
187 $" {GetVariableName(variable.Id)}");
188 }
189 }
190
191 writer.WriteLine("End");
192 }
193
194 private static void WriteExpression(
195 TextWriter writer,
196 LinearExpression expression,
197 bool includeConstant)
198 {
199 bool wroteTerm =
200 false;
201
202 if (includeConstant &&
203 expression.Constant != 0.0)
204 {
205 writer.Write(
206 FormatNumber(expression.Constant));
207
208 wroteTerm =
209 true;
210 }
211
212 foreach (LinearTerm term in expression.Terms)
213 {
214 double coefficient =
215 term.Coefficient;
216
217 if (coefficient == 0.0)
218 {
219 continue;
220 }
221
222 if (wroteTerm)
223 {
224 writer.Write(
225 coefficient >= 0.0
226 ? " + "
227 : " - ");
228 }
229 else if (coefficient < 0.0)
230 {
231 writer.Write("-");
232 }
233
234 double magnitude =
235 Math.Abs(coefficient);
236
237 if (magnitude != 1.0)
238 {
239 writer.Write(
240 FormatNumber(magnitude));
241 writer.Write(" ");
242 }
243
244 writer.Write(
246
247 wroteTerm =
248 true;
249 }
250
251 if (!wroteTerm)
252 {
253 writer.Write("0");
254 }
255 }
256
257 private static void WriteBounds(
258 TextWriter writer,
259 MathematicalVariable variable)
260 {
261 string name =
262 GetVariableName(variable.Id);
263
264 double lower =
265 variable.LowerBound;
266
267 double upper =
268 variable.UpperBound;
269
270 if (double.IsNegativeInfinity(lower) &&
271 double.IsPositiveInfinity(upper))
272 {
273 writer.WriteLine(
274 $" {name} free");
275
276 return;
277 }
278
279 if (lower == upper)
280 {
281 writer.WriteLine(
282 $" {name} = {FormatNumber(lower)}");
283
284 return;
285 }
286
287 if (!double.IsNegativeInfinity(lower) &&
288 !double.IsPositiveInfinity(upper))
289 {
290 writer.WriteLine(
291 $" {FormatNumber(lower)} <= {name} <= " +
292 $"{FormatNumber(upper)}");
293
294 return;
295 }
296
297 if (!double.IsNegativeInfinity(lower))
298 {
299 writer.WriteLine(
300 $" {FormatNumber(lower)} <= {name}");
301
302 return;
303 }
304
305 writer.WriteLine(
306 $" {name} <= {FormatNumber(upper)}");
307 }
308
309 private static string FormatNumber(
310 double value)
311 {
312 if (!double.IsFinite(value))
313 {
314 throw new ArgumentOutOfRangeException(
315 nameof(value),
316 value,
317 "Only finite coefficients and right-hand sides can " +
318 "be written as LP numeric values.");
319 }
320
321 return value.ToString(
322 "G17",
323 CultureInfo.InvariantCulture);
324 }
325}
Writes a solver-independent MathematicalModel to a conservative LP file understood by the supported n...
static string GetVariableName(int variableId)
Returns the stable LP name associated with a mathematical variable.
void Write(MathematicalModel model, string path)
Writes the specified model to an LP file.
Represents one linear constraint in a mathematical optimization model.
MathematicalConstraintSense Sense
Gets or sets the relational sense of the constraint.
bool IsEnabled
Gets or sets a value indicating whether the constraint is enabled.
LinearExpression LeftHandSide
Gets or sets the left-hand-side linear expression.
Represents a linear expression composed of variable terms and a constant value.
List< LinearTerm > Terms
Gets the linear terms of the expression.
double Constant
Gets or sets the constant value of the expression.
Represents one coefficient-variable term in a linear expression.
Definition LinearTerm.cs:13
int VariableId
Gets or sets the identifier of the referenced mathematical variable.
Definition LinearTerm.cs:49
double Coefficient
Gets or sets the coefficient multiplying the referenced variable.
Definition LinearTerm.cs:60
Represents a solver-independent mathematical optimization model.
void EnsureValid()
Validates the complete mathematical model.
Represents one decision variable in a mathematical optimization model.
MathematicalVariableType VariableType
Gets or sets the variable domain.
int Id
Gets or sets the unique variable identifier within the mathematical model.
double LowerBound
Gets or sets the variable lower bound.
double UpperBound
Gets or sets the variable upper bound.
ObjectiveSense
Identifies the optimization direction of a mathematical objective.
MathematicalVariableType
Identifies the domain of a mathematical decision variable.