Skip to main content

exodusii

A Python interface for reading, writing, comparing, querying, and spatially reducing Exodus II finite-element databases.

Requires Python ≥ 3.13 and netCDF4.


Installation

python -m pip install -e .

Run tests:

cd /path/to/exodusii
pytest tests/api/ tests/mesh/ tests/core/ tests/cli/ tests/io/ tests/compat/ tests/test_attributes.py

Reading a database

from exodusii import ExodusFile

with ExodusFile.open("results.exo") as exo:
    print(exo.title)
    print(exo.dimension)  # 1, 2, or 3
    print(exo.node_count)
    print(exo.element_count)
    print(exo.times())

    coords = exo.coordinates()  # (n_nodes, dim)
    temp = exo.values("TEMP", on="node", time="last")
    energy = exo.values("ENERGY", on="element", block_id=1, time="last")
    ke = exo.values("KE", on="global")  # full time history

    print(exo.element_block_ids())
    print(exo.variable_names("node"))
    print(exo.variable_names("element"))

Time selectors: "first", "last", an int (0-based index), or a float (nearest physical time).


Writing a database

import numpy as np
from exodusii import ExodusWriter

coords = np.array([[0, 0], [1, 0], [1, 1], [0, 1]], dtype=float)

with ExodusWriter.create("out.exo") as w:
    # Declare all counts upfront — this cannot change after initialize()
    w.initialize("my mesh", dimension=2, node_count=4, element_count=1, element_blocks=1)
    w.write_coordinates(coords)
    w.define_element_block(10, "quad", [[1, 2, 3, 4]])

    w.define_global_variables(["TOTAL_ENERGY"])
    w.define_node_variables(["TEMP"])
    w.define_element_variables(["STRESS"])

    w.write_time(0.0)
    w.write_global_values([0.0])
    w.write_node_values("TEMP", [100.0, 200.0, 300.0, 400.0])
    w.write_element_values("STRESS", [42.0], block_id=10)

Parallel / decomposed Exodus files

from exodusii import ParallelExodusFile

with ParallelExodusFile.open("mesh.e.4.0", "mesh.e.4.1", "mesh.e.4.2", "mesh.e.4.3") as exo:
    print(exo.node_count)  # global node count
    coords = exo.coordinates()  # assembled from all pieces
    temp = exo.values("TEMP", on="node", time="last")

# Join all pieces into a single serial file
with ParallelExodusFile.open(*files) as exo:
    exo.write("joined.exo")

Aggregation uses the per-file node_num_map / elem_num_map. If a map is absent a UserWarning is emitted and a sequential fallback is used (only correct for non-overlapping partitions).

Fast global reads from decomposed files

Global scalar variables are fully present in every component file. Use the --piece CLI flag to read one small piece directly and avoid opening the full joined (potentially multi-GB) database:

python -m exodusii stats  run.exo.96.00 --select g/MAT_MASS_1 --piece 0
python -m exodusii query  run.exo.96.00 --select g/MASSDELETED --time last --piece 0

Region-masked statistics and mass

Compute statistics of a field variable restricted to a geometric region, with an optional field-threshold predicate:

from exodusii import ExodusFile
from exodusii.mesh import Cylinder

with ExodusFile.open("run.exo") as exo:
    cyl = Cylinder([0, 0, 0], [0.05, 0, 0], radius=0.013)

    # Field statistics inside a cylinder, filtered by a field predicate
    r = exo.region_stats(
        "YIELD_STRESS_2",
        block_id=3,
        region=cyl,
        where="EQPS_2 > 1.0",  # VARNAME OP VALUE only
        reduce=["mean", "max", "count"],
        time="last",
        symmetry_factor=4.0,  # applied to sum/count; NOT mean/max
    )
    print(r.stats["mean"], r.count_selected)

    # Material mass inside a region
    result = exo.region_mass(
        block_id=3,
        region=cyl,
        density_name="DENSITY",
        volfrac_name="VOLFRC_2",  # optional volume fraction
        time="last",
        symmetry_factor=4.0,
    )
    print(result.mass)  # = 4 * sum(|vol| * density * volfrac)

Available reducers: mean, max, min, sum, count, std. Symmetry factor scales only the extensive reducers (sum, count, mass).

CLI:

python -m exodusii region-stats run.exo \
  --select e/YIELD_STRESS_2 \
  --cylinder 0 0 0 0.05 0 0 0.013 \
  --where "EQPS_2 > 1.0" \
  --reduce mean,max,count \
  --time last --block 3 --symmetry 4

Region flags: --cylinder, --sphere, --circle, --rectangle.


Exodiff-style comparison

from exodusii import diff, DiffOptions, Tolerance, ToleranceMode

result = diff("gold.exo", "test.exo")
if not result:
    for vd in result.variable_diffs:
        print(vd.entity, vd.name, vd.max_delta)

