Skip to content

Codebase Structure

This page maps the src/jaff source tree, explains what each package owns, and shows how data flows through the library from a raw network file to generated code.

Package Map

src/jaff/
├── core/                       # Domain model
│   ├── network/                # network.py (Network — main entry point)
│   │   ├── _spec.py            # NetworkSpec — normalized Network params
│   │   └── _args.py            # NetworkArgs — raw CLI arg accumulator
│   ├── reaction/               # reaction.py (Reaction) · reactions.py (Reactions)
│   ├── species/                # specie.py (Specie) · species.py (Species)
│   ├── elements/               # element.py (Element) · elements.py (Elements)
│   ├── parsers/                # File parsers (network + auxiliary)
│   │   ├── network/            # Multi-format network file parser
│   │   │   ├── _engine.py      # NetworkParser — drives format plugins
│   │   │   ├── _typing/        # parsedListProps, krome/prizmoFormatProps
│   │   │   └── _formats/       # One subpackage per format (plugins)
│   │   │       ├── _base.py    # NetworkFormat ABC (plugin contract)
│   │   │       ├── _context.py # ParseContext — shared per-parse state
│   │   │       ├── __init__.py # register / all_formats / build_state
│   │   │       ├── krome/      # header.py · var.py · reaction.py
│   │   │       ├── prizmo/     # vars.py · reaction.py
│   │   │       ├── udfa/       # reaction.py
│   │   │       ├── uclchem/    # reaction.py
│   │   │       └── kida/       # reaction.py
│   │   └── auxiliary_func/     # .jfunc auxiliary function parser
│   │       ├── _engine.py      # AuxiliaryFunctionParser
│   │       └── _typing/        # AuxiliaryFunctionsDict
│   └── _typing/                # Shared core TypedDicts (Network/Element/Reaction)
├── physics/                    # Symbolic ODE/flux generation + physics helpers
│   ├── _equations.py           # get_sfluxes, get_sodes, get_sradodes
│   ├── photo_reactions/        # Photochemistry: cross sections, radiation, shielding
│   │   ├── _photochemistry.py  # get_xsec / get_verner_xsec / shielding — lookups
│   │   ├── _radiation.py       # Radiation moment equations
│   │   ├── _typing/            # TypedDicts (XsecsProps, ...)
│   │   └── shielding/          # Shielding-function registry (@_register, by reaction metadata)
│   │       ├── _base.py        # ShieldingFunction ABC (name, reaction attrs)
│   │       ├── global_/        # Global models, reaction=None (e.g. leiden.py)
│   │       └── H2__PHOTON__H_H/ # Local H2 self-shielding (db1996, hg2015) + shared _utils
│   ├── _typing/                # TypedDicts (Numeric, ...)
│   └── constants.py            # Physical constants (astropy Quantities)
├── plotting/                   # Publication-style seaborn plotting
│   ├── _api.py                 # plot_rates / plot_xsecs — free functions (reactions, exprs, arrays)
│   ├── plotter.py              # Plotter.render_series — seaborn-objects renderer
│   ├── _theme.py               # seaborn theme, palettes, scoped/global application
│   ├── _frames.py              # tidy DataFrame builders
│   ├── _units.py               # energy/xsec unit conversion + axis labels
│   └── _xsec.py                # trim / dynamic-scale helpers
├── codegen/                    # Code generation pipeline
│   ├── codegen.py              # SymPy → C/C++/Fortran/Python/Rust/Julia/R
│   ├── preprocessor.py         # Template marker substitution
│   ├── builder.py              # Plugin-based orchestration
│   └── _template_engine.py     # JAFF directive rendering
├── io/                         # Serialization and logging
│   ├── _io.py                  # .jaff gzip-JSON read/write; data table export
│   └── _logger.py              # JaffLogger + progress bars
├── config/                     # Package-wide path constants
│   └── _config.py              # SRC_DIR, DATA_DIR, XSECS/SHIELDING dirs, ...
├── drivers/                    # Config / data format adapters
│   ├── toml.py                 # TOML config reader
│   ├── csv.py                  # CSV I/O
│   ├── hdf5.py                 # HDF5 I/O
│   ├── sqlite.py               # SQLite I/O
│   └── pooch.py                # Download/cache remote cross-section data files
├── cli/                        # Command-line entry points (Typer)
│   ├── _helper.py              # Shared argument helpers (funcfile_arg)
│   ├── jaffgen/                # jaffgen — template-driven code generation
│   │   ├── _engine.py          # JaffGen pipeline + Typer `generate` command
│   │   ├── _structs.py         # State / ResolvedPath
│   │   └── _config_table.py    # [[table]] config → HDF5/CSV output
│   └── jaffx/                  # jaffx — network inspection / export
│       └── _engine.py          # JaffX handlers + nested Typer commands
├── plugins/                    # Named solver plugins
│   ├── python_solve_ivp/       # SciPy solve_ivp wrapper
│   ├── fortran_dlsodes/        # Fortran DLSODES solver
│   ├── kokkos_ode/             # Kokkos GPU ODE solver
│   └── microphysics/           # AMReX microphysics driver
├── templates/                  # Source templates consumed by plugins
│   ├── generator/<name>/       # JAFF directive template files
│   └── preprocessor/<name>/    # Marker substitution templates
├── types/                      # Base data structures
│   ├── _catalogue.py           # Catalogue[T] — O(1) list + dict lookup
│   ├── _vector.py              # Typed numeric container
│   ├── _indexed.py             # IndexedList / IndexedValue
│   └── _hdf5.py                # HDF5 type helpers
├── common/                     # Shared utilities
│   ├── _helper.py              # Element/mass table loading
│   ├── _integrators.py         # Dependency resolution (DFS)
│   ├── _sympy_json.py          # Versioned SymPy ↔ JSON encoding
│   ├── _fastlog.py             # Fast structured logging
│   └── _welcome.py             # MOTD / version banner
├── errors/
│   └── _parser.py              # ParserError hierarchy
├── data/                       # Raw data assets
│   ├── atom_mass.csv           # Element mass table (bundled)
│   ├── xsecs/                  # Photo cross-section data (downloaded via drivers/pooch.py, not bundled)
│   │   ├── leiden.hdf5         # Leiden PDR cross sections (one group per reaction)
│   │   ├── norad.hdf5          # NORAD/OP ground-state photoionisation
│   │   └── verner_1996.csv     # Verner (1996) analytic-fit parameters
│   └── shielding/              # Line-shielding tables (downloaded via drivers/pooch.py, not bundled)
│       └── leiden.hdf5         # Leiden line shielding (one group per reaction)
├── db/                         # Prebuilt SQLite database
│   └── jaff.db                 # Mass + photo cross-section (Leiden/NORAD + Verner) tables, built from data/
└── _utils/                     # Standalone maintenance scripts
    ├── generate_mass_table.py          # Build mass tables in jaff.db from data/atom_mass.csv
    ├── download_nahar_xsecs.py         # Download NORAD/OP ground-state photoionisation .dat files
    ├── collapse_xsecs_hdf5.py          # Merge per-reaction files into leiden.hdf5 / norad.hdf5
    ├── split_xsecs_photodecay.py       # Split source diss/ion datasets into the photodecay channel
    ├── generate_photo_xsecs_table.py   # Build photo_reaction_cross_sections table in jaff.db
    ├── generate_ion_xsecs_table.py     # Build verner_cross_sections table in jaff.db
    └── build_shielding_hdf5.py         # Collapse Leiden shielding tables into shielding/leiden.hdf5

