ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
FedergruenTzurSolver.cs
Go to the documentation of this file.
1using System.Buffers;
7
9
10/// <summary>
11/// Implements the general forward Federgruen-Tzur dynamic lot-sizing algorithm.
12/// </summary>
13/// <remarks>
14/// <para>
15/// This exact solver maintains Federgruen and Tzur's Minimal Optimal
16/// Predecessor set as a lower envelope of the affine functions
17/// <c>F(i,t) - S(t)</c>. Candidate periods are ranked by
18/// <c>C(i) = p[i] - H(i-1)</c>; adjacent envelope intersections are the
19/// <c>G(k,l)</c> cumulative-demand thresholds of the paper.
20/// </para>
21/// <para>
22/// The ranked predecessor structure is implemented as an array-backed AVL
23/// binary search tree augmented with predecessor/successor links. Each period
24/// enters the structure at most once and a dominated period is deleted at most
25/// once. Insertions/deletions cost <c>O(log n)</c>, so the complete general
26/// algorithm uses <c>O(n log n)</c> time and <c>O(n)</c> memory.
27/// </para>
28/// <para>
29/// Reference:
30/// A. Federgruen and M. Tzur,
31/// "A Simple Forward Algorithm to Solve General Dynamic Lot Sizing Models with
32/// n Periods in O(n log n) or O(n) Time",
33/// Management Science 37(8), 909-925, 1991.
34/// DOI: 10.1287/mnsc.37.8.909.
35/// </para>
36/// <para>
37/// The implementation is an equivalent modern representation of the paper's
38/// Minimal Optimal Predecessor list. The paper explicitly recommends a balanced
39/// binary tree to avoid linear fetch/store work for insertion and deletion.
40/// Here the balanced tree is stored in pooled primitive arrays to avoid
41/// per-candidate managed allocations.
42/// </para>
43/// </remarks>
44public sealed class FedergruenTzurSolver : IUlsSolver
45{
46 private const int CancellationCheckMask = 255;
47
48 /// <inheritdoc />
49 public string Name => "Federgruen-Tzur general O(n log n)";
50
51 /// <inheritdoc />
53
54 /// <inheritdoc />
55 /// <exception cref="ArithmeticException">
56 /// Thrown when cumulative or transformed values cannot be represented as
57 /// finite <see cref="double"/> values.
58 /// </exception>
60 UlsProblem problem,
61 CancellationToken cancellationToken = default)
62 {
63 ArgumentNullException.ThrowIfNull(problem);
64 cancellationToken.ThrowIfCancellationRequested();
65
66 var horizon = problem.Horizon;
67 var valueBuffer = ArrayPool<double>.Shared.Rent(horizon + 1);
68 var predecessorBuffer = ArrayPool<int>.Shared.Rent(horizon + 1);
69
70 try
71 {
72 var value = valueBuffer.AsSpan(0, horizon + 1);
73 var predecessor = predecessorBuffer.AsSpan(0, horizon + 1);
74
75 value[0] = 0.0;
76 predecessor[0] = -1;
77
78 var demands = problem.Demands;
79 var setupCosts = problem.SetupCosts;
80 var productionCosts = problem.UnitProductionCosts;
81 var holdingCosts = problem.HoldingCosts;
82
83 var cumulativeDemand = 0.0;
84 var cumulativeHoldingBefore = 0.0;
85 var firstPeriodHoldingCost = 0.0;
86
87 using var candidates =
88 new FedergruenTzurCandidateTree(horizon);
89
90 for (var period = 0; period < horizon; period++)
91 {
92 if ((period & CancellationCheckMask) == 0)
93 {
94 cancellationToken.ThrowIfCancellationRequested();
95 }
96
97 var previousCumulativeDemand = cumulativeDemand;
98
99 cumulativeDemand = AddFinite(
100 cumulativeDemand,
101 demands[period],
102 "cumulative demand");
103
104 firstPeriodHoldingCost = AddFinite(
105 firstPeriodHoldingCost,
106 MultiplyFinite(
107 demands[period],
108 cumulativeHoldingBefore,
109 "first-period-order holding-cost transform"),
110 "first-period-order holding-cost transform");
111
112 // Federgruen-Tzur notation:
113 // C(i) = c_i - H(i-1).
114 var transformedVariableCost =
115 productionCosts[period] -
116 cumulativeHoldingBefore;
117
118 EnsureFinite(
119 transformedVariableCost,
120 "transformed variable production cost");
121
122 // From equations (1d) and (2) in Federgruen-Tzur:
123 //
124 // F(i,t) = S(t) + B_i + C(i) D(t),
125 //
126 // B_i = F(i-1) + K_i - S(i)
127 // + D(i)H(i-1) - c_i D(i-1).
128 var intercept = value[period];
129
130 intercept = AddFinite(
131 intercept,
132 setupCosts[period],
133 "candidate intercept");
134
135 intercept = AddFinite(
136 intercept,
137 -firstPeriodHoldingCost,
138 "candidate intercept");
139
140 intercept = AddFinite(
141 intercept,
142 MultiplyFinite(
143 cumulativeDemand,
144 cumulativeHoldingBefore,
145 "candidate intercept"),
146 "candidate intercept");
147
148 intercept = AddFinite(
149 intercept,
150 -MultiplyFinite(
151 productionCosts[period],
152 previousCumulativeDemand,
153 "candidate intercept"),
154 "candidate intercept");
155
156 candidates.Add(
157 period,
158 transformedVariableCost,
159 intercept);
160
161 var bestPeriod =
162 candidates.GetBestAndDiscardPast(
163 cumulativeDemand);
164
165 var bestLineValue = AddFinite(
166 candidates.GetIntercept(bestPeriod),
167 MultiplyFinite(
168 candidates.GetSlope(bestPeriod),
169 cumulativeDemand,
170 "candidate line evaluation"),
171 "candidate line evaluation");
172
173 var orderValue = AddFinite(
174 firstPeriodHoldingCost,
175 bestLineValue,
176 "forward dynamic-programming value");
177
178 // If the current period has zero demand, the horizon can be
179 // extended without an order. Represent this zero-length arc by
180 // predecessor=period so the shared ZIO reconstruction remains
181 // valid without creating a zero-quantity setup.
182 if (demands[period] == 0.0 &&
183 value[period] <= orderValue)
184 {
185 value[period + 1] = value[period];
186 predecessor[period + 1] = period;
187 }
188 else
189 {
190 value[period + 1] = orderValue;
191 predecessor[period + 1] = bestPeriod;
192 }
193
194 cumulativeHoldingBefore = AddFinite(
195 cumulativeHoldingBefore,
196 holdingCosts[period],
197 "cumulative holding cost");
198 }
199
200 cancellationToken.ThrowIfCancellationRequested();
201
203 problem,
204 predecessor,
205 Name,
206 cancellationToken);
207 }
208 finally
209 {
210 ArrayPool<double>.Shared.Return(
211 valueBuffer,
212 clearArray: false);
213
214 ArrayPool<int>.Shared.Return(
215 predecessorBuffer,
216 clearArray: false);
217 }
218 }
219
220 private static double AddFinite(
221 double left,
222 double right,
223 string operation)
224 {
225 var value = left + right;
226 EnsureFinite(value, operation);
227 return value;
228 }
229
230 private static double MultiplyFinite(
231 double left,
232 double right,
233 string operation)
234 {
235 var value = left * right;
236 EnsureFinite(value, operation);
237 return value;
238 }
239
240 private static void EnsureFinite(
241 double value,
242 string operation)
243 {
244 if (!double.IsFinite(value))
245 {
246 throw new ArithmeticException(
247 $"Numerical overflow while computing {operation}.");
248 }
249 }
250}
Implements the general forward Federgruen-Tzur dynamic lot-sizing algorithm.
UlsSolverKind Kind
Gets the broad family of the solver.
UlsSolveResult Solve(UlsProblem problem, CancellationToken cancellationToken=default)
string Name
Gets the stable human-readable name of the solver.
Array-backed balanced candidate tree for the Federgruen-Tzur forward algorithm.
Reconstructs a zero-inventory-order ULS solution from shortest-path predecessors.
static UlsSolveResult Build(UlsProblem problem, ReadOnlySpan< int > predecessor, string solverName, CancellationToken cancellationToken)
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.