# Custom tolerances
opts = DiffOptions(
    default_tolerance=Tolerance(ToleranceMode.ABSOLUTE, 1e-8),
    variable_tolerances={"PRESSURE": Tolerance(ToleranceMode.COMBINED, 1e-5)},
    exclude=frozenset({"TIME"}),
)
result = diff("gold.exo", "test.exo", opts)

# Coordinate-based mesh matching (different node/element ordering)
opts = DiffOptions(coordinate_matching=True, matching_tolerance=1e-8)
result = diff("gold.exo", "reordered.exo", opts)

Tolerance modes mirror SEACAS exodiff: relative, absolute, combined, ignore, eigenrel, eigenabs, eigencom, ulps_float, ulps_double.

TimeSelection controls which steps are compared and whether file-2 values are interpolated to file-1 time points.

python -m exodusii diff gold.exo test.exo
python -m exodusii diff --absolute -t 1e-8 gold.exo test.exo
python -m exodusii diff --start LAST gold.exo test.exo
python -m exodusii diff --match-coordinates gold.exo reordered.exo
python -m exodusii diff --format json --terse gold.exo test.exo

Exit codes: 0 same, 1 error, 2 different.

Command (control) files

A diff can be driven by a command file on disk. Two formats are supported; exodusii.read_command_file() (and the CLI -f/--command-file) auto-detect which one a file uses. Explicit CLI flags override values from the file.

YAML (preferred, modern)

A structured, self-documenting format with full feature parity with the SEACAS exodiff command file:

# opts.yaml
tolerances:
  default:    {mode: relative, value: 1.0e-6, floor: 1.0e-12}
  coordinate: {mode: absolute, value: 1.0e-8}
  time:       {mode: relative, value: 1.0e-6, floor: 1.0e-15}
  categories:                         # per-category default tolerances
    nodal:  {mode: absolute, value: 1.0e-7}
  variables:                          # per-variable overrides (highest priority)
    DISPLX: {mode: relative, value: 1.0e-9}
variables:
  ignore_case: true
  exclude: [VELZ]
  include:                            # per-category include lists
    nodal:  [DISPLX, DISPLY]
    global: all                       # 'all' => compare all variables
coordinates:  {compare: true}
attributes:   {compare: true}
mesh_matching: {enabled: false, tolerance: 1.0e-6, require_unique: true}
time:
  start: 1
  stop: -1
  increment: 1
  step_offset: 0
  exclude_steps: []
  interpolate: false
report: {show_all: false}

Any tolerance may be an explicit mapping, a shorthand string ("absolute 1e-8" / "1e-6"), or a bare number.

python -m exodusii diff -f opts.yaml gold.exo test.exo
import exodusii

res = exodusii.read_command_file("opts.yaml")  # -> CommandFileResult
result = exodusii.diff("gold.exo", "test.exo", res.options)

Emit a YAML command file from a DiffOptions (full/explicit; round-trips):

opts = exodusii.DiffOptions(...)
opts.to_yaml("opts.yaml")  # write to a file
print(opts.to_yaml())  # or get the string
# Emit the effective options (command file + CLI flags) and exit, no compare
python -m exodusii diff --absolute -t 1e-8 --emit-options opts.yaml gold.exo test.exo
python -m exodusii diff --emit-options - gold.exo test.exo   # to stdout

SEACAS exodiff text (legacy, backward compatible)

The original whitespace-delimited exodiff command-file grammar is also accepted (auto-detected):

# cmds.txt  (SEACAS exodiff command-file grammar)
DEFAULT TOLERANCE relative 1e-6 floor 1e-12
COORDINATES absolute 1e-8
NODAL VARIABLES absolute 1e-7
	DISPLX relative 1e-9      # per-variable override
	!VELZ                     # exclude
GLOBAL VARIABLES (all) relative 1e-4
python -m exodusii diff -f cmds.txt gold.exo test.exo

Directives: DEFAULT TOLERANCE, COORDINATES, TIME STEPS, FINAL TIME TOLERANCE, per-category <GLOBAL|NODAL|ELEMENT|NODESET|SIDESET|EDGEBLOCK| FACEBLOCK> VARIABLES blocks (with (all), per-variable tolerances, and !name exclusions), ELEMENT ATTRIBUTES, STEP OFFSET, EXCLUDE TIMES, INTERPOLATE, APPLY MATCHING/NODESET MATCH/SIDESET MATCH, and IGNORE CASE/CASE SENSITIVE. Keywords are case-insensitive and abbreviable. A category block that lists specific names (without (all)) restricts the comparison to those names for that category. Accepted-but-inert directives (e.g. PEDANTIC, CALCULATE NORMS) are reported via CommandFileResult.warnings.

The reader classes (YamlCommandFileReader, ExodiffCommandFileReader) share the abstract CommandFileReader base; command_file_reader(path) is the format-detecting factory (YAML preferred).


Query and stats CLI

# Inspect a database
python -m exodusii inspect  mesh.exo
python -m exodusii variables mesh.exo
python -m exodusii blocks   mesh.exo
python -m exodusii times    mesh.exo

