Templating

Tools for turning template lattice/tao.init files into concrete outputs: latform-apply for a single file, latform-template for a whole set across instances.

latform-apply

Apply value overrides and/or renames to a single template file and write the result to stdout (-o FILE, or -i/--in-place to rewrite the input file). The file may be Bmad or a Fortran-namelist file (*.init / *.nml); the format is auto-detected from the extension and can be forced with --format {bmad,namelist}. Unlike jinja/cookiecutter, a Bmad template is itself valid Bmad — it parses, lints, and formats standalone — while a YAML sidecar supplies per-element values.

latform-apply TEMPLATE [--format {bmad,namelist}] [--values VALUES.yaml]
              [--set NAMELIST KEY VALUE]
              [--rename OLD NEW] [--prefix FROM TO] [--suffix FROM TO]
              [--parts DELIMS FROM TO] [--delimiters CHARS]
              [--no-format-namelist] [--namelist-indent N]
              [--namelist-field-case {upper,lower,same}]
              [--no-namelist-align-equals] [--no-namelist-align-comments]
              [-o OUT | -i]

Given quad.bmad:

Q1: quadrupole, L=0.3, k1=0.0

and values.yaml:

Q1:
  k1: 1.523 # override an attribute
# "/.*_BPM/": { type: BPM_TYPE }   # regex key: every matching element
# BEN0: { type: null }             # null removes an attribute
latform-apply quad.bmad --values values.yaml
# -> Q1: quadrupole, L=0.3, k1=1.523

# --values - reads the overrides (YAML or JSON) from stdin, for programmatic use
echo '{"Q1": {"k1": 1.523}}' | latform-apply quad.bmad --values -

--rename is repeatable and accepts literal or regex rules:

latform-apply quad.bmad --rename 'Q(\d+)' 'ARC_Q\1'
# -> ARC_Q1: quadrupole, L=0.3, k1=0.0

--prefix, --suffix, and --parts are the named rename forms (all repeatable):

# prefix: leading FROM only, bounded by . or _  (CX_A -> C1_A; O_CX_A untouched)
latform-apply lat.bmad --prefix CX C1

# suffix: trailing FROM, bounded by . or _  (Q_XCR -> Q_HCOR)
latform-apply lat.bmad --suffix _XCR _HCOR

# parts: rename whole segments anywhere, split on the given delimiters
#        (CX_A -> C1_A and O_CX_A -> O_C1_A)
latform-apply lat.bmad --parts ._ CX C1

# --delimiters sets the boundary chars for --prefix/--suffix (default . and _)
latform-apply lat.bmad --prefix CX C1 --delimiters _

Namelist files

For a namelist file (*.init / *.nml, e.g. a Tao tao.init), overrides are keyed by namelist group instead of by element. Renames do not apply. Each group maps to a {key: value} block; existing keys are updated in place, missing keys are appended, and a null value removes a key. A name#N suffix (1-based) targets the N-th of a repeated group.

values.yaml:

tao_params:
  global%n_opti_cycles: 50 # update in place
  global%plot_on: null # remove the key
tao_beam_init:
  beam_init%n_particle: 5000 # group appended if absent
latform-apply tao.init --values values.yaml

# --set NAMELIST KEY VALUE is the inline, repeatable equivalent (and wins over
# --values); -i rewrites tao.init in place instead of printing to stdout.
latform-apply tao.init --set tao_params global%n_opti_cycles 50 -i

The output namelist is reformatted by default (field indentation, lowercase field names, aligned = and comments, and a blank line after each group). latform-apply changes layout only — unlike latform/latform-template it does not normalize values. Pass --no-format-namelist to preserve the source layout, or tune the result with the namelist formatting flags:

latform-apply tao.init --no-format-namelist -i          # edit values, keep layout
latform-apply tao.init --no-namelist-align-equals -i    # reformat but don't align '='

latform-template

Expand a Bmad lattice template set across several instances, writing files under --output-dir (default: current directory). A YAML sidecar lists the template files and per-instance values, renames, and output paths. Use --dry-run to list the files that would be written without writing them.

