latform
A Bmad lattice parser, formatter, and analysis toolkit for particle accelerator
lattice files. It parses, formats, lints, diffs, inspects, and templates Bmad
lattices, and also understands Tao tao.init / Fortran-namelist files.
Installation
pip install latform
From source / with a conda environment
conda env create -n latform python=3.12 pip
conda activate latform
git clone https://github.com/ken-lauer/latform
cd latform
python -m pip install .
Optional extras — .[test] (test suite), .[doc] (docs), .[lsp] (language
server), or combine them: .[test,doc].
Quickstart
# Format a lattice and print to stdout
latform my_lattice.bmad
# Format in place
latform -i my_lattice.bmad
# Preview what the formatter would change (no writes)
latform --diff my_lattice.bmad
# Format from a pipe
cat my_lattice.bmad | latform -
# Format every file in a directory, in place
latform -i *.bmad
# Lint without reformatting (exits non-zero on findings — good for CI)
latform-lint my_lattice.bmad
Point latform at a Tao tao.init and it formats the tao.init and every
Bmad lattice it references:
latform -i tao.init # rewrite the init and its lattices in place
latform --diff tao.init # preview all of the changes
The tools
latform installs several focused commands:
| Command | What it does |
|---|---|
latform |
Parse and format Bmad lattice files (and tao.init namelists) |
latform-lint |
Lint lattice files without reformatting (non-zero exit on findings) |
latform-apply |
Apply value overrides / renames to a single template file |
latform-template |
Expand a template set across many instances |
latform-dump |
Extract parameters and element information |
latform-diff |
Compare two lattice files structurally |
latform-gitdiff |
Compare a lattice file across git revisions |
latform-graph |
Visualize the file dependency (call) tree |
Run any command with -h for its full option list, or see the
CLI reference. A ninth command,
latform-lsp, provides editor integration — see
Editor integration.
Common operations
Enforce house style on save / in CI
See also: latform-pre-commit.
Format in place and lint as two steps:
latform -i lat/*.bmad # reformat
latform-lint -r lat/main.bmad # lint recursively; exits 1 on any finding
Set your project's rules once in a latform.toml (see
Configuration) so every invocation and every contributor gets
the same result. latform --lint reports lints alongside the formatted output;
latform-lint is the CI-friendly, exit-code-driven counterpart.
Rename elements
# Inline old,new pairs (repeatable)
latform -R 'Q1,QF' -R 'Q2,QD' my_lattice.bmad
# From a CSV of old,new pairs
latform --rename-file renames.csv my_lattice.bmad
Flatten a multi-file lattice into one
latform -r my_lattice.bmad # parse recursively, following `call`s
latform --flatten my_lattice.bmad # inline all called files into one output
Inspect a lattice
latform-dump my_lattice.bmad # parameters, used, and unused elements
latform-dump -u my_lattice.bmad # only unused elements
latform-dump -m 'Q*' my_lattice.bmad # filter by glob (or -r for regex)
latform-dump -d ',' my_lattice.bmad # CSV-style output
Compare lattices
latform-diff old.bmad new.bmad # structural diff of params & elements
latform-gitdiff my_lattice.bmad HEAD~1 # compare a file against an earlier commit
Visualize the dependency tree
latform-graph my_lattice.bmad # text tree of `call`ed files
latform-graph -f mermaid -o deps.mmd main.bmad # Mermaid diagram to a file
Fill in a single template file
A Bmad template is itself valid Bmad; --set edits one value inline, and a YAML
sidecar supplies them in bulk.
latform-apply const.bmad --set 'A = 10' -i # a constant
latform-apply quad.bmad --set 'Q1[k1] = 1.523' # an element attribute
latform-apply quad.bmad --set 'parameter[e_tot] = 5e9' # a parameter
latform-apply quad.bmad --unset 'BEN0[type]' # drop an attribute
# values.yaml: Q1: { k1: 1.523 }
latform-apply quad.bmad --values values.yaml
latform-apply quad.bmad --rename 'Q(\d+)' 'ARC_Q\1' # regex rename
The same command edits tao.init / namelist files, keyed by namelist group:
latform-apply tao.init --set 'tao_params[global%n_opti_cycles] = 50' -i
Expand a template set across instances
Describe the template files, per-instance values, and renames in an
instances.yaml, then generate every instance at once:
latform-template instances.yaml -d build/ # write instances to build/
latform-template instances.yaml -d build/ --dry-run # list files without writing
See Templating for the instances.yaml schema
(prefix / suffix / regex / parts renames, shared context: files, and per-instance
tao_init: generation).
Configuration
latform and latform-lint read project-wide settings from a latform.toml
(bare tables) or a [tool.latform] section in pyproject.toml. Discovery walks
upward from the current directory: the first latform.toml wins, otherwise the
first pyproject.toml that contains a [tool.latform] table. --config PATH
forces a specific file; --no-config skips config entirely.
Precedence is CLI flag → config → built-in default. Only latform and
latform-lint read this file (latform-apply / latform-template take their
namelist options as flags). The block below lists every recognized key with its
default; unknown keys in [format] warn and are ignored.
# latform.toml — all options shown with their defaults.
# ── Entry points ────────────────────────────────────────────────────────────
# Used when latform / latform-lint run with NO file arguments. Paths are
# relative to this config file. Providing either implies recursive parsing.
top-level = ["lat/main.bmad"] # list of lattice files
tao-init = "tao.init" # a path, or a list of paths; its
# design_lattice files become the top level
# ── Formatting ──────────────────────────────────────────────────────────────
# Same names as the CLI flags, minus the leading `--`. An explicit CLI flag
# always overrides the value here.
[format]
line-length = 100 # int
max-line-length = 130 # int; default = 130% of line-length
compact = false # bool; no blank lines between statement types
name-case = "upper" # upper | lower | same (element names)
attribute-case = "lower" # upper | lower | same (attribute names)
kind-case = "lower" # upper | lower | same (element types/keywords)
builtin-case = "lower" # upper | lower | same (builtin functions)
controller-variable-case = "same" # upper | lower | same (overlay/group/ramper vars)
section-break-character = "-" # str
section-break-width = 100 # int; default = line-length
strip-comments = false # bool
# Namelist (tao.init / *.nml) formatting — also under [format]:
format-namelist = true # bool; false = preserve layout verbatim
namelist-indent = 2 # int >= 0; field indent width
namelist-field-case = "lower" # upper | lower | same (field names)
namelist-align-equals = true # bool; align `=` into a column
namelist-align-comments = true # bool; align trailing `!` comments
namelist-logicals = ["T", "F"] # [true, false] tokens, or `false` to
# leave logical values untouched
# ── Linting ─────────────────────────────────────────────────────────────────
[lint]
ignore = ["LF002"] # list of codes to suppress everywhere
min-name-length = 1 # int >= 1; LF009 flags names shorter than this
# (at 1, only the confusable i/l/o are flagged)
builtin-constant-rtol = 1e-4 # positive float; LF010 match tolerance vs.
# built-in physical constants
# Per-file lint suppression. Keys are globs matched against the file's name,
# full path, and path relative to this config; values are codes to ignore there.
[lint.per-file-ignores]
"legacy/*.bmad" = ["LF004", "LF006"]
"vendor/**/*.bmad" = ["LF004"]
Lint ignores are cumulative: --ignore on the CLI, the global [lint] ignore
list, and any matching [lint.per-file-ignores] entries all apply.
The equivalent in pyproject.toml nests everything one level deeper under
[tool.latform]:
[tool.latform]
top-level = ["lat/main.bmad"]
[tool.latform.format]
line-length = 100
name-case = "upper"
kind-case = "lower"
[tool.latform.lint]
ignore = ["LF002"]
[tool.latform.lint.per-file-ignores]
"legacy/*.bmad" = ["LF004", "LF006"]
With no config at all, the tools fall back to a tao.init found in the current
directory or a parent, so they work in a bare Tao project directory. See the
Lint Codes reference
for what each LF0xx means.
Editor integration (language server)
latform ships a Language Server Protocol implementation, latform-lsp, for Bmad
lattice files. It provides go-to-definition, find-references, hover, completion,
rename, formatting (whole-file and range), document/workspace symbols, semantic
highlighting, code actions, and live diagnostics from the same lint rules as
latform-lint. It reads your project's latform.toml / pyproject.toml, so
formatting and diagnostics match the CLI.
The server is behind the optional lsp extra (it needs pygls):
pip install "latform[lsp]"
latform-lsp --help # runs over stdio; normally launched by your editor
Editor plugins
Ready-made clients that discover latform-lsp and wire up the Bmad filetype for
you:
| Editor | Plugin |
|---|---|
| VS Code | ken-lauer/latform-vscode |
| Vim | ken-lauer/vim-latform |
| Neovim | ken-lauer/latform-lsp.nvim |
Each still needs latform-lsp on PATH (pip install "latform[lsp]"); see the
plugin's README for install and configuration.
Manual LSP Configuration
Manual setup
Any LSP client works — point it at the latform-lsp command over stdio,
associated with your Bmad files (*.bmad, *.lat, tao.init). Without one of
the plugins above:
Neovim — built-in LSP client (0.11+):
vim.filetype.add({
extension = { bmad = "bmad", lat = "bmad" },
filename = { ["tao.init"] = "bmad" },
})
vim.lsp.config("latform", {
cmd = { "latform-lsp" },
filetypes = { "bmad" },
root_markers = { "latform.toml", "pyproject.toml", "tao.init", ".git" },
})
vim.lsp.enable("latform")
Vim — with vim-lsp:
augroup latform_ft
autocmd!
autocmd BufRead,BufNewFile *.bmad,*.lat,tao.init setfiletype bmad
augroup END
if executable('latform-lsp')
autocmd User lsp_setup call lsp#register_server({
\ 'name': 'latform',
\ 'cmd': {server_info->['latform-lsp']},
\ 'allowlist': ['bmad'],
\ })
endif
The log level and log file can be passed through any client (--log-level,
--log-file, or the LATFORM_LSP_LOG_LEVEL / LATFORM_LSP_LOG_FILE environment
variables). For other editors, see the LSP project's
list of client implementations.
Using latform as a library
from latform import parse, parse_file, parse_file_recursive
result = parse_file("my_lattice.bmad")
See the Python API docs for details.
Documentation
- CLI reference — full usage for every command
- Configuration —
latform.toml/pyproject.toml - Style guide — the formatting rules latform applies
- Python API — using latform programmatically
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file latform-0.3.1.tar.gz.
File metadata
- Download URL: latform-0.3.1.tar.gz
- Upload date:
- Size: 308.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
873972cf39333a6f24ed13853d58aa0e9bf008c3731dcdd48cdb19ea4599f6e8
|
|
| MD5 |
9989b960a8b46f95e81ef6625bba04c0
|
|
| BLAKE2b-256 |
aa97efa632f19034aa7945dd9e996623b6b97b492e8b8309f397bd54fd649861
|
Provenance
The following attestation bundles were made for latform-0.3.1.tar.gz:
Publisher:
pypi.yml on ken-lauer/latform
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
latform-0.3.1.tar.gz -
Subject digest:
873972cf39333a6f24ed13853d58aa0e9bf008c3731dcdd48cdb19ea4599f6e8 - Sigstore transparency entry: 2556127573
- Sigstore integration time:
-
Permalink:
ken-lauer/latform@45151b7c36c595e45bbaba18c16147378c8689cd -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/ken-lauer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@45151b7c36c595e45bbaba18c16147378c8689cd -
Trigger Event:
push
-
Statement type:
File details
Details for the file latform-0.3.1-py3-none-any.whl.
File metadata
- Download URL: latform-0.3.1-py3-none-any.whl
- Upload date:
- Size: 290.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8389aeac6b511abb00dc30cab46dc9ae4b8c346eadc0e53ae8b152cbfad52d1
|
|
| MD5 |
a32edbb513bb25801047f081bb47fd0f
|
|
| BLAKE2b-256 |
c97a4d57fd31ff4c01842d7324af40709926f9bd7990cd6a4938fee5156d1b22
|
Provenance
The following attestation bundles were made for latform-0.3.1-py3-none-any.whl:
Publisher:
pypi.yml on ken-lauer/latform
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
latform-0.3.1-py3-none-any.whl -
Subject digest:
d8389aeac6b511abb00dc30cab46dc9ae4b8c346eadc0e53ae8b152cbfad52d1 - Sigstore transparency entry: 2556127699
- Sigstore integration time:
-
Permalink:
ken-lauer/latform@45151b7c36c595e45bbaba18c16147378c8689cd -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/ken-lauer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@45151b7c36c595e45bbaba18c16147378c8689cd -
Trigger Event:
push
-
Statement type: