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,
|
||||
context: SearchContext,
|
||||
accept: (candidate: State, addition?: boolean) => boolean,
|
||||
donorObjectives = new Map<string, PlanningObjective[]>(),
|
||||
): PlanningSteps<boolean> {
|
||||
const missing = targetDates.filter(
|
||||
(date) => !state.placements.has(date) && !context.declaredDates.has(date),
|
||||
@@ -2221,10 +2222,12 @@ function* calendarPlacementCandidates(
|
||||
yield;
|
||||
context.check();
|
||||
if (targetDates.includes(placement.date)) continue;
|
||||
rankedDonors.push({
|
||||
date: placement.date,
|
||||
objectives: activeObjectiveVector(releasePlacement(state, placement.date), context),
|
||||
});
|
||||
let objectives = donorObjectives.get(placement.date);
|
||||
if (!objectives) {
|
||||
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.
|
||||
rankedDonors.sort(
|
||||
@@ -2330,8 +2333,17 @@ function* holidayWeekPlacementCandidates(
|
||||
context: SearchContext,
|
||||
accept: (candidate: State, addition?: boolean) => boolean,
|
||||
): 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)
|
||||
yield* calendarPlacementCandidates(state, week.dates, ruleId, context, accept);
|
||||
yield* calendarPlacementCandidates(
|
||||
state,
|
||||
week.dates,
|
||||
ruleId,
|
||||
context,
|
||||
accept,
|
||||
donorObjectives,
|
||||
);
|
||||
}
|
||||
|
||||
function* timeOffPeriodPlacementCandidates(
|
||||
|
||||
Reference in new issue
Block a user