ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
UlsFormulationSolutionMapper.cs
Go to the documentation of this file.
4
6
7internal static class UlsFormulationSolutionMapper
8{
9 internal static UlsSolution Map(
10 UlsProblem problem,
11 UlsFormulation formulation,
12 IReadOnlyDictionary<int, double> values,
13 double zeroTolerance,
14 double feasibilityTolerance)
15 {
16 ArgumentNullException.ThrowIfNull(problem);
17 ArgumentNullException.ThrowIfNull(formulation);
18 ArgumentNullException.ThrowIfNull(values);
19
20 var production =
21 new double[problem.Horizon];
22
23 var inventory =
24 new double[problem.Horizon];
25
26 var setup =
27 new bool[problem.Horizon];
28
29 switch (formulation.Kind)
30 {
31 case UlsFormulationKind.AggregateInventory:
32 MapAggregate(
33 problem,
34 formulation,
35 values,
36 production,
37 inventory,
38 setup,
39 feasibilityTolerance);
40 break;
41
42 case UlsFormulationKind.FacilityLocation:
43 MapFacilityLocation(
44 problem,
45 formulation,
46 values,
47 production,
48 inventory,
49 setup,
50 zeroTolerance,
51 feasibilityTolerance);
52 break;
53
54 case UlsFormulationKind.ShortestPath:
55 MapShortestPath(
56 problem,
57 formulation,
58 values,
59 production,
60 inventory,
61 setup,
62 zeroTolerance,
63 feasibilityTolerance);
64 break;
65
66 case UlsFormulationKind.InventoryEliminated:
67 MapInventoryEliminated(
68 problem,
69 formulation,
70 values,
71 production,
72 inventory,
73 setup,
74 feasibilityTolerance);
75 break;
76
77 default:
78 throw new NotSupportedException(
79 $"Unsupported ULS formulation kind '{formulation.Kind}'.");
80 }
81
82 ComputeCostComponents(
83 problem,
84 production,
85 inventory,
86 setup,
87 out double setupCost,
88 out double productionCost,
89 out double holdingCost);
90
92 production,
93 inventory,
94 setup,
95 setupCost,
96 productionCost,
97 holdingCost);
98 }
99
100 private static void MapAggregate(
101 UlsProblem problem,
102 UlsFormulation formulation,
103 IReadOnlyDictionary<int, double> values,
104 double[] production,
105 double[] inventory,
106 bool[] setup,
107 double feasibilityTolerance)
108 {
109 for (int period = 0;
110 period < problem.Horizon;
111 period++)
112 {
113 production[period] =
114 CleanNonnegative(
115 GetValue(
116 formulation.Variables.Production,
117 period,
118 values,
119 "production"),
120 feasibilityTolerance);
121
122 inventory[period] =
123 CleanNonnegative(
124 GetValue(
125 formulation.Variables.Inventory,
126 period,
127 values,
128 "inventory"),
129 feasibilityTolerance);
130
131 setup[period] =
132 GetBinaryDecision(
133 formulation.Variables.Setup,
134 period,
135 values,
136 "setup");
137 }
138 }
139
140 private static void MapFacilityLocation(
141 UlsProblem problem,
142 UlsFormulation formulation,
143 IReadOnlyDictionary<int, double> values,
144 double[] production,
145 double[] inventory,
146 bool[] setup,
147 double zeroTolerance,
148 double feasibilityTolerance)
149 {
150 foreach (KeyValuePair<(int First, int Second), int> entry in
151 formulation.Variables.Disaggregated)
152 {
153 double quantity =
154 CleanNonnegative(
155 GetValue(
156 values,
157 entry.Value,
158 $"q[{entry.Key.First},{entry.Key.Second}]"),
159 feasibilityTolerance);
160
161 production[entry.Key.First] +=
162 quantity;
163 }
164
165 for (int period = 0;
166 period < problem.Horizon;
167 period++)
168 {
169 if (Math.Abs(production[period]) <= zeroTolerance)
170 {
171 production[period] = 0.0;
172 }
173
174 setup[period] =
175 GetBinaryDecision(
176 formulation.Variables.Setup,
177 period,
178 values,
179 "setup");
180 }
181
182 ReconstructInventory(
183 problem,
184 production,
185 inventory,
186 zeroTolerance,
187 feasibilityTolerance);
188 }
189
190 private static void MapInventoryEliminated(
191 UlsProblem problem,
192 UlsFormulation formulation,
193 IReadOnlyDictionary<int, double> values,
194 double[] production,
195 double[] inventory,
196 bool[] setup,
197 double feasibilityTolerance)
198 {
199 for (int period = 0;
200 period < problem.Horizon;
201 period++)
202 {
203 production[period] =
204 CleanNonnegative(
205 GetValue(
206 formulation.Variables.Production,
207 period,
208 values,
209 "production"),
210 feasibilityTolerance);
211
212 setup[period] =
213 GetBinaryDecision(
214 formulation.Variables.Setup,
215 period,
216 values,
217 "setup");
218 }
219
220 ReconstructInventory(
221 problem,
222 production,
223 inventory,
224 zeroTolerance: feasibilityTolerance,
225 feasibilityTolerance);
226 }
227
228 private static void MapShortestPath(
229 UlsProblem problem,
230 UlsFormulation formulation,
231 IReadOnlyDictionary<int, double> values,
232 double[] production,
233 double[] inventory,
234 bool[] setup,
235 double zeroTolerance,
236 double feasibilityTolerance)
237 {
238 int node = 0;
239 int steps = 0;
240
241 while (node < problem.Horizon)
242 {
243 if (++steps > problem.Horizon + 1)
244 {
245 throw new InvalidOperationException(
246 "The shortest-path solution contains an invalid cycle.");
247 }
248
249 var outgoing =
250 formulation.Variables.Arcs
251 .Where(
252 entry =>
253 entry.Key.From == node)
254 .Select(
255 entry =>
256 new
257 {
258 entry.Key.To,
259 Value =
260 GetValue(
261 values,
262 entry.Value,
263 $"arc[{entry.Key.From},{entry.Key.To}]")
264 })
265 .Where(
266 candidate =>
267 candidate.Value > zeroTolerance)
268 .OrderByDescending(
269 candidate =>
270 candidate.Value)
271 .ThenBy(
272 candidate =>
273 candidate.To)
274 .ToArray();
275
276 if (outgoing.Length == 0)
277 {
278 throw new InvalidOperationException(
279 $"No positive outgoing shortest-path arc exists from node {node}.");
280 }
281
282 int next =
283 outgoing[0].To;
284
285 if (next <= node ||
286 next > problem.Horizon)
287 {
288 throw new InvalidOperationException(
289 $"Invalid shortest-path arc ({node},{next}).");
290 }
291
292 double segmentDemand = 0.0;
293
294 for (int demandPeriod = node;
295 demandPeriod < next;
296 demandPeriod++)
297 {
298 segmentDemand +=
299 problem.Demands[demandPeriod];
300 }
301
302 if (segmentDemand > zeroTolerance)
303 {
304 production[node] +=
305 segmentDemand;
306
307 setup[node] = true;
308 }
309
310 node = next;
311 }
312
313 ReconstructInventory(
314 problem,
315 production,
316 inventory,
317 zeroTolerance,
318 feasibilityTolerance);
319 }
320
321 private static void ReconstructInventory(
322 UlsProblem problem,
323 double[] production,
324 double[] inventory,
325 double zeroTolerance,
326 double feasibilityTolerance)
327 {
328 double previous = 0.0;
329
330 for (int period = 0;
331 period < problem.Horizon;
332 period++)
333 {
334 double value =
335 previous +
336 production[period] -
337 problem.Demands[period];
338
339 if (Math.Abs(value) <= zeroTolerance)
340 {
341 value = 0.0;
342 }
343
344 inventory[period] =
345 CleanNonnegative(
346 value,
347 feasibilityTolerance);
348
349 previous =
350 inventory[period];
351 }
352 }
353
354 private static void ComputeCostComponents(
355 UlsProblem problem,
356 double[] production,
357 double[] inventory,
358 bool[] setup,
359 out double setupCost,
360 out double productionCost,
361 out double holdingCost)
362 {
363 setupCost = 0.0;
364 productionCost = 0.0;
365 holdingCost = 0.0;
366
367 for (int period = 0;
368 period < problem.Horizon;
369 period++)
370 {
371 if (setup[period])
372 {
373 setupCost +=
374 problem.SetupCosts[period];
375 }
376
377 productionCost +=
378 production[period] *
379 problem.UnitProductionCosts[period];
380
381 holdingCost +=
382 inventory[period] *
383 problem.HoldingCosts[period];
384 }
385
386 if (!double.IsFinite(setupCost) ||
387 !double.IsFinite(productionCost) ||
388 !double.IsFinite(holdingCost))
389 {
390 throw new ArithmeticException(
391 "A reconstructed ULS cost component is not finite.");
392 }
393 }
394
395 private static double GetValue(
396 IReadOnlyDictionary<int, int> semanticMap,
397 int period,
398 IReadOnlyDictionary<int, double> values,
399 string family)
400 {
401 if (!semanticMap.TryGetValue(
402 period,
403 out int variableId))
404 {
405 throw new InvalidOperationException(
406 $"The formulation contains no {family} variable for period {period}.");
407 }
408
409 return GetValue(
410 values,
411 variableId,
412 $"{family}[{period}]");
413 }
414
415 private static double GetValue(
416 IReadOnlyDictionary<int, double> values,
417 int variableId,
418 string description)
419 {
420 if (!values.TryGetValue(
421 variableId,
422 out double value) ||
423 !double.IsFinite(value))
424 {
425 throw new InvalidOperationException(
426 $"No finite solver value exists for {description} " +
427 $"(variable id {variableId}).");
428 }
429
430 return value;
431 }
432
433 private static bool GetBinaryDecision(
434 IReadOnlyDictionary<int, int> semanticMap,
435 int period,
436 IReadOnlyDictionary<int, double> values,
437 string family)
438 {
439 double value =
440 GetValue(
441 semanticMap,
442 period,
443 values,
444 family);
445
446 if (value == 0.0)
447 {
448 return false;
449 }
450
451 if (value == 1.0)
452 {
453 return true;
454 }
455
456 throw new InvalidOperationException(
457 $"{family}[{period}] was not normalized to 0 or 1: {value:G17}.");
458 }
459
460 private static double CleanNonnegative(
461 double value,
462 double feasibilityTolerance)
463 {
464 if (!double.IsFinite(value))
465 {
466 throw new InvalidOperationException(
467 "A reconstructed ULS decision is not finite.");
468 }
469
470 if (value >= 0.0)
471 {
472 return value;
473 }
474
475 if (Math.Abs(value) <= feasibilityTolerance)
476 {
477 return 0.0;
478 }
479
480 throw new InvalidOperationException(
481 $"A reconstructed ULS decision is materially negative: {value:G17}.");
482 }
483}
static UlsSolution Map(UlsProblem problem, UlsFormulation formulation, IReadOnlyDictionary< int, double > values, double zeroTolerance, double feasibilityTolerance)
Solver-independent ULS mathematical formulation plus semantic variable map.
UlsFormulationVariableMap Variables
Gets semantic variable mappings.
UlsFormulationKind Kind
Gets the formulation kind.
IReadOnlyDictionary< int, int > Inventory
Gets inventory-variable ids by period.
IReadOnlyDictionary< int, int > Setup
Gets setup-variable ids by period.
IReadOnlyDictionary< int, int > Production
Gets production-variable ids by period.
IReadOnlyDictionary<(int First, int Second), int > Disaggregated
Gets disaggregated-variable ids. For the facility-location formulation, the key is (production period...
Represents a validated classical uncapacitated lot-sizing problem.
Definition UlsProblem.cs:23
ReadOnlySpan< double > UnitProductionCosts
Gets unit production costs by period.
int Horizon
Gets the number of planning periods.
Definition UlsProblem.cs:82
ReadOnlySpan< double > HoldingCosts
Gets end-of-period unit holding costs by period.
ReadOnlySpan< double > Demands
Gets demand by period.
Definition UlsProblem.cs:92
ReadOnlySpan< double > SetupCosts
Gets fixed setup costs by period.
Definition UlsProblem.cs:97
Represents a feasible production plan for a ULS problem.
Definition UlsSolution.cs:7
static UlsSolution FromOwnedBuffers(double[] productionQuantities, double[] endingInventories, bool[] setupDecisions, double setupCost, double productionCost, double holdingCost)
Creates a solution while transferring ownership of already allocated solver buffers.
UlsFormulationKind
Identifies a mathematical-programming formulation of classical ULS.