ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
WemmerlovPpbCore.cs
Go to the documentation of this file.
1using System.Buffers;
4
6
7/// <summary>
8/// Shared implementation of the PPB variants analyzed by Wemmerlöv (1983).
9/// </summary>
10internal static class WemmerlovPpbCore
11{
12 public static UlsSolveResult Solve(
13 UlsProblem problem,
14 string solverName,
15 double correctionFactor,
16 bool useLookAheadLookBack,
17 CancellationToken cancellationToken)
18 {
19 ArgumentNullException.ThrowIfNull(problem);
20 cancellationToken.ThrowIfCancellationRequested();
21
23 problem,
24 solverName);
25
26 if (correctionFactor < 0.0 ||
27 correctionFactor > 0.5 ||
28 !double.IsFinite(correctionFactor))
29 {
30 throw new ArgumentOutOfRangeException(
31 nameof(correctionFactor));
32 }
33
34 if (useLookAheadLookBack &&
35 !HasStrictlyPositiveDemand(problem))
36 {
37 throw new NotSupportedException(
38 $"{solverName} conservatively requires strictly positive " +
39 "demand in every period when Look-Ahead/Look-Back is enabled.");
40 }
41
42 var horizon = problem.Horizon;
43 var buffer = ArrayPool<int>.Shared.Rent(horizon);
44
45 try
46 {
47 var cycleEnds = buffer.AsSpan(0, horizon);
48 cycleEnds.Fill(-1);
49
50 var demands = problem.Demands;
51 var setupCost = problem.SetupCosts[0];
52
53 var holdingCost =
54 horizon > 1
55 ? problem.HoldingCosts[0]
56 : 0.0;
57
58 var start =
60 demands,
61 0);
62
63 while (start < horizon)
64 {
65 cancellationToken.ThrowIfCancellationRequested();
66
67 var end = SelectPpbCycleEnd(
68 demands,
69 start,
70 setupCost,
71 holdingCost,
72 correctionFactor);
73
74 if (useLookAheadLookBack)
75 {
76 end = ApplyLookAheadLookBack(
77 demands,
78 start,
79 end,
80 setupCost,
81 holdingCost,
82 correctionFactor);
83 }
84
85 cycleEnds[start] = end;
86
87 start =
89 demands,
90 end + 1);
91 }
92
94 problem,
95 cycleEnds,
96 solverName,
97 cancellationToken);
98 }
99 finally
100 {
101 ArrayPool<int>.Shared.Return(
102 buffer,
103 clearArray: false);
104 }
105 }
106
107 private static int SelectPpbCycleEnd(
108 ReadOnlySpan<double> demands,
109 int start,
110 double setupCost,
111 double holdingCost,
112 double correctionFactor)
113 {
114 if (holdingCost == 0.0)
115 {
116 return demands.Length - 1;
117 }
118
119 var epp = setupCost / holdingCost;
120
121 var adjustedPartPeriods =
122 MultiplyFinite(
123 correctionFactor,
124 demands[start],
125 "corrected part-period quantity");
126
127 var bestEnd = start;
128 var bestDifference =
129 Math.Abs(epp - adjustedPartPeriods);
130
131 for (var end = start + 1;
132 end < demands.Length;
133 end++)
134 {
135 adjustedPartPeriods = AddFinite(
136 adjustedPartPeriods,
137 MultiplyFinite(
138 end - start + correctionFactor,
139 demands[end],
140 "corrected part-period quantity"),
141 "corrected cumulative part-period quantity");
142
143 var difference =
144 Math.Abs(epp - adjustedPartPeriods);
145
146 if (difference < bestDifference ||
147 (difference == bestDifference &&
148 end > bestEnd))
149 {
150 bestDifference = difference;
151 bestEnd = end;
152 }
153
154 if (adjustedPartPeriods >= epp)
155 {
156 break;
157 }
158 }
159
160 return bestEnd;
161 }
162
163 private static int ApplyLookAheadLookBack(
164 ReadOnlySpan<double> demands,
165 int start,
166 int end,
167 double setupCost,
168 double holdingCost,
169 double correctionFactor)
170 {
171 var next = end + 1;
172
173 if (next >= demands.Length)
174 {
175 return end;
176 }
177
178 var coverage = end - start + 1;
179
180 // Figure 4 / Box 1:
181 // compare the incremental holding cost of adding D[next] to the
182 // current lot with the cost of opening the next replenishment.
183 if (next + 1 < demands.Length)
184 {
185 var incrementalCurrentLotCost =
186 MultiplyFinite(
187 holdingCost,
188 MultiplyFinite(
189 coverage,
190 demands[next],
191 "Look-Ahead gate quantity"),
192 "Look-Ahead gate cost");
193
194 if (incrementalCurrentLotCost <= setupCost)
195 {
196 // Figure 4 / Box 2:
197 // current next replenishment at 'next'
198 // versus moving that replenishment one period forward.
199 var currentPatternCost =
200 MultiplyFinite(
201 holdingCost,
202 AddFinite(
203 MultiplyFinite(
204 correctionFactor,
205 demands[next],
206 "Look-Ahead current cost"),
207 MultiplyFinite(
208 1.0 + correctionFactor,
209 demands[next + 1],
210 "Look-Ahead current cost"),
211 "Look-Ahead current cost"),
212 "Look-Ahead current cost");
213
214 var shiftedPatternCost =
215 MultiplyFinite(
216 holdingCost,
217 AddFinite(
218 MultiplyFinite(
219 coverage + correctionFactor,
220 demands[next],
221 "Look-Ahead shifted cost"),
222 MultiplyFinite(
223 correctionFactor,
224 demands[next + 1],
225 "Look-Ahead shifted cost"),
226 "Look-Ahead shifted cost"),
227 "Look-Ahead shifted cost");
228
229 if (shiftedPatternCost < currentPatternCost)
230 {
231 return end + 1;
232 }
233 }
234 }
235
236 // Figure 4 / Box 3:
237 // test whether the last requirement of the tentative lot should
238 // instead be included in the next replenishment.
239 if (end > start)
240 {
241 var currentPatternCost =
242 MultiplyFinite(
243 holdingCost,
244 AddFinite(
245 MultiplyFinite(
246 coverage - 1.0 + correctionFactor,
247 demands[end],
248 "Look-Back current cost"),
249 MultiplyFinite(
250 correctionFactor,
251 demands[next],
252 "Look-Back current cost"),
253 "Look-Back current cost"),
254 "Look-Back current cost");
255
256 var shiftedPatternCost =
257 MultiplyFinite(
258 holdingCost,
259 AddFinite(
260 MultiplyFinite(
261 correctionFactor,
262 demands[end],
263 "Look-Back shifted cost"),
264 MultiplyFinite(
265 1.0 + correctionFactor,
266 demands[next],
267 "Look-Back shifted cost"),
268 "Look-Back shifted cost"),
269 "Look-Back shifted cost");
270
271 if (shiftedPatternCost < currentPatternCost)
272 {
273 return end - 1;
274 }
275 }
276
277 return end;
278 }
279
280 private static bool HasStrictlyPositiveDemand(
281 UlsProblem problem)
282 {
283 var demands = problem.Demands;
284
285 for (var period = 0;
286 period < demands.Length;
287 period++)
288 {
289 if (!(demands[period] > 0.0))
290 {
291 return false;
292 }
293 }
294
295 return true;
296 }
297
298 private static double AddFinite(
299 double left,
300 double right,
301 string operation)
302 {
303 var value = left + right;
304
305 if (!double.IsFinite(value))
306 {
307 throw new ArithmeticException(
308 $"Numerical overflow while computing {operation}.");
309 }
310
311 return value;
312 }
313
314 private static double MultiplyFinite(
315 double left,
316 double right,
317 string operation)
318 {
319 var value = left * right;
320
321 if (!double.IsFinite(value))
322 {
323 throw new ArithmeticException(
324 $"Numerical overflow while computing {operation}.");
325 }
326
327 return value;
328 }
329}
Shared applicability checks for classical stationary-cost lot-sizing heuristics.
static void ThrowIfNotStationary(UlsProblem problem, string solverName)
static int FindNextPositiveDemand(ReadOnlySpan< double > demands, int start)
Builds and validates a zero-backlogging heuristic solution from a set of replenishment cycles.
static UlsSolveResult Build(UlsProblem problem, ReadOnlySpan< int > cycleEnds, string solverName, CancellationToken cancellationToken)
Shared implementation of the PPB variants analyzed by Wemmerlöv (1983).
static UlsSolveResult Solve(UlsProblem problem, string solverName, double correctionFactor, bool useLookAheadLookBack, CancellationToken cancellationToken)
Represents a validated classical uncapacitated lot-sizing problem.
Definition UlsProblem.cs:23
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 the outcome returned by a ULS solution strategy.