perf(planning): retain the best valid plan more cheaply

Retaining a better valid plan reconciled its optimization provenance,
which evaluated every active objective once per credited placement with
that placement released. Only the credited objectives are compared, so
the released plan now evaluates just those and skips observing the
others. Retention also stops computing quality metrics that nothing
read. The retained plan and what Cancel returns are unchanged.

Part of #302.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nessarandClaude Opus 5.5 committed 2026-10-18 19:50:58 +02:00
1 parent 0b0bb8f9a8
commit 2fcba758c8
2 files changed
+46 -21

No files matched your search

+24
View File
@@ -35,3 +35,27 @@ the placements in the same order with the same yields.
| loaded year | 58.26 | 57.75 | | loaded year | 58.26 | 57.75 |
| two years | 2.27 | 1.82 | | two years | 2.27 | 1.82 |
| three years | 7.45 | 5.52 | | three years | 7.45 | 5.52 |
### Retain the best valid plan more cheaply
Retaining a better valid plan for cancellation reconciled its optimization
provenance. For every placement credited by an optimization rule, that
evaluated all active objectives with the placement released. Only the credited
objectives are compared, so the released plan now evaluates just those, and
the calendar observations of the other objectives are no longer computed.
Retention also no longer computes quality metrics that nothing read. An
instrumented three-year run spent 1.16 of its 1.22 retention seconds in that
reconciliation; afterwards retention took 0.26 seconds.
| Scenario | Before (s) | After (s) |
| --------------------------- | ---------: | --------: |
| representative year | 0.45 | 0.37 |
| typed obligations | 4.76 | 5.03 |
| chained earned entitlements | 0.02 | 0.02 |
| deliberate conflict | 0.01 | 0.01 |
| loaded year | 55.59 | 53.58 |
| two years | 1.68 | 1.22 |
| three years | 5.29 | 3.85 |
The typed-obligations difference is run-to-run noise; its retention cost was
22 milliseconds.
+22 -21
View File
@@ -544,12 +544,8 @@ interface SearchContext {
observerTime: number; observerTime: number;
completedPreferenceStates: Map<number, string>; completedPreferenceStates: Map<number, string>;
completedGenericState?: string; completedGenericState?: string;
/** Supplied metrics were evaluated after reconciling earned entitlement dates. */ /** A supplied vector was evaluated after reconciling earned entitlement dates. */
retain?: ( retain?: (state: State, vector?: PlanningObjectiveVector) => void;
state: State,
finalizedMetrics?: PlanningMetrics,
vector?: PlanningObjectiveVector,
) => void;
} }
type PlanningCalendar = Pick< type PlanningCalendar = Pick<
@@ -1869,7 +1865,15 @@ function reconcileOptimizationProvenance(
); );
if (!credits.length) continue; if (!credits.length) continue;
const withoutState = releasePlacement(state, placement.date); const withoutState = releasePlacement(state, placement.date);
const withoutPlacement = activeObjectiveVector(withoutState, context); // Only credited objectives are compared, so skip observing the others.
const withoutPlacement = [
...activeObjectives(
withoutState,
context,
Number.POSITIVE_INFINITY,
new Set(credits.map(({ id }) => id)),
),
];
const ruleIds = new Set( const ruleIds = new Set(
[...placement.ruleIds].filter( [...placement.ruleIds].filter(
(id) => !credits.some((objective) => objective.id === `optimization:${id}`), (id) => !credits.some((objective) => objective.id === `optimization:${id}`),
@@ -2468,7 +2472,7 @@ function evaluateRuleCandidate(
const vector = objectiveVector(state, context, active); const vector = objectiveVector(state, context, active);
// Preserve the best valid candidate even if the active prefix rejects it. // Preserve the best valid candidate even if the active prefix rejects it.
if (compareObjectiveLists(vector.objectives, incumbentVector.objectives) < 0) if (compareObjectiveLists(vector.objectives, incumbentVector.objectives) < 0)
context.retain?.(state, undefined, vector); context.retain?.(state, vector);
// shortcut: single additions cannot cross equal-value plateaus; add coordinated balance bundles when a measured configuration needs them. // shortcut: single additions cannot cross equal-value plateaus; add coordinated balance bundles when a measured configuration needs them.
if (addition && comparison === 0) return undefined; if (addition && comparison === 0) return undefined;
return { state, vector }; return { state, vector };
@@ -2676,7 +2680,7 @@ function* optimizeRulePlacements(
const active = activeObjectiveVector(result, context); const active = activeObjectiveVector(result, context);
const baseline = active.slice(0, throughIndex + 1); const baseline = active.slice(0, throughIndex + 1);
const beforeVector = objectiveVector(result, context, active); const beforeVector = objectiveVector(result, context, active);
context.retain?.(result, undefined, beforeVector); context.retain?.(result, beforeVector);
if ( if (
allowAdd && allowAdd &&
prefixBounds.length > 0 && prefixBounds.length > 0 &&
@@ -3395,6 +3399,7 @@ function* activeObjectives(
state: State, state: State,
context: SearchContext, context: SearchContext,
throughIndex = Number.POSITIVE_INFINITY, throughIndex = Number.POSITIVE_INFINITY,
only?: ReadonlySet<PlanningObjectiveId>,
): Generator<PlanningObjective> { ): Generator<PlanningObjective> {
let preferences: PreferenceResult[] | undefined; let preferences: PreferenceResult[] | undefined;
let balances: OptimizationObservations | undefined; let balances: OptimizationObservations | undefined;
@@ -3403,6 +3408,11 @@ function* activeObjectives(
for (const rule of context.rules) { for (const rule of context.rules) {
if (!rule.enabled || rule.force !== 'preference') continue; if (!rule.enabled || rule.force !== 'preference') continue;
if (index++ > throughIndex) return; if (index++ > throughIndex) return;
if (
only &&
!only.has(`${rule.type === 'optimization' ? 'optimization' : 'preference'}:${rule.id}`)
)
continue;
if (rule.type === 'optimization') { if (rule.type === 'optimization') {
const observations = const observations =
rule.configuration.elements === 'leave-balances' rule.configuration.elements === 'leave-balances'
@@ -4257,17 +4267,16 @@ function* calculatePlanningSteps(
let checkpoint: PlanOutcome | undefined; let checkpoint: PlanOutcome | undefined;
let checkpointState: State | undefined; let checkpointState: State | undefined;
let retained: let retained:
| (Pick<ObligationPreview, 'state' | 'preferences' | 'metrics'> & { | (Pick<ObligationPreview, 'state' | 'preferences'> & {
vector: PlanningObjectiveVector; vector: PlanningObjectiveVector;
}) })
| undefined; | undefined;
if (retainIncumbents) if (retainIncumbents)
context.retain = (candidate, finalizedMetrics, candidateVector) => { context.retain = (candidate, candidateVector) => {
const check = context.check; const check = context.check;
context.check = () => undefined; context.check = () => undefined;
try { try {
const finalized = const finalized = candidateVector
finalizedMetrics || candidateVector
? candidate ? candidate
: finalizeEarnedState(candidate, context); : finalizeEarnedState(candidate, context);
if (!finalized) return; if (!finalized) return;
@@ -4314,14 +4323,6 @@ function* calculatePlanningSteps(
), ),
vector, vector,
preferences: candidatePreferences, preferences: candidatePreferences,
metrics: finalizedMetrics
? {
...finalizedMetrics,
preferenceSatisfaction: candidatePreferences.map(
({ satisfied }) => satisfied,
),
}
: qualityMetrics(finalized, context, candidatePreferences),
}; };
if (firstValidCandidate && checkpointCallback) if (firstValidCandidate && checkpointCallback)
observeCallback(context, () => observeCallback(context, () =>