# Query result variables (JSON output)
python -m exodusii query mesh.exo --select g/TOTAL_ENERGY
python -m exodusii query mesh.exo --select n/TEMP --time last --limit 10
python -m exodusii query mesh.exo --select e/ENERGY --time step:5

# Compact numeric summaries
python -m exodusii stats mesh.exo --select n/TEMP --time last
python -m exodusii stats mesh.exo --select e/ENERGY --time last --by-block

Variable selector format: ENTITY/NAME — e.g. g/TOTAL_ENERGY, n/TEMP, e/ENERGY, ns/FLUX, ss/PRESSURE.


Python query API

from exodusii import ExodusFile, query, print_query

with ExodusFile.open("mesh.exo") as exo:
    result = query(exo, "n/TEMP", "e/ENERGY", time="last")
    print(result.names)
    print(result.data[:5])
    print_query(exo, "g/TM_STEP")

Mesh geometry helpers

from exodusii.mesh import entity_centers, element_volumes, bounding_box
from exodusii.mesh import Cylinder, Sphere, Circle, Rectangle

with ExodusFile.open("run.exo") as exo:
    coords = exo.coordinates()
    conn = exo.element_connectivity(block_id=1, zero_based=True)

# Element centroids — feed to region.contains() to select elements
centers = entity_centers(conn, coords)

# Per-element volumes/areas
vols = element_volumes("hex8", conn, coords)

# Bounding box
lo, hi = bounding_box(coords)

# Region predicates
cyl = Cylinder([0, 0, 0], [0.05, 0, 0], radius=0.013)
mask = cyl.contains(centers)  # boolean ndarray

Supported element types: quad4, hex8, tri3, tet4, wedge6.


Agent CLI (self-learning)

# Static capability reference (no file required)
python -m exodusii learn capabilities overview
python -m exodusii learn capabilities region_reduce
python -m exodusii learn capabilities python_api.writer

# Skills (self-contained workflow guides)
python -m exodusii learn skills list
python -m exodusii learn skills exodusii-region-reduce
python -m exodusii learn skills exodusii-geometry

Package layout

exodusii/
├── api/             modern API (ExodusFile, ExodusWriter, ParallelExodusFile,
│                    diff, query, region_reduce, lineout, copy)
├── core/            domain model (Entity, Block, Tolerance, errors, schema)
├── io/              NetCDF backend (Protocol + netCDF4 implementation)
├── mesh/            geometry (elements, regions, entity_centers, element_volumes)
├── compat/          legacy ExodusIIFile / put_*/get_* API
├── cli/             JSON-oriented CLI (main.py dispatch + one file per subcommand, exodiff.py, exoread, learn)
└── data/            capabilities.json, skills.json

Legacy API (compatibility)

The historical put_* / get_* API from the original exodusii library is preserved for downstream code that has not yet migrated:

import exodusii

# Read
with exodusii.File("mesh.exo") as exo:
    times = exo.get_times()
    temp = exo.get_node_variable_values("TEMP", time_step=-1)

# Write
with exodusii.File("out.exo", mode="w") as exo:
    exo.put_init("title", 2, 4, 1, 1, 0, 0)
    exo.put_coords(coords)
    exo.put_element_block(10, "quad", 1, 4)
    exo.put_element_conn(10, [[1, 2, 3, 4]])

# Globals only
exodusii.write_globals({"energy": values}, times, filename="globals.exo")

New code should use the modern API (ExodusFile, ExodusWriter, ParallelExodusFile). The compatibility layer is fully tested and maintained but will not receive new features.

Metadata

Release files for exodusii 26.11.7

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

Source distribution (sdist)

Source distribution for exodusii 26.11.7
File Size Uploaded Provenance Actions
exodusii-26.11.7.tar.gz 744.0 kB With provenance Details for exodusii-26.11.7.tar.gz

Built distribution (wheel)

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

Total release size: 1.0 MB

Release files / exodusii-26.11.7.tar.gz

Download URL exodusii-26.11.7.tar.gz
Size 744.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8b409261d8d004fc660e87b262a1b8c4d75f2c8e94bfdee9ca6b97208152a916
BLAKE2b-256 checksum
How to use checksums
3e3380ee9676e43fe9988f5c2b40d372e615a9c7ebedf20d173eea9a20c9f2c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 7, 2026.

Transparency log

Release files / exodusii-26.11.7-py3-none-any.whl

Download URL exodusii-26.11.7-py3-none-any.whl
Size 291.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6a79ab5dc8738952e886e791cc0e98ce08297cc484b031f8d58dff21d0025ea1
BLAKE2b-256 checksum
How to use checksums
a08a1891a4e9b131ec419347207975c0db2aa508fbcb7bf4a45792a42aaeae4a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 7, 2026.

Transparency log

Security for exodusii 26.11.7

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.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for exodusii-26.11.7.tar.gz

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 Nov 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

26.11.7 This release

2 release files Full provenance

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