RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
Functions | Variables
plan Namespace Reference

Functions

dict load_ledger ()
 
tuple detect (machine_index=None)
 
list build_commands (str stage, dict machine, dict reg, int slots, float budget_h, list share, int grid_v=2000, int grid_vbin=20)
 
tuple residual_rank (dict led)
 
tuple pick_levers (dict led, list worst, int n_wanted=3)
 
 box_for (str col, dict led, float base)
 
float usable_span (str col, dict led, float base, int prefer)
 
list levels_for (str col, float base, int n, int prefer=0, box=None)
 
int prefer_for (str col, str obs, ratio)
 
dict parse_box_override (str spec)
 
tuple build_design (dict led, list levers, list n_levels, str stage, int row_base, list why=None, dict box_override=None)
 
 cost_key (dict cm, dict machine)
 
dict estimate_cost (dict led, dict machine, int n_rows)
 
 main (argv=None)
 

Variables

 encoding
 
 errors
 
 HERE = Path(__file__).resolve().parent
 
str LEDGER = HERE / "calibration_ledger.json"
 
str MACHINES = HERE / "machines.json"
 
dict LEVER_MAP
 
dict DIRECTION
 
dict PRIOR_BOX
 
dict VALIDITY_BOX
 
float MIN_USABLE_SPAN = 0.10
 
dict INTEGER_COLS = {"i_mobile", "v_mobile", "i_discrete", "v_discrete"}
 
dict BOOKKEEPING
 
dict INTEGER_LEVERS = {"i_mobile"}
 

Detailed Description

Propose the next calibration stage for whatever machine this is.

READS  calibration_ledger.json (written by learn.py)
WRITES design/<stage>_calib.csv, design/<stage>_labels.json, and the `next`
       block of the ledger + CALIBRATION_GUIDE.md

IT DOES NOT LAUNCH ANYTHING.  It prints the exact run_ensemble command, sized
to this machine slot count and row budget, and stops.  A design nobody read
can burn 20 h of a machine time, and the machine is usually one the author is
not sitting at.

HOW IT CHOOSES.  Greedy, and deliberately simple enough to argue with:

  1. Pin every parameter to the BEST VALID ROW in the ledger.  That row is the
     only point known to reach full dose grid-clean, so it is the only safe
     place to stand while varying something.
  2. Rank the residuals |log10(model/target)| over the six observables.
  3. Choose levers for the biggest residual, preferring in this order:
        never-varied  >  inconclusive  >  live
     and never proposing a lever the ledger calls `dead`.
  4. Size the factorial to the machine slot count so every row runs
     concurrently, and refuse if the estimated cost exceeds the row budget.

The lever->observable map below is the ONE piece of physics judgement in this
file.  It is explicit so it can be corrected as the campaign learns, rather
than buried in a heuristic.

Usage:
    python plan.py                       # propose for this machine
    python plan.py --machine 0           # propose as if machine 0
    python plan.py --levers eta,f_cl_i   # override the choice
    python plan.py --stage S15           # override the name

Function Documentation

◆ box_for()

plan.box_for ( str  col,
dict  led,
float  base 
)
The prior box for `col`, clipped to the region measured as affordable.

THE CLIFF LOCATION IS UNKNOWN.  S14 shows E_b_i2 = 0.60 never reaches dose
and 0.75 always does; the boundary is somewhere in between and nothing
measures where.  Placing a design point inside that unresolved interval is
a coin flip that costs a full row budget when it loses, so the box is
clipped to the nearest value KNOWN to complete -- not to just above the
value known to fail.

Definition at line 280 of file plan.py.

◆ build_commands()

list plan.build_commands ( str  stage,
dict  machine,
dict  reg,
int  slots,
float  budget_h,
list  share,
int   grid_v = 2000,
int   grid_vbin = 20 
)
The run_ensemble invocation(s) for this stage.

THE SHARD INDEX IS NOT THE REGISTRY INDEX.  run_ensemble assigns a row with
`assign_machine(row_id, of) == row_id % of`, so with `--of 1` only
`--machine 0` is ever given any rows.  Passing this host registry index
(2 for the workstation) with `--of 1` silently assigns it ZERO rows and the
run exits having done nothing.  Shard index is therefore always the
position within the participating set, while the registry index is used
only to name the output file so two machines cannot collide.

Definition at line 168 of file plan.py.

◆ build_design()

tuple plan.build_design ( dict  led,
list  levers,
list  n_levels,
str  stage,
int  row_base,
list   why = None,
dict   box_override = None 
)

Definition at line 390 of file plan.py.

◆ cost_key()

plan.cost_key ( dict  cm,
dict  machine 
)
Which cost_model entry is this registry machine?

THE TWO FILES KEY THE SAME MACHINE DIFFERENTLY.  learn.py keys cost_model by
platform.node() -- the value rows actually carry -- while machines.json keys
by human name.  'MATRIX-PC2' never equals 'Matrix-PC', so entry 1 matched
nothing and fell through to the "scaled from another machine" branch, which
then picked MATRIX-PC2's OWN numbers (it has the worst median) and inflated
them by the 0.5/0.4 speed ratio: a measured 14.02 h worst row was reported
as 17.5 h and tripped the budget refusal on a design that fits.  Match on
the registry's own node_regex, which is the one field that already means
"what this host calls itself".

Definition at line 448 of file plan.py.

◆ detect()

tuple plan.detect (   machine_index = None)

Definition at line 156 of file plan.py.

◆ estimate_cost()

dict plan.estimate_cost ( dict  led,
dict  machine,
int  n_rows 
)

Definition at line 473 of file plan.py.

◆ levels_for()

list plan.levels_for ( str  col,
float  base,
int  n,
int   prefer = 0,
  box = None 
)
n levels for `col`, spanning the prior box.

`prefer` is the corrective direction: -1 sweeps from the box floor up to
the base, +1 from the base up to the ceiling, 0 spans the whole box.  The
base is always one of the endpoints when prefer != 0, so the stage stays
anchored to the point already known to be grid-clean.

Definition at line 329 of file plan.py.

◆ load_ledger()

dict plan.load_ledger ( )

Definition at line 150 of file plan.py.

◆ main()

plan.main (   argv = None)

Definition at line 498 of file plan.py.

◆ parse_box_override()

dict plan.parse_box_override ( str  spec)
--box i_mobile=5:32 -> {"i_mobile": (5.0, 32.0)}.

Definition at line 369 of file plan.py.

◆ pick_levers()

tuple plan.pick_levers ( dict  led,
list  worst,
int   n_wanted = 3 
)
Choose levers for the worst residual, honouring the ledger verdicts.

Definition at line 227 of file plan.py.

◆ prefer_for()

int plan.prefer_for ( str  col,
str  obs,
  ratio 
)
Which way to sweep `col` to shrink the residual on `obs`.

Definition at line 359 of file plan.py.

◆ residual_rank()

tuple plan.residual_rank ( dict  led)
Observables ordered by how badly they miss, worst first.

Returns (chaseable, deferred).  Deferred observables are still ranked and
reported -- they are just not allowed to select levers.  See
`policy.deprioritized_observables` in the ledger for why each is deferred.

Definition at line 212 of file plan.py.

◆ usable_span()

float plan.usable_span ( str  col,
dict  led,
float  base,
int  prefer 
)
Fraction of the prior box still available in the corrective direction.

Definition at line 312 of file plan.py.

Variable Documentation

◆ BOOKKEEPING

dict plan.BOOKKEEPING
Initial value:
1= {"row_id", "condition", "cond_row_id", "matrix", "base_idx",
2 "param_j", "theta_id"}

Definition at line 139 of file plan.py.

◆ DIRECTION

dict plan.DIRECTION
Initial value:
1= {
2 ("eta", "N_100"): +1, ("eta", "N_111"): +1, # more production, more nuclei
3 ("eta", "d_100"): -1, ("eta", "d_111"): -1, # ... spread over more loops
4 ("f_cl_i", "N_100"): +1, ("f_cl_i", "N_111"): +1, # born clustered = born nucleated
5 ("f_cl_i", "d_100"): -1, ("f_cl_i", "d_111"): -1,
6 ("E_b_i2", "N_100"): +1, ("E_b_i2", "N_111"): +1, # small clusters survive
7 ("E_b_i2", "d_100"): -1, ("E_b_i2", "d_111"): -1,
8 ("B_111", "d_111"): +1,
9 ("f_cl_v", "N_void"): +1,
10 # V1 measured both of these directly, one lever at a time:
11 # gamma_s 3.0 -> 1.5 N_void 0.00036x -> 47860x (lower capillary
12 # penalty, small cavities survive)
13 # E_m_v 0.50 -> 0.90 N_void 0.165x -> 6793x (slower vacancies,
14 # less recombination before they cluster)
15 ("gamma_s", "N_void"): -1, ("gamma_s", "d_void"): -1,
16 ("E_m_v", "N_void"): +1, ("E_m_v", "d_void"): +1,
17}

Definition at line 77 of file plan.py.

◆ encoding

plan.encoding

Definition at line 45 of file plan.py.

◆ errors

plan.errors

Definition at line 45 of file plan.py.

◆ HERE

plan.HERE = Path(__file__).resolve().parent

Definition at line 47 of file plan.py.

◆ INTEGER_COLS

dict plan.INTEGER_COLS = {"i_mobile", "v_mobile", "i_discrete", "v_discrete"}

Definition at line 137 of file plan.py.

◆ INTEGER_LEVERS

dict plan.INTEGER_LEVERS = {"i_mobile"}

Definition at line 147 of file plan.py.

◆ LEDGER

str plan.LEDGER = HERE / "calibration_ledger.json"

Definition at line 48 of file plan.py.

◆ LEVER_MAP

dict plan.LEVER_MAP
Initial value:
1= {
2 # Too many loops / too much SIA locked in loops -> attack production and
3 # the clustered fraction born in cascades before touching sink geometry.
4 "N_100": ["eta", "f_cl_i", "E_b_i2", "i_mobile", "s_i"],
5 "N_111": ["eta", "f_cl_i", "E_b_i2", "B_111", "i_mobile"],
6 # Loops too small -> growth vs nucleation balance, then mobility.
7 "d_100": ["f_cl_i", "eta", "E_b_i2", "L_hat", "phi_max_junc"],
8 "d_111": ["B_111", "E_b_i2", "f_cl_i", "E_m_i", "L_hat"],
9 # Cavities absent -> vacancy clustering, surface energy, He.
10 "N_void": ["f_cl_v", "gamma_s", "E_b_v2", "E_b_hV_1", "s_v"],
11 "d_void": ["gamma_s", "E_b_v2", "f_cl_v", "E_m_v", "E_b_hV_1"],
12}

Definition at line 58 of file plan.py.

◆ MACHINES

str plan.MACHINES = HERE / "machines.json"

Definition at line 49 of file plan.py.

◆ MIN_USABLE_SPAN

float plan.MIN_USABLE_SPAN = 0.10

Definition at line 133 of file plan.py.

◆ PRIOR_BOX

dict plan.PRIOR_BOX
Initial value:
1= {
2 "eta": (0.05, 0.60, "log"),
3 "f_cl_i": (0.02, 0.50, "log"),
4 "f_cl_v": (0.05, 0.80, "linear"),
5 "E_b_i2": (0.40, 1.20, "linear"),
6 "E_b_v2": (0.10, 0.45, "linear"),
7 "B_111": (0.20, 0.60, "linear"),
8 "L_hat": (10.0, 3000.0, "log"),
9 "i_mobile": (5, 60, "linear"),
10 "gamma_s": (1.5, 3.0, "linear"),
11 "s_i": (1.0, 3.0, "linear"),
12 "s_v": (1.0, 3.0, "linear"),
13 "E_m_i": (0.25, 0.55, "linear"),
14 "E_m_v": (0.50, 0.90, "linear"),
15 "phi_max_junc": (0.05, 0.60, "linear"),
16 "E_b_hV_1": (1.0, 3.0, "linear"),
17}

Definition at line 97 of file plan.py.

◆ VALIDITY_BOX

dict plan.VALIDITY_BOX
Initial value:
1= {
2 # S14: f_cl_v = 0.5 ran grid-clean to 15 dpa (occ_100 = 0.34); f_cl_v = 0.7
3 # piled at the ceiling (occ_100 = 0.90, grid_limited). The boundary inside
4 # (0.5, 0.7) is unresolved, so 0.5 is the highest value known to work.
5 # NOTE the caveat: S14 moved f_cl_v, E_b_v2 and s_v together, so the
6 # pile-up is not attributable to f_cl_v alone. The cap is conservative for
7 # that reason and can be lifted once a stage varies f_cl_v on its own.
8 "f_cl_v": (None, 0.5),
9}

Definition at line 121 of file plan.py.