Architecture Diagram

%%{init: {"flowchart": {"useMaxWidth": false}}}%%
flowchart TD
    subgraph input_sg ["Input"]
        NF["Network file\nKROME · PRIZMO · UDFA\nKIDA · UCLChem · .jaff"]
        JF[".jfunc\nauxiliary functions"]
        CFG["jaffgen.toml / CLI"]
    end

    subgraph parse_sg ["Parsing  —  core.parsers"]
        NE["NetworkParser\nauto-detect format\nformat plugins → dicts"]
        AE["AuxiliaryParser\n@var / @function\nSymPy expressions"]
    end

    subgraph model_sg ["Domain Model  —  core"]
        NET["Network\nassemble · validate\nSpecies · Reactions · Elements"]
    end

    subgraph codegen_sg ["Code Generation"]
        EQ["physics\nsfluxes · sodes · sradodes"]
        CG["Codegen\nSymPy → C · C++ · F90\nPy · Rust · Julia · R"]
        TP["TemplateParser  —  jaffgen path\ntemplates/generator/\nSUB · REPEAT · REDUCE directives"]
        OUT_G["Generated output files"]
        PP["Preprocessor  —  builder path\ntemplates/preprocessor/\n!! KEY marker substitution"]
        BL["Builder\nplugin dispatch"]
        OUT_B["Plugin output files"]
    end

    NF --> NE
    JF --> AE
    CFG --> NE

    NE --> NET
    AE --> NET

    NET --> EQ --> CG

    CG --> TP --> OUT_G
    CG --> PP --> BL --> OUT_B

