Skip to main content

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

Metadata

Release files for latform 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for latform 0.3.2
File Size Uploaded
latform-0.3.2.tar.gz 310.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for latform 0.3.2
File Interpreter ABI Platform
latform-0.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 602.6 kB

Release files / latform-0.3.2.tar.gz

Download URL latform-0.3.2.tar.gz
Size 310.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7755bd3eb03b62c7e1549951dd24697872290e0ab5cfc5f1772b1b1c154abd85
BLAKE2b-256 checksum
How to use checksums
fc646dd10bd4ba742271e012c82baf0a7ef9f578a999af2ac65dfbbcc06c5378
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release files / latform-0.3.2-py3-none-any.whl

Download URL latform-0.3.2-py3-none-any.whl
Size 292.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8ff0540e084c47f4711e2c685012e40af5f0e51af587cc2ba42d03f4e952671c
BLAKE2b-256 checksum
How to use checksums
667ee3a8318537304005dfc7e05f3cad78fc98d29dffe7a569ed567567209898
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.2 This release

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page