59 private const int CancellationCheckMask = 255;
62 public string Name =>
"Wagelmans general O(n log n)";
75 CancellationToken cancellationToken =
default)
77 ArgumentNullException.ThrowIfNull(problem);
78 cancellationToken.ThrowIfCancellationRequested();
82 var prefixBuffer = ArrayPool<double>.Shared.Rent(horizon + 1);
83 var transformedCostBuffer = ArrayPool<double>.Shared.Rent(horizon);
84 var valueBuffer = ArrayPool<double>.Shared.Rent(horizon + 1);
85 var successorBuffer = ArrayPool<int>.Shared.Rent(horizon);
86 var hullBuffer = ArrayPool<HullLine>.Shared.Rent(horizon + 1);
90 var prefixDemand = prefixBuffer.AsSpan(0, horizon + 1);
91 var transformedCost = transformedCostBuffer.AsSpan(0, horizon);
92 var value = valueBuffer.AsSpan(0, horizon + 1);
93 var successor = successorBuffer.AsSpan(0, horizon);
94 var hull = hullBuffer.AsSpan(0, horizon + 1);
96 BuildPrefixDemand(problem.
Demands, prefixDemand);
97 BuildTransformedProductionCosts(
102 value[horizon] = 0.0;
106 hull[0] =
new HullLine(
107 slope: prefixDemand[horizon],
109 startX:
double.NegativeInfinity,
110 successorPeriod: horizon);
115 for (var period = horizon - 1; period >= 0; period--)
117 if ((period & CancellationCheckMask) == 0)
119 cancellationToken.ThrowIfCancellationRequested();
122 var queryX = transformedCost[period];
123 var bestLine = Query(hull, hullCount, queryX);
126 prefixDemand[bestLine.SuccessorPeriod] -
127 prefixDemand[period];
129 var setupValue = AddFinite(
134 "transformed variable production cost"),
135 "setup plus transformed variable production cost");
137 setupValue = AddFinite(
140 "backward dynamic-programming value");
142 if (demands[period] == 0.0 &&
143 value[period + 1] <= setupValue)
145 value[period] = value[period + 1];
146 successor[period] = -1;
150 value[period] = setupValue;
151 successor[period] = bestLine.SuccessorPeriod;
158 slope: prefixDemand[period],
159 intercept: value[period],
160 startX:
double.NegativeInfinity,
161 successorPeriod: period));
164 cancellationToken.ThrowIfCancellationRequested();
174 ArrayPool<double>.Shared.Return(prefixBuffer, clearArray:
false);
175 ArrayPool<double>.Shared.Return(transformedCostBuffer, clearArray:
false);
176 ArrayPool<double>.Shared.Return(valueBuffer, clearArray:
false);
177 ArrayPool<int>.Shared.Return(successorBuffer, clearArray:
false);
178 ArrayPool<HullLine>.Shared.Return(hullBuffer, clearArray:
false);
182 private static void BuildPrefixDemand(
183 ReadOnlySpan<double> demands,
184 Span<double> prefixDemand)
186 prefixDemand[0] = 0.0;
188 for (var period = 0; period < demands.Length; period++)
190 prefixDemand[period + 1] = AddFinite(
191 prefixDemand[period],
193 "cumulative demand");
197 private static void BuildTransformedProductionCosts(
198 ReadOnlySpan<double> productionCosts,
199 ReadOnlySpan<double> holdingCosts,
200 Span<double> transformedCosts)
202 var relevantHoldingSuffix = 0.0;
204 for (var period = productionCosts.Length - 1;
208 if (period < productionCosts.Length - 1)
210 relevantHoldingSuffix = AddFinite(
211 relevantHoldingSuffix,
212 holdingCosts[period],
213 "holding-cost suffix");
216 transformedCosts[period] = AddFinite(
217 productionCosts[period],
218 relevantHoldingSuffix,
219 "transformed production cost");
223 private static HullLine Query(
224 ReadOnlySpan<HullLine> hull,
229 var high = hullCount - 1;
233 var middle = low + ((high - low + 1) >> 1);
235 if (hull[middle].StartX <= x)
248 private static int AddLine(
253 while (hullCount > 0)
255 var last = hull[hullCount - 1];
257 if (newLine.Slope == last.Slope)
259 if (newLine.Intercept >= last.Intercept)
268 if (last.Slope <= newLine.Slope)
270 throw new InvalidOperationException(
271 "WagelmansGeneralSolver received nonmonotone cumulative-demand slopes.");
274 var startX = IntersectionX(last, newLine);
276 if (hullCount == 1 || startX > last.StartX)
286 ? double.NegativeInfinity
287 : IntersectionX(hull[hullCount - 1], newLine);
289 hull[hullCount] =
new HullLine(
293 newLine.SuccessorPeriod);
295 return hullCount + 1;
298 private static double IntersectionX(
302 var denominator = left.Slope - right.Slope;
304 if (!(denominator > 0.0) || !
double.IsFinite(denominator))
306 throw new ArithmeticException(
307 "A Wagelmans convex-envelope intersection has an invalid denominator.");
310 var numerator = right.Intercept - left.Intercept;
311 var intersection = numerator / denominator;
313 if (!
double.IsFinite(intersection))
315 throw new ArithmeticException(
316 "A Wagelmans convex-envelope intersection is not finite.");
322 private static UlsSolveResult BuildResult(
324 ReadOnlySpan<double> prefixDemand,
325 ReadOnlySpan<int> successor,
326 CancellationToken cancellationToken)
329 var production =
new double[horizon];
330 var inventory =
new double[horizon];
331 var setup =
new bool[horizon];
335 while (period < horizon)
337 if ((period & CancellationCheckMask) == 0)
339 cancellationToken.ThrowIfCancellationRequested();
342 var next = successor[period];
350 if (next <= period || next > horizon)
352 throw new InvalidOperationException(
353 "The Wagelmans successor chain is inconsistent.");
357 prefixDemand[next] - prefixDemand[period];
359 setup[period] = production[period] > 0.0;
364 var productionCost = 0.0;
365 var holdingCost = 0.0;
366 var runningInventory = 0.0;
373 for (period = 0; period < horizon; period++)
375 if ((period & CancellationCheckMask) == 0)
377 cancellationToken.ThrowIfCancellationRequested();
382 setupCost = AddFinite(
385 "solution setup cost");
388 productionCost = AddFinite(
391 productionCosts[period],
393 "solution production cost"),
394 "solution production cost");
396 runningInventory = AddFinite(
399 "inventory balance");
401 runningInventory -= demands[period];
403 var tolerance = 1e-10 * Math.Max(
405 Math.Max(Math.Abs(runningInventory), Math.Abs(demands[period])));
407 if (runningInventory < -tolerance)
409 throw new InvalidOperationException(
410 $
"Reconstructed solution has negative inventory at period {period}.");
413 if (runningInventory < 0.0)
415 runningInventory = 0.0;
418 inventory[period] = runningInventory;
420 holdingCost = AddFinite(
423 holdingCosts[period],
425 "solution holding cost"),
426 "solution holding cost");
429 var solution = UlsSolution.FromOwnedBuffers(
437 return new UlsSolveResult(
438 "Wagelmans general O(n log n)",
443 private static double AddFinite(
448 var value = left + right;
450 if (!
double.IsFinite(value))
452 throw new ArithmeticException(
453 $
"Numerical overflow while computing {operation}.");
459 private static double MultiplyFinite(
464 var value = left * right;
466 if (!
double.IsFinite(value))
468 throw new ArithmeticException(
469 $
"Numerical overflow while computing {operation}.");
475 private readonly
struct HullLine
484 Intercept = intercept;
486 SuccessorPeriod = successorPeriod;
489 public double Slope {
get; }
491 public double Intercept {
get; }
493 public double StartX {
get; }
495 public int SuccessorPeriod {
get; }