Data Flow — End to End

The table below traces a single jaffgen invocation from command line to output files.

Step Component What happens
1 cli/jaffgen/_engine.py Parse CLI args (Typer), read jaffgen.toml, resolve config: CLI > jaffgen.toml > Network defaults
2 core/parsers/network/_engine.py Auto-detect format via registered plugins; convert each reaction line to a parsedListProps dict
3 core/parsers/auxiliary_func/_engine.py Parse .jfunc file (if present); resolve @var/@function blocks into SymPy expressions
4 core/network/network.py Build Species, Reactions, Elements catalogues; validate duplicates, sinks, isomers
5 physics/_equations.py Compute symbolic fluxes (sfluxes) and ODE RHS (sodes) using SymPy
6 codegen/codegen.py Translate SymPy expressions into assignment strings for the chosen language
7 codegen/preprocessor.py Walk template files; replace !! PREPROCESS_KEY … !! PREPROCESS_END blocks with generated strings
8 codegen/builder.py Invoke the named plugin's main() to write final output files to the build directory

Key Design Decisions

Plugin-based, format-agnostic parser. Each network format is a NetworkFormat subclass living in its own subpackage under core/parsers/network/_formats/. A class registers itself with the @register decorator; NetworkParser discovers all formats via all_formats(), ordered by each format's priority (not file or import order). Every format exposes a fast _global_re filter and a detailed _local_re extractor, and writes results through a shared ParseContext. Adding a new format means adding one subpackage — no edits to the engine or shared code. See Adding a Parser.

SymPy as the intermediate representation. All rate expressions, fluxes, and ODEs live as SymPy objects inside Network. Code generation (Codegen) calls SymPy's language-specific printers (ccode, cxxcode, fcode, etc.), so adding a new target language is a single Language subclass in jaff/codegen/_languages.py.

Plugin-based code generation. Builder discovers plugins at jaff.plugins.<name>.plugin and calls their main(). Each plugin owns its template files and knows nothing about the parser. This keeps solver-specific logic out of the core library.

Catalogue[T] for all domain collections. Species, Reactions, and Elements all inherit from Catalogue, giving O(1) lookup by integer index, slice, string name, and serialized canonical name. The serialized form (e.g. "+/H/H/O" for H₂O⁺) enables duplicate detection that is independent of input name formatting.

.jaff binary format. Networks can be saved as gzip-compressed JSON (.jaff files) via io/_io.py. On load, SymPy expressions are reconstructed from the versioned compact encoding in common/_sympy_json.py. This avoids re-parsing large networks on repeated runs.

Utility Scripts

src/jaff/_utils/ holds standalone, easy-to-run scripts for maintaining the bundled data. They are not part of the runtime data flow — they are run by hand (or during maintenance) to regenerate the assets in data/ and db/jaff.db.

The cross-section scripts are ordered as a pipeline: download raw NORAD data, collapse the per-reaction files into combined HDF5 files, then build the SQLite lookup tables that JAFF queries at runtime.

Script Purpose
generate_mass_table.py Read data/atom_mass.csv and (re)build the element mass tables inside db/jaff.db.
download_nahar_xsecs.py Download NORAD/OP (Nahar, OSU) ground-state photoionisation cross sections (Z = 1..26) into data/xsecs/op/ using serialized reaction names.
collapse_xsecs_hdf5.py Merge the per-reaction Leiden and NORAD files into combined leiden.hdf5 / norad.hdf5 (one group per reaction, photon energy in eV, σ in cm²).
split_xsecs_photodecay.py Split the source dissociation/ionisation datasets into the single photodecay channel used by the collapsed HDF5 files.
generate_photo_xsecs_table.py Build the photo_reaction_cross_sections table in db/jaff.db from the collapsed HDF5 files (photo_absorption flag, decay_type + file.hdf5::<group> pointers).
generate_ion_xsecs_table.py Build the verner_cross_sections table in db/jaff.db from the Verner (1996) analytic-fit parameters in data/xsecs/verner_1996.csv.
build_shielding_hdf5.py Collapse the per-species Leiden line-shielding tables into data/shielding/leiden.hdf5 (one group per reaction).

Run a script as a module from the project root, e.g.:

python -m jaff._utils.generate_mass_table
uv run python -m jaff._utils.generate_mass_table