Skip to content

Write a schedule

A schedule turns a run non-stationary: named regimes, durations, axes. The full key reference lists every field; this is the working pattern.

from skyfall_crl.regime import RegimeSchedule, ScheduledRegime

provider = ScheduledRegime(RegimeSchedule([
    {"regime_id": "baseline", "duration_steps": 300, "ticket_arrival_rate": 0.2},
    {"regime_id": "surge", "duration_steps": 300, "ticket_arrival_rate": 0.8},
    {"regime_id": "baseline_revisit", "alias_of": "baseline", "duration_steps": 300},
]))
for step in (0, 299, 300, 650):
    print(f"step {step:>4}: {provider.regime_id(step)}")
print(f"revisit repeats: {provider.regime_at(700).origin}")
step    0: baseline
step  299: baseline
step  300: surge
step  650: baseline_revisit
revisit repeats: baseline

The same mapping goes into an experiment document under regime.params.schedule, or into its own YAML file.

The three rules that matter:

  1. Bring something back. The alias_of entry is what makes forgetting measurable — the same axes under a new label, with the origin recorded so both labels report one number. A schedule that never returns to anything cannot measure forgetting, and the table will show --.
  2. Pick how shifts land. shift_mode: abrupt (default) switches whole at the boundary; linear_drift eases numeric axes over drift_window steps — a different experimental question; cyclic wraps the entries for as long as the run lasts.
  3. Axes are open, and typos are guarded. Any key that is not regime_id, duration_steps or alias_of is an axis. A near-miss of a known axis warns (tickt_arival_rate is never intentional); a key literally named axes is rejected as nesting-by-mistake; a genuinely new name passes — and is only meaningful if something (your wrapper, a task view) reads it.