69 "Aggarwal-Park Monge matrix search O(n log n)";
77 CancellationToken cancellationToken =
default)
79 ArgumentNullException.ThrowIfNull(problem);
80 cancellationToken.ThrowIfCancellationRequested();
85 ArrayPool<double>.Shared.Rent(horizon + 1);
87 var transformedCostBuffer =
88 ArrayPool<double>.Shared.Rent(horizon);
91 ArrayPool<double>.Shared.Rent(horizon + 1);
93 var predecessorBuffer =
94 ArrayPool<int>.Shared.Rent(horizon + 1);
97 ArrayPool<double>.Shared.Rent(horizon);
100 ArrayPool<int>.Shared.Rent(horizon);
103 ArrayPool<int>.Shared.Rent(horizon);
106 ArrayPool<int>.Shared.Rent(horizon + 1);
108 var matrixWorkspaceBuffer =
109 ArrayPool<int>.Shared.Rent(
110 checked((2 * (horizon + 1)) + 8));
113 ArrayPool<double>.Shared.Rent(horizon);
118 prefixBuffer.AsSpan(0, horizon + 1);
120 var transformedCost =
121 transformedCostBuffer.AsSpan(0, horizon);
124 valueBuffer.AsSpan(0, horizon + 1);
127 predecessorBuffer.AsSpan(0, horizon + 1);
130 interceptBuffer.AsSpan(0, horizon);
133 orderBuffer.AsSpan(0, horizon);
136 scratchBuffer.AsSpan(0, horizon);
139 argMinBuffer.AsSpan(0, horizon + 1);
141 var matrixWorkspace =
142 matrixWorkspaceBuffer.AsSpan(
144 checked((2 * (horizon + 1)) + 8));
147 sortKeyBuffer.AsSpan(0, horizon);
153 BuildTransformedProductionCosts(
158 value.Fill(
double.PositiveInfinity);
159 predecessor.Fill(-1);
169 order[period] = period;
174 -transformedCost[period];
183 CanonicalizeEqualSlopeGroups(
205 cancellationToken.ThrowIfCancellationRequested();
215 ArrayPool<double>.Shared.Return(
219 ArrayPool<double>.Shared.Return(
220 transformedCostBuffer,
223 ArrayPool<double>.Shared.Return(
227 ArrayPool<int>.Shared.Return(
231 ArrayPool<double>.Shared.Return(
235 ArrayPool<int>.Shared.Return(
239 ArrayPool<int>.Shared.Return(
243 ArrayPool<int>.Shared.Return(
247 ArrayPool<int>.Shared.Return(
248 matrixWorkspaceBuffer,
251 ArrayPool<double>.Shared.Return(
257 private static void SolveRange(
263 ReadOnlySpan<double> prefixDemand,
264 ReadOnlySpan<double> transformedCost,
266 Span<int> predecessor,
267 Span<double> intercept,
271 Span<int> matrixWorkspace,
272 CancellationToken cancellationToken)
274 cancellationToken.ThrowIfCancellationRequested();
291 left + ((right - left) >> 1);
293 var leftOrderCount = 0;
295 for (var position = 0;
296 position < orderCount;
299 if (order[orderStart + position] <= middle)
305 var nextLeft = orderStart;
306 var nextRight = orderStart + leftOrderCount;
308 for (var position = 0;
309 position < orderCount;
313 order[orderStart + position];
315 if (period <= middle)
317 scratch[nextLeft++] = period;
321 scratch[nextRight++] = period;
326 .Slice(orderStart, orderCount)
328 order.Slice(orderStart, orderCount));
330 var rightOrderStart =
331 orderStart + leftOrderCount;
333 var rightOrderCount =
334 orderCount - leftOrderCount;
354 targetStart: middle + 1,
385 MergeSlopeOrderedChildren(
394 private static void RelaxCrossMatrix(
397 ReadOnlySpan<int> predecessorColumns,
398 ReadOnlySpan<double> prefixDemand,
399 ReadOnlySpan<double> transformedCost,
400 ReadOnlySpan<double> intercept,
402 Span<int> predecessor,
404 Span<int> matrixWorkspace)
406 if (predecessorColumns.IsEmpty ||
407 targetStart > targetEnd)
413 targetEnd - targetStart + 1;
415 AggarwalParkMatrixSearch.FindRowMinima(
425 for (var target = targetStart;
429 var candidatePredecessor =
432 if (candidatePredecessor < 0)
434 throw new InvalidOperationException(
435 "Aggarwal-Park matrix search did not return a predecessor.");
439 AggarwalParkMatrixSearch.Evaluate(
441 candidatePredecessor,
446 if (candidateValue < value[target])
448 value[target] = candidateValue;
449 predecessor[target] =
450 candidatePredecessor;
455 private static void FinalizeState(
458 ReadOnlySpan<double> prefixDemand,
459 ReadOnlySpan<double> transformedCost,
461 Span<int> predecessor,
462 Span<double> intercept)
465 problem.
Demands[state - 1] == 0.0 &&
466 value[state - 1] <= value[state])
475 if (!
double.IsFinite(value[state]))
477 throw new ArithmeticException(
478 $
"No finite Aggarwal-Park dynamic-programming value was obtained for state {state}.");
486 var transformedDemandCost =
487 transformedCost[state] *
490 var candidateIntercept =
493 transformedDemandCost;
495 if (!
double.IsFinite(transformedDemandCost) ||
496 !
double.IsFinite(candidateIntercept))
498 throw new ArithmeticException(
499 "Numerical overflow while constructing an Aggarwal-Park predecessor line.");
506 private static void BuildPrefixDemand(
507 ReadOnlySpan<double> demands,
508 Span<double> prefixDemand)
510 prefixDemand[0] = 0.0;
513 period < demands.Length;
517 prefixDemand[period] +
520 if (!
double.IsFinite(next))
522 throw new ArithmeticException(
523 "Numerical overflow while computing cumulative demand.");
526 prefixDemand[period + 1] = next;
530 private static void BuildTransformedProductionCosts(
531 ReadOnlySpan<double> productionCosts,
532 ReadOnlySpan<double> holdingCosts,
533 Span<double> transformedCost)
535 var holdingSuffix = 0.0;
537 for (var period = productionCosts.Length - 1;
541 if (period < productionCosts.Length - 1)
544 holdingCosts[period];
546 if (!
double.IsFinite(holdingSuffix))
548 throw new ArithmeticException(
549 "Numerical overflow while computing holding-cost suffix.");
554 productionCosts[period] +
557 if (!
double.IsFinite(value))
559 throw new ArithmeticException(
560 "Numerical overflow while computing transformed production cost.");
563 transformedCost[period] = value;
567 private static void CanonicalizeEqualSlopeGroups(
574 while (start < length)
578 while (end < length &&
579 sortedKeys[end] == sortedKeys[start])
596 private static void MergeSlopeOrderedChildren(
600 ReadOnlySpan<double> transformedCost,
604 if (leftCount == 0 ||
610 var leftPosition = orderStart;
611 var leftEnd = orderStart + leftCount;
613 var rightPosition = leftEnd;
614 var rightEnd = rightPosition + rightCount;
616 var destination = orderStart;
618 while (leftPosition < leftEnd &&
619 rightPosition < rightEnd)
625 order[rightPosition];
632 scratch[destination++] =
639 scratch[destination++] =
646 while (leftPosition < leftEnd)
648 scratch[destination++] =
649 order[leftPosition++];
652 while (rightPosition < rightEnd)
654 scratch[destination++] =
655 order[rightPosition++];
661 leftCount + rightCount)
665 leftCount + rightCount));
668 private static bool ComesBefore(
671 ReadOnlySpan<double> transformedCost)
674 transformedCost[leftPeriod];
677 transformedCost[rightPeriod];
679 if (leftSlope > rightSlope)
684 if (leftSlope < rightSlope)
689 return leftPeriod < rightPeriod;