ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
FedergruenTzurLinearCandidateDeque.cs
Go to the documentation of this file.
1using System.Buffers;
2
4
5/// <summary>
6/// Array-backed monotone candidate deque used by the two linear-time
7/// Federgruen-Tzur specializations.
8/// </summary>
9/// <remarks>
10/// Candidate slopes are stored in strictly decreasing order. Envelope
11/// activation thresholds are therefore maintained by deleting only from the
12/// front or the back, exactly as in Sections 3 and 4 of Federgruen and Tzur
13/// (1991).
14/// </remarks>
15internal sealed class FedergruenTzurLinearCandidateDeque : IDisposable
16{
17 private readonly double[] _slope;
18 private readonly double[] _intercept;
19 private readonly double[] _startX;
20 private readonly int[] _period;
21
22 private int _head;
23 private int _tail = -1;
24 private bool _disposed;
25
27 {
28 if (capacity <= 0)
29 {
30 throw new ArgumentOutOfRangeException(
31 nameof(capacity),
32 capacity,
33 "Candidate-deque capacity must be positive.");
34 }
35
36 _slope = ArrayPool<double>.Shared.Rent(capacity);
37 _intercept = ArrayPool<double>.Shared.Rent(capacity);
38 _startX = ArrayPool<double>.Shared.Rent(capacity);
39 _period = ArrayPool<int>.Shared.Rent(capacity);
40 }
41
42 public bool IsEmpty
43 {
44 get
45 {
46 ThrowIfDisposed();
47 return _tail < _head;
48 }
49 }
50
51 public double LastSlope
52 {
53 get
54 {
55 ThrowIfDisposed();
56
57 if (IsEmpty)
58 {
59 throw new InvalidOperationException(
60 "The candidate deque is empty.");
61 }
62
63 return _slope[_tail];
64 }
65 }
66
67 /// <summary>
68 /// Adds a candidate whose slope must not exceed the slope of the current
69 /// last candidate. Equal-slope candidates are reduced to the globally
70 /// dominating intercept.
71 /// </summary>
72 /// <returns>
73 /// <see langword="true"/> when the candidate remains on the lower envelope.
74 /// </returns>
75 public bool AddMonotone(
76 int period,
77 double slope,
78 double intercept)
79 {
80 ThrowIfDisposed();
81
82 if (!double.IsFinite(slope) ||
83 !double.IsFinite(intercept))
84 {
85 throw new ArithmeticException(
86 "Federgruen-Tzur candidate coefficients must be finite.");
87 }
88
89 while (!IsEmpty)
90 {
91 var lastSlope = _slope[_tail];
92
93 if (slope > lastSlope)
94 {
95 throw new InvalidOperationException(
96 "A linear Federgruen-Tzur specialization received " +
97 "nonmonotone candidate slopes.");
98 }
99
100 if (slope < lastSlope)
101 {
102 break;
103 }
104
105 if (_intercept[_tail] <= intercept)
106 {
107 return false;
108 }
109
110 _tail--;
111 }
112
113 var activationX = double.NegativeInfinity;
114
115 while (!IsEmpty)
116 {
117 activationX = IntersectionX(
118 _slope[_tail],
119 _intercept[_tail],
120 slope,
121 intercept);
122
123 if (_tail == _head ||
124 activationX > _startX[_tail])
125 {
126 break;
127 }
128
129 _tail--;
130 }
131
132 if (IsEmpty)
133 {
134 activationX = double.NegativeInfinity;
135 }
136 else
137 {
138 activationX = IntersectionX(
139 _slope[_tail],
140 _intercept[_tail],
141 slope,
142 intercept);
143 }
144
145 _tail++;
146
147 _period[_tail] = period;
148 _slope[_tail] = slope;
149 _intercept[_tail] = intercept;
150 _startX[_tail] = activationX;
151
152 return true;
153 }
154
155 /// <summary>
156 /// Returns the active candidate at the supplied cumulative demand and
157 /// permanently discards candidate intervals that lie strictly in the past.
158 /// </summary>
159 public int GetBestAndDiscardPast(double cumulativeDemand)
160 {
161 ThrowIfDisposed();
162
163 if (!double.IsFinite(cumulativeDemand))
164 {
165 throw new ArithmeticException(
166 "Cumulative demand must be finite.");
167 }
168
169 if (IsEmpty)
170 {
171 throw new InvalidOperationException(
172 "The candidate deque is empty.");
173 }
174
175 while (_head < _tail &&
176 _startX[_head + 1] < cumulativeDemand)
177 {
178 _head++;
179 }
180
181 return _period[_head];
182 }
183
184 public double BestSlope
185 {
186 get
187 {
188 ThrowIfDisposed();
189
190 if (IsEmpty)
191 {
192 throw new InvalidOperationException(
193 "The candidate deque is empty.");
194 }
195
196 return _slope[_head];
197 }
198 }
199
200 public double BestIntercept
201 {
202 get
203 {
204 ThrowIfDisposed();
205
206 if (IsEmpty)
207 {
208 throw new InvalidOperationException(
209 "The candidate deque is empty.");
210 }
211
212 return _intercept[_head];
213 }
214 }
215
216 public void Dispose()
217 {
218 if (_disposed)
219 {
220 return;
221 }
222
223 _disposed = true;
224
225 ArrayPool<double>.Shared.Return(_slope, clearArray: false);
226 ArrayPool<double>.Shared.Return(_intercept, clearArray: false);
227 ArrayPool<double>.Shared.Return(_startX, clearArray: false);
228 ArrayPool<int>.Shared.Return(_period, clearArray: false);
229 }
230
231 private static double IntersectionX(
232 double higherSlope,
233 double higherIntercept,
234 double lowerSlope,
235 double lowerIntercept)
236 {
237 var denominator = higherSlope - lowerSlope;
238
239 if (!(denominator > 0.0) ||
240 !double.IsFinite(denominator))
241 {
242 throw new ArithmeticException(
243 "A linear Federgruen-Tzur envelope intersection has " +
244 "an invalid denominator.");
245 }
246
247 var numerator = lowerIntercept - higherIntercept;
248 var intersection = numerator / denominator;
249
250 if (!double.IsFinite(intersection))
251 {
252 throw new ArithmeticException(
253 "A linear Federgruen-Tzur envelope intersection is not finite.");
254 }
255
256 return intersection;
257 }
258
259 private void ThrowIfDisposed()
260 {
261 ObjectDisposedException.ThrowIf(_disposed, this);
262 }
263}
int GetBestAndDiscardPast(double cumulativeDemand)
Returns the active candidate at the supplied cumulative demand and permanently discards candidate int...
bool AddMonotone(int period, double slope, double intercept)
Adds a candidate whose slope must not exceed the slope of the current last candidate....