38 private readonly record
struct MergeCandidate(
43 "Karni Maximum Part-Period Gain";
54 CancellationToken cancellationToken =
default)
56 ArgumentNullException.ThrowIfNull(problem);
57 cancellationToken.ThrowIfCancellationRequested();
66 ArrayPool<int>.Shared.Rent(horizon);
71 cycleBuffer.AsSpan(0, horizon);
75 ReadOnlySpan<double> demands =
97 if (holdingCost == 0.0)
109 double economicPartPeriod =
128 Array.Fill(previous, -1);
129 Array.Fill(next, -1);
131 int previousPositive = -1;
133 for (
int period = first;
137 if ((period & 255) == 0)
139 cancellationToken.ThrowIfCancellationRequested();
142 if (demands[period] == 0.0)
147 active[period] =
true;
148 quantity[period] = demands[period];
149 previous[period] = previousPositive;
151 if (previousPositive >= 0)
153 next[previousPositive] =
164 (
double PartPeriods,
int RightStart)>();
166 for (
int right = next[first];
173 while (queue.TryDequeue(
174 out MergeCandidate candidate,
177 cancellationToken.ThrowIfCancellationRequested();
180 candidate.RightStart;
182 if (!active[right] ||
183 candidate.Version != version[right] ||
196 if (!
double.IsFinite(partPeriods))
198 throw new ArithmeticException(
199 "Numerical overflow while evaluating an MPG merge.");
219 "MPG merged lot quantity");
223 active[right] =
false;
229 if (rightNeighbor >= 0)
231 previous[rightNeighbor] =
234 version[rightNeighbor]++;
238 Enqueue(rightNeighbor);
241 for (
int start = first;
246 throw new InvalidOperationException(
247 "Invalid MPG active-lot chain.");
285 if (!
double.IsFinite(partPeriods))
287 throw new ArithmeticException(
288 "Numerical overflow while creating an MPG merge candidate.");
303 ArrayPool<int>.Shared.Return(
309 private static bool StrictlyGreater(
325 private static double AddFinite(
333 if (!
double.IsFinite(value))
335 throw new ArithmeticException(
336 $
"Numerical overflow while computing {operation}.");
Shared applicability checks for classical stationary-cost lot-sizing heuristics.
static void ThrowIfNotStationary(UlsProblem problem, string solverName)
static bool HasStationaryRelevantCosts(UlsProblem problem)
static int FindNextPositiveDemand(ReadOnlySpan< double > demands, int start)
Builds and validates a zero-backlogging heuristic solution from a set of replenishment cycles.
static UlsSolveResult Build(UlsProblem problem, ReadOnlySpan< int > cycleEnds, string solverName, CancellationToken cancellationToken)
Implements Karni's Maximum Part-Period Gain (MPG) heuristic.
static bool IsApplicable(UlsProblem problem)
UlsSolveResult Solve(UlsProblem problem, CancellationToken cancellationToken=default)
Solves an uncapacitated lot-sizing problem.
UlsSolverKind Kind
Gets the broad family of the solver.
string Name
Gets the stable human-readable name of the solver.
Represents a validated classical uncapacitated lot-sizing problem.
int Horizon
Gets the number of planning periods.
ReadOnlySpan< double > HoldingCosts
Gets end-of-period unit holding costs by period.
ReadOnlySpan< double > Demands
Gets demand by period.
ReadOnlySpan< double > SetupCosts
Gets fixed setup costs by period.
Represents the outcome returned by a ULS solution strategy.
Defines the common strategy contract implemented by every ULS solver.
UlsSolverKind
Identifies the broad family of a ULS solution strategy.