latform-template INSTANCES.yaml [-d OUTPUT_DIR] [--dry-run]
                 [--no-format-namelist] [--namelist-indent N]
                 [--namelist-field-case {upper,lower,same}]
                 [--no-namelist-align-equals] [--no-namelist-align-comments]

instances.yaml (paths are relative to the file's own directory):

template:
  - input: cx.bmad
    output: "{instance}/{instance}.bmad" # {instance} -> c1, c2, ...
  - input: cx.cor.bmad
    output: "{instance}/{instance}.cor.bmad"

renames:
  "CX([_.].*|$)": "{instance:upper}\\1" # flat shortcut: literal unless it has * + ?

instances:
  c1: {}
  c2:
    values: { CX_LINE_ROT: pi/2 } # per-instance overrides

renames also accepts a structured form with any of prefix, suffix, regex, and parts, each usable globally (in place of the flat block above) or per instance. They are typically used one at a time — pick the form that fits. {instance}, {instance:upper}, and {instance:lower} interpolate in every replacement.

Prefix / suffix renames

prefix renames a leading FROM bounded by a delimiter (default . or _); suffix is the mirror for a trailing FROM. Both are {from: to} maps.

renames:
  prefix:
    CX: "{instance:upper}" # CX_BEN0 -> C1_BEN0, CX.COL00 -> C1.COL00, bare CX -> C1
    O_CX: "O_{instance:upper}" # embedded needs its own entry (prefix is leading-only)
  suffix:
    _XCR: _HCOR # A_XCR -> A_HCOR (trailing, bounded)

Regex renames

regex is the escape hatch: raw re.sub per name, so you write your own backreferences. (Equivalent to the flat shortcut when the key contains * + ?.)

renames:
  regex:
    "CX([_.].*|$)": "{instance:upper}\\1"

Parts renames

parts renames whole delimiter-separated segments anywhere in a name — the one form that also rewrites embedded segments (e.g. O_CX_BEN), so a single rule covers leading, embedded, dotted, and bare occurrences.

renames:
  parts:
    - delimiters: "._" # a string "._" or a list [".", "_"]
      from: CX
      to: "{instance:upper}"

Set a top-level delimiters to change the default delimiter set for prefix, suffix, and parts. With a default in place, parts may be written as a plain {from: to} map, matching the shape of prefix/suffix:

delimiters: "._" # applies to prefix / suffix / parts (default: . and _)

renames:
  parts:
    CX: "{instance:upper}" # uses the top-level delimiters

Per name, the first matching rule wins in the order literal → regex → prefix → suffix → parts. A plain {from: to} map with no prefix/suffix/regex/parts key is the flat shortcut shown earlier (today's literal-or-regex behavior).

latform-template instances.yaml -d build/
# wrote: build/c1/c1.bmad
# wrote: build/c1/c1.cor.bmad
# wrote: build/c2/c2.bmad
# ...

latform-template instances.yaml -d build/ --dry-run
# would write: build/c1/c1.bmad
# ...

Files that the template calls but are not in template (e.g. shared settings/) can be listed under a top-level context: key to be loaded for name resolution only — they are never written, and calls to them are left untouched. calls between transform-set files are rewritten to the instance outputs automatically.

Tao init (tao_init:)

An optional top-level tao_init: key renders a Tao tao.init per instance. It takes an input (the template tao.init) and an output path (which may use {instance}). The design_lattice file entries are rewritten to the instance's generated lattice paths — the same rewriting applied to call targets.

tao_init:
  input: tao.init
  output: "{instance}/tao.init"

instances:
  c1: {}
  c2:
    tao_init:
      namelists: # add/update namelist sections for this instance
        tao_params:
          global%n_opti_cycles: 50 # updated in place
        tao_beam_init:
          beam_init%n_particle: 5000 # group appended if absent

A per-instance tao_init.namelists block adds or updates namelist sections: each entry is a {key: value} map, values interpolate {instance}, and a name#N suffix (1-based) targets the N-th of a repeated group.

The emitted tao.init is reformatted by default, and its values are normalized against the bundled Tao schema — strings quoted, enum indices mapped to names, and logicals canonicalized to T/F (see Value normalization). This is controlled by the shared namelist formatting flags (e.g. --no-format-namelist to keep the template's layout and values). The generated Bmad lattices are always reformatted regardless.