ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
SadjadiAryanezhadSadeghiSolver.cs
Go to the documentation of this file.
1using System.Buffers;
6
8
9/// <summary>
10/// Implements the fixed-cost improved Wagner-Whitin method of
11/// Sadjadi, Aryanezhad and Sadeghi.
12/// </summary>
13/// <remarks>
14/// <para>
15/// The 2009 paper keeps the forward Wagner-Whitin recursion but avoids branches
16/// once the cumulative future demand exceeds the Derived/Economic Part-Period
17/// threshold <c>DPP = A / H</c>. It also uses the Planning Horizon Theorem.
18/// </para>
19/// <para>
20/// This class is deliberately public and separate from
21/// <see cref="HeadyZhuEconomicPartPeriodSolver"/> so that the 2009 publication
22/// can be reproduced and benchmarked as its own method.
23/// </para>
24/// <para>
25/// Applicability: constant setup cost, constant unit production cost, and
26/// constant relevant unit holding cost. Worst-case time is <c>O(T^2)</c>,
27/// auxiliary memory is <c>O(T)</c>, while the number of evaluated branches is
28/// data dependent.
29/// </para>
30/// <para>
31/// Reference:
32/// S. J. Sadjadi, M. B. Gh. Aryanezhad and H. A. Sadeghi,
33/// "An Improved WAGNER-WHITIN Algorithm",
34/// International Journal of Industrial Engineering &amp; Production Research
35/// 20(3), 117-123, 2009.
36/// </para>
37/// </remarks>
39{
40 private const int CancellationCheckMask = 255;
41
42 /// <inheritdoc />
43 public string Name =>
44 "Sadjadi-Aryanezhad-Sadeghi improved Wagner-Whitin";
45
46 /// <inheritdoc />
48
49 /// <summary>
50 /// Determines whether the fixed-cost assumptions of the published first
51 /// model are satisfied.
52 /// </summary>
53 public static bool IsApplicable(UlsProblem problem)
54 {
55 ArgumentNullException.ThrowIfNull(problem);
56
57 var horizon = problem.Horizon;
58 var setupCosts = problem.SetupCosts;
59 var productionCosts = problem.UnitProductionCosts;
60 var holdingCosts = problem.HoldingCosts;
61
62 var setupCost = setupCosts[0];
63 var productionCost = productionCosts[0];
64
65 for (var period = 1; period < horizon; period++)
66 {
67 if (setupCosts[period] != setupCost ||
68 productionCosts[period] != productionCost)
69 {
70 return false;
71 }
72 }
73
74 if (horizon > 1)
75 {
76 var holdingCost = holdingCosts[0];
77
78 for (var period = 1; period < horizon - 1; period++)
79 {
80 if (holdingCosts[period] != holdingCost)
81 {
82 return false;
83 }
84 }
85 }
86
87 return true;
88 }
89
90 /// <summary>
91 /// Gets the published <c>DPP = A/H</c> threshold.
92 /// </summary>
93 public static double GetDerivedPartPeriodThreshold(UlsProblem problem)
94 {
95 ArgumentNullException.ThrowIfNull(problem);
96
97 if (!IsApplicable(problem))
98 {
99 throw new NotSupportedException(
100 "The Sadjadi DPP threshold requires constant setup, " +
101 "production and relevant holding costs.");
102 }
103
104 var holdingCost =
105 problem.Horizon > 1
106 ? problem.HoldingCosts[0]
107 : 0.0;
108
109 return holdingCost == 0.0
110 ? double.PositiveInfinity
111 : problem.SetupCosts[0] / holdingCost;
112 }
113
114 /// <inheritdoc />
116 UlsProblem problem,
117 CancellationToken cancellationToken = default)
118 {
119 ArgumentNullException.ThrowIfNull(problem);
120 cancellationToken.ThrowIfCancellationRequested();
121
122 if (!IsApplicable(problem))
123 {
124 throw new NotSupportedException(
125 "SadjadiAryanezhadSadeghiSolver requires constant setup, " +
126 "unit production and relevant holding costs.");
127 }
128
129 var horizon = problem.Horizon;
130 var valueBuffer = ArrayPool<double>.Shared.Rent(horizon + 1);
131 var predecessorBuffer = ArrayPool<int>.Shared.Rent(horizon + 1);
132
133 try
134 {
135 var value = valueBuffer.AsSpan(0, horizon + 1);
136 var predecessor = predecessorBuffer.AsSpan(0, horizon + 1);
137
138 value.Fill(double.PositiveInfinity);
139 predecessor.Fill(-1);
140 value[0] = 0.0;
141
142 var demands = problem.Demands;
143 var setupCost = problem.SetupCosts[0];
144 var productionCost = problem.UnitProductionCosts[0];
145
146 var holdingCost =
147 horizon > 1
148 ? problem.HoldingCosts[0]
149 : 0.0;
150
151 var planningHorizonStart = 0;
152 var cancellationCounter = 0;
153
154 for (var end = 0; end < horizon; end++)
155 {
156 if ((cancellationCounter++ & CancellationCheckMask) == 0)
157 {
158 cancellationToken.ThrowIfCancellationRequested();
159 }
160
161 if (demands[end] == 0.0)
162 {
163 value[end + 1] = value[end];
164 predecessor[end + 1] = end;
165 continue;
166 }
167
168 var best = double.PositiveInfinity;
169 var bestStart = -1;
170 var futureDemand = 0.0;
171 var intervalHoldingCost = 0.0;
172
173 for (var start = end;
174 start >= planningHorizonStart;
175 start--)
176 {
177 if ((cancellationCounter++ & CancellationCheckMask) == 0)
178 {
179 cancellationToken.ThrowIfCancellationRequested();
180 }
181
182 if (start < end)
183 {
184 var incrementalHoldingCost =
185 MultiplyFinite(
186 holdingCost,
187 futureDemand,
188 "DPP incremental holding cost");
189
190 // This is the paper's DPP = A/H branch-elimination
191 // criterion written without division.
192 if (incrementalHoldingCost > setupCost)
193 {
194 break;
195 }
196
197 intervalHoldingCost = AddFinite(
198 intervalHoldingCost,
199 incrementalHoldingCost,
200 "candidate holding cost");
201 }
202
203 futureDemand = AddFinite(
204 futureDemand,
205 demands[start],
206 "candidate demand");
207
208 var candidate = AddFinite(
209 value[start],
210 setupCost,
211 "candidate cost");
212
213 candidate = AddFinite(
214 candidate,
215 MultiplyFinite(
216 productionCost,
217 futureDemand,
218 "candidate production cost"),
219 "candidate cost");
220
221 candidate = AddFinite(
222 candidate,
223 intervalHoldingCost,
224 "candidate cost");
225
226 // Backward scan + strict comparison retains the latest
227 // optimal predecessor, strengthening the planning horizon.
228 if (candidate < best)
229 {
230 best = candidate;
231 bestStart = start;
232 }
233 }
234
235 if (!double.IsFinite(best) ||
236 bestStart < planningHorizonStart)
237 {
238 throw new ArithmeticException(
239 $"No finite Sadjadi value was obtained for period {end}.");
240 }
241
242 value[end + 1] = best;
243 predecessor[end + 1] = bestStart;
244 planningHorizonStart = bestStart;
245 }
246
248 problem,
249 predecessor,
250 Name,
251 cancellationToken);
252 }
253 finally
254 {
255 ArrayPool<double>.Shared.Return(valueBuffer, clearArray: false);
256 ArrayPool<int>.Shared.Return(predecessorBuffer, clearArray: false);
257 }
258 }
259
260 private static double AddFinite(
261 double left,
262 double right,
263 string operation)
264 {
265 var value = left + right;
266
267 if (!double.IsFinite(value))
268 {
269 throw new ArithmeticException(
270 $"Numerical overflow while computing {operation}.");
271 }
272
273 return value;
274 }
275
276 private static double MultiplyFinite(
277 double left,
278 double right,
279 string operation)
280 {
281 var value = left * right;
282
283 if (!double.IsFinite(value))
284 {
285 throw new ArithmeticException(
286 $"Numerical overflow while computing {operation}.");
287 }
288
289 return value;
290 }
291}
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 fixed-cost improved Wagner-Whitin method of Sadjadi, Aryanezhad and Sadeghi.
static bool IsApplicable(UlsProblem problem)
Determines whether the fixed-cost assumptions of the published first model are satisfied.
static double GetDerivedPartPeriodThreshold(UlsProblem problem)
Gets the published DPP = A/H threshold.
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.