ULSAlgorithms 1.1.0-g3e5595996d
High-performance exact and heuristic algorithms for uncapacitated lot sizing
Loading...
Searching...
No Matches
AggarwalParkMatrixSearch.cs
Go to the documentation of this file.
2
3/// <summary>
4/// SMAWK row-minimum search for the implicit Monge matrices generated by the
5/// Aggarwal-Park divide-and-conquer lot-sizing recursion.
6/// </summary>
7/// <remarks>
8/// <para>
9/// Rows are cumulative-demand query points in nondecreasing order and columns
10/// are predecessor lines in nonincreasing transformed marginal-cost order.
11/// The implicit entry is
12/// <c>intercept[column] + slope[column] * prefixDemand[row]</c>.
13/// This matrix is Monge, so all row minima are obtained in linear time in the
14/// number of rows plus columns.
15/// </para>
16/// <para>
17/// The implementation stores only reduced column indices. Row subsets created
18/// by the SMAWK recursion are represented arithmetically by
19/// <c>(rowStart, rowStep, rowCount)</c>, avoiding temporary row arrays.
20/// </para>
21/// </remarks>
22internal static class AggarwalParkMatrixSearch
23{
24 public static void FindRowMinima(
25 int rowStart,
26 int rowCount,
27 ReadOnlySpan<int> columns,
28 ReadOnlySpan<double> prefixDemand,
29 ReadOnlySpan<double> slope,
30 ReadOnlySpan<double> intercept,
31 Span<int> argMin,
32 Span<int> workspace)
33 {
34 if (rowCount <= 0)
35 {
36 return;
37 }
38
39 if (columns.IsEmpty)
40 {
41 throw new ArgumentException(
42 "At least one predecessor column is required.",
43 nameof(columns));
44 }
45
46 var requiredWorkspace = checked((2 * rowCount) + 2);
47
48 if (workspace.Length < requiredWorkspace)
49 {
50 throw new ArgumentException(
51 $"SMAWK workspace must contain at least {requiredWorkspace} entries.",
52 nameof(workspace));
53 }
54
55 Search(
56 rowStart,
57 rowStep: 1,
58 rowCount,
59 columns,
60 prefixDemand,
61 slope,
62 intercept,
63 argMin,
64 workspace);
65 }
66
67 private static void Search(
68 int rowStart,
69 int rowStep,
70 int rowCount,
71 ReadOnlySpan<int> columns,
72 ReadOnlySpan<double> prefixDemand,
73 ReadOnlySpan<double> slope,
74 ReadOnlySpan<double> intercept,
75 Span<int> argMin,
76 Span<int> workspace)
77 {
78 if (rowCount == 0)
79 {
80 return;
81 }
82
83 var reducedCount = 0;
84
85 for (var columnPosition = 0;
86 columnPosition < columns.Length;
87 columnPosition++)
88 {
89 var column = columns[columnPosition];
90
91 while (reducedCount > 0)
92 {
93 var comparisonRowPosition = reducedCount - 1;
94
95 if (comparisonRowPosition >= rowCount)
96 {
97 break;
98 }
99
100 var row =
101 rowStart +
102 (comparisonRowPosition * rowStep);
103
104 var oldColumn =
105 workspace[reducedCount - 1];
106
107 if (!IsStrictlyBetter(
108 row,
109 column,
110 oldColumn,
111 prefixDemand,
112 slope,
113 intercept))
114 {
115 break;
116 }
117
118 reducedCount--;
119 }
120
121 if (reducedCount < rowCount)
122 {
123 workspace[reducedCount] = column;
124 reducedCount++;
125 }
126 }
127
128 var reducedColumns =
129 workspace[..reducedCount];
130
131 var oddRowCount = rowCount / 2;
132
133 if (oddRowCount > 0)
134 {
135 Search(
136 rowStart + rowStep,
137 checked(rowStep * 2),
138 oddRowCount,
139 reducedColumns,
140 prefixDemand,
141 slope,
142 intercept,
143 argMin,
144 workspace[reducedCount..]);
145 }
146
147 var lowerPosition = 0;
148
149 for (var rowPosition = 0;
150 rowPosition < rowCount;
151 rowPosition += 2)
152 {
153 var row =
154 rowStart +
155 (rowPosition * rowStep);
156
157 if (rowPosition > 0)
158 {
159 var lowerColumn =
160 argMin[row - rowStep];
161
162 while (lowerPosition < reducedCount &&
163 reducedColumns[lowerPosition] != lowerColumn)
164 {
165 lowerPosition++;
166 }
167
168 if (lowerPosition >= reducedCount)
169 {
170 throw new InvalidOperationException(
171 "SMAWK lower-bound column was not found.");
172 }
173 }
174
175 var upperPosition = reducedCount - 1;
176
177 if (rowPosition + 1 < rowCount)
178 {
179 var upperRow = row + rowStep;
180 var upperColumn = argMin[upperRow];
181
182 upperPosition = lowerPosition;
183
184 while (upperPosition < reducedCount &&
185 reducedColumns[upperPosition] != upperColumn)
186 {
187 upperPosition++;
188 }
189
190 if (upperPosition >= reducedCount)
191 {
192 throw new InvalidOperationException(
193 "SMAWK upper-bound column was not found.");
194 }
195 }
196
197 var bestPosition = lowerPosition;
198
199 for (var position = lowerPosition + 1;
200 position <= upperPosition;
201 position++)
202 {
203 if (IsStrictlyBetter(
204 row,
205 reducedColumns[position],
206 reducedColumns[bestPosition],
207 prefixDemand,
208 slope,
209 intercept))
210 {
211 bestPosition = position;
212 }
213 }
214
215 argMin[row] = reducedColumns[bestPosition];
216
217 if (rowPosition + 1 < rowCount)
218 {
219 lowerPosition = upperPosition;
220 }
221 }
222 }
223
224 private static bool IsStrictlyBetter(
225 int row,
226 int candidateColumn,
227 int currentColumn,
228 ReadOnlySpan<double> prefixDemand,
229 ReadOnlySpan<double> slope,
230 ReadOnlySpan<double> intercept)
231 {
232 var candidate = Evaluate(
233 row,
234 candidateColumn,
235 prefixDemand,
236 slope,
237 intercept);
238
239 var current = Evaluate(
240 row,
241 currentColumn,
242 prefixDemand,
243 slope,
244 intercept);
245
246 // Strict comparison keeps the left-most column when values tie, which
247 // is the tie convention required by monotone matrix searching.
248 return candidate < current;
249 }
250
251 public static double Evaluate(
252 int row,
253 int column,
254 ReadOnlySpan<double> prefixDemand,
255 ReadOnlySpan<double> slope,
256 ReadOnlySpan<double> intercept)
257 {
258 var product =
259 slope[column] *
260 prefixDemand[row];
261
262 var value =
263 intercept[column] +
264 product;
265
266 if (!double.IsFinite(product) ||
267 !double.IsFinite(value))
268 {
269 throw new ArithmeticException(
270 "Numerical overflow while evaluating an Aggarwal-Park Monge-matrix entry.");
271 }
272
273 return value;
274 }
275}
SMAWK row-minimum search for the implicit Monge matrices generated by the Aggarwal-Park divide-and-co...
static void FindRowMinima(int rowStart, int rowCount, ReadOnlySpan< int > columns, ReadOnlySpan< double > prefixDemand, ReadOnlySpan< double > slope, ReadOnlySpan< double > intercept, Span< int > argMin, Span< int > workspace)
static double Evaluate(int row, int column, ReadOnlySpan< double > prefixDemand, ReadOnlySpan< double > slope, ReadOnlySpan< double > intercept)