ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
WagnerWhitinEvansSolver.cs
Go to the documentation of this file.
1using System.Buffers;
6
8
9/// <summary>
10/// Implements the low-storage Wagner-Whitin dynamic program described by Evans.
11/// </summary>
12/// <remarks>
13/// <para>
14/// Evans observed that the classical Wagner-Whitin problem is a shortest-path
15/// computation on an acyclic network and that regeneration-interval costs do not
16/// need to be stored in a complete matrix. This implementation updates candidate
17/// interval costs incrementally as the planning horizon is extended.
18/// </para>
19/// <para>
20/// Time complexity: <c>O(n^2)</c>.
21/// Auxiliary working memory: <c>O(n)</c>.
22/// </para>
23/// <para>
24/// Reference:
25/// J. R. Evans,
26/// "An Efficient Implementation of the Wagner-Whitin Algorithm for Dynamic
27/// Lot-Sizing", Journal of Operations Management 5(2), 229-235, 1985.
28/// DOI: 10.1016/0272-6963(85)90009-9.
29/// </para>
30/// <para>
31/// The code is an equivalent modern C# implementation of Evans' low-core-storage
32/// recurrence, not a transliteration of the original FORTRAN listing.
33/// </para>
34/// </remarks>
36{
37 /// <inheritdoc />
38 public string Name => "Wagner-Whitin Evans 1985";
39
40 /// <inheritdoc />
42
43 /// <inheritdoc />
45 UlsProblem problem,
46 CancellationToken cancellationToken = default)
47 {
48 ArgumentNullException.ThrowIfNull(problem);
49 cancellationToken.ThrowIfCancellationRequested();
50
51 var horizon = problem.Horizon;
52
53 var valueBuffer = ArrayPool<double>.Shared.Rent(horizon + 1);
54 var predecessorBuffer = ArrayPool<int>.Shared.Rent(horizon + 1);
55 var intervalCostBuffer = ArrayPool<double>.Shared.Rent(horizon);
56 var deliveredCostBuffer = ArrayPool<double>.Shared.Rent(horizon);
57 var cumulativeDemandBuffer = ArrayPool<double>.Shared.Rent(horizon);
58
59 try
60 {
61 var value = valueBuffer.AsSpan(0, horizon + 1);
62 var predecessor = predecessorBuffer.AsSpan(0, horizon + 1);
63 var intervalCost = intervalCostBuffer.AsSpan(0, horizon);
64 var deliveredCost = deliveredCostBuffer.AsSpan(0, horizon);
65 var cumulativeDemand = cumulativeDemandBuffer.AsSpan(0, horizon);
66
67 value.Fill(double.PositiveInfinity);
68 predecessor.Fill(-1);
69 intervalCost.Clear();
70 deliveredCost.Clear();
71 cumulativeDemand.Clear();
72
73 value[0] = 0.0;
74
75 var demands = problem.Demands;
76 var setupCosts = problem.SetupCosts;
77 var productionCosts = problem.UnitProductionCosts;
78 var holdingCosts = problem.HoldingCosts;
79
80 for (var end = 0; end < horizon; end++)
81 {
82 cancellationToken.ThrowIfCancellationRequested();
83
84 deliveredCost[end] = productionCosts[end];
85 intervalCost[end] = 0.0;
86 cumulativeDemand[end] = 0.0;
87
88 var best = double.PositiveInfinity;
89 var bestStart = -1;
90
91 for (var start = 0; start <= end; start++)
92 {
93 cumulativeDemand[start] += demands[end];
94 intervalCost[start] += demands[end] * deliveredCost[start];
95
96 if (!double.IsFinite(intervalCost[start]))
97 {
98 throw new ArithmeticException(
99 "Numerical overflow while updating an Evans regeneration interval.");
100 }
101
102 var regenerationCost =
103 cumulativeDemand[start] > 0.0
104 ? setupCosts[start] + intervalCost[start]
105 : 0.0;
106
107 var candidate = value[start] + regenerationCost;
108
109 if (candidate < best)
110 {
111 best = candidate;
112 bestStart = start;
113 }
114 }
115
116 if (!double.IsFinite(best) || bestStart < 0)
117 {
118 throw new ArithmeticException(
119 $"No finite Evans dynamic-programming value was obtained for period {end}.");
120 }
121
122 value[end + 1] = best;
123 predecessor[end + 1] = bestStart;
124
125 if (end < horizon - 1)
126 {
127 for (var start = 0; start <= end; start++)
128 {
129 deliveredCost[start] += holdingCosts[end];
130
131 if (!double.IsFinite(deliveredCost[start]))
132 {
133 throw new ArithmeticException(
134 "Numerical overflow while updating Evans delivered unit costs.");
135 }
136 }
137 }
138 }
139
141 problem,
142 predecessor,
143 Name,
144 cancellationToken);
145 }
146 finally
147 {
148 ArrayPool<double>.Shared.Return(valueBuffer, clearArray: false);
149 ArrayPool<int>.Shared.Return(predecessorBuffer, clearArray: false);
150 ArrayPool<double>.Shared.Return(intervalCostBuffer, clearArray: false);
151 ArrayPool<double>.Shared.Return(deliveredCostBuffer, clearArray: false);
152 ArrayPool<double>.Shared.Return(cumulativeDemandBuffer, clearArray: false);
153 }
154 }
155}
Reconstructs a zero-inventory-order ULS solution from shortest-path predecessors.
static UlsSolveResult Build(UlsProblem problem, ReadOnlySpan< int > predecessor, string solverName, CancellationToken cancellationToken)
Implements the low-storage Wagner-Whitin dynamic program described by Evans.
UlsSolverKind Kind
Gets the broad family of the solver.
string Name
Gets the stable human-readable name of the solver.
UlsSolveResult Solve(UlsProblem problem, CancellationToken cancellationToken=default)
Solves an uncapacitated lot-sizing problem.The solve result.
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 the outcome returned by a ULS solution strategy.
Defines the common strategy contract implemented by every ULS solver.
Definition IUlsSolver.cs:14
UlsSolverKind
Identifies the broad family of a ULS solution strategy.