perf(planning): reuse the donor ranking across public-holiday weeks
The holiday-week stage ranked every placement as a donor once per public-holiday week, although the ranking depends only on the plan. The weeks of one plan now share each placement's released objectives; each week still skips its own dates and walks the placements in the same order with the same yields. Part of #302. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b356117263
commit
0b0bb8f9a8
2 files changed
+54
-5
No files matched your search
@@ -0,0 +1,37 @@
|
|||||||
|
# Issue #302: plan from the best-ranked obligation solution
|
||||||
|
|
||||||
|
Step-by-step record for [issue #302](https://gitea.nessar.fr/nessar/vac-optimizer/issues/302).
|
||||||
|
The exact steps keep planning outcomes, stable explanations and final states
|
||||||
|
identical to the frozen engine at `a871f8f`. The
|
||||||
|
[frozen-engine differential check](README.md#frozen-engine-differential-check),
|
||||||
|
including its cancelled asynchronous comparisons, passes after every step.
|
||||||
|
|
||||||
|
## Per-step measurements
|
||||||
|
|
||||||
|
Each step was timed against its parent in the same session, on the development
|
||||||
|
machine (AMD Ryzen 7 7800X3D, WSL2, Bun 1.4.3), with no other test, build or
|
||||||
|
profiling workload running. Both commits' cores were bundled with the
|
||||||
|
frozen-engine build command and called through `calculatePlanningAsyncInternal`
|
||||||
|
in one Bun process, with `calculationDate: '2024-01-01'`. This is the
|
||||||
|
benchmark's in-thread asynchronous path, without the planning-calculation
|
||||||
|
service. For every scenario, each engine ran one warm-up and then one measured
|
||||||
|
calculation, alternating engines. Outcomes, explanations and final states were
|
||||||
|
hashed; every pair below matched. Only same-session pairs are comparable.
|
||||||
|
|
||||||
|
### Reuse the donor ranking across public-holiday weeks
|
||||||
|
|
||||||
|
For every public-holiday week, the holiday-week stage ranked every placement as
|
||||||
|
a potential donor by releasing it and evaluating the active objectives. That
|
||||||
|
ranking depends only on the plan, so the weeks of one plan now share each
|
||||||
|
placement's released objectives. Each week still skips its own dates and walks
|
||||||
|
the placements in the same order with the same yields.
|
||||||
|
|
||||||
|
| Scenario | Before (s) | After (s) |
|
||||||
|
| --------------------------- | ---------: | --------: |
|
||||||
|
| representative year | 0.66 | 0.66 |
|
||||||
|
| typed obligations | 5.11 | 4.89 |
|
||||||
|
| chained earned entitlements | 0.02 | 0.02 |
|
||||||
|
| deliberate conflict | 0.01 | 0.01 |
|
||||||
|
| loaded year | 58.26 | 57.75 |
|
||||||
|
| two years | 2.27 | 1.82 |
|
||||||
|
| three years | 7.45 | 5.52 |
|
||||||
@@ -2208,6 +2208,7 @@ function* calendarPlacementCandidates(
|
|||||||
ruleId: string,
|
ruleId: string,
|
||||||
context: SearchContext,
|
context: SearchContext,
|
||||||
accept: (candidate: State, addition?: boolean) => boolean,
|
accept: (candidate: State, addition?: boolean) => boolean,
|
||||||
|
donorObjectives = new Map<string, PlanningObjective[]>(),
|
||||||
): PlanningSteps<boolean> {
|
): PlanningSteps<boolean> {
|
||||||
const missing = targetDates.filter(
|
const missing = targetDates.filter(
|
||||||
(date) => !state.placements.has(date) && !context.declaredDates.has(date),
|
(date) => !state.placements.has(date) && !context.declaredDates.has(date),
|
||||||
@@ -2221,10 +2222,12 @@ function* calendarPlacementCandidates(
|
|||||||
yield;
|
yield;
|
||||||
context.check();
|
context.check();
|
||||||
if (targetDates.includes(placement.date)) continue;
|
if (targetDates.includes(placement.date)) continue;
|
||||||
rankedDonors.push({
|
let objectives = donorObjectives.get(placement.date);
|
||||||
date: placement.date,
|
if (!objectives) {
|
||||||
objectives: activeObjectiveVector(releasePlacement(state, placement.date), context),
|
objectives = activeObjectiveVector(releasePlacement(state, placement.date), context);
|
||||||
});
|
donorObjectives.set(placement.date, objectives);
|
||||||
|
}
|
||||||
|
rankedDonors.push({ date: placement.date, objectives });
|
||||||
}
|
}
|
||||||
// shortcut: recursion retains the original donor ranking; rerank when a measured bundle misses a better allocation.
|
// shortcut: recursion retains the original donor ranking; rerank when a measured bundle misses a better allocation.
|
||||||
rankedDonors.sort(
|
rankedDonors.sort(
|
||||||
@@ -2330,8 +2333,17 @@ function* holidayWeekPlacementCandidates(
|
|||||||
context: SearchContext,
|
context: SearchContext,
|
||||||
accept: (candidate: State, addition?: boolean) => boolean,
|
accept: (candidate: State, addition?: boolean) => boolean,
|
||||||
): PlanningSteps<void> {
|
): PlanningSteps<void> {
|
||||||
|
// Releasing a donor scores the same for every week of this plan.
|
||||||
|
const donorObjectives = new Map<string, PlanningObjective[]>();
|
||||||
for (const week of context.holidayWeeks)
|
for (const week of context.holidayWeeks)
|
||||||
yield* calendarPlacementCandidates(state, week.dates, ruleId, context, accept);
|
yield* calendarPlacementCandidates(
|
||||||
|
state,
|
||||||
|
week.dates,
|
||||||
|
ruleId,
|
||||||
|
context,
|
||||||
|
accept,
|
||||||
|
donorObjectives,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function* timeOffPeriodPlacementCandidates(
|
function* timeOffPeriodPlacementCandidates(
|
||||||
|
|||||||
Reference in new issue
Block a user