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.10.29

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.10.29
File Size Uploaded Provenance Actions
exodusii-26.10.29.tar.gz 743.4 kB Without provenance Details for exodusii-26.10.29.tar.gz

Built distribution (wheel)

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

Total release size: 1.0 MB

Release files / exodusii-26.10.29.tar.gz

Download URL exodusii-26.10.29.tar.gz
Size 743.4 kB
Tags Source
SHA-256 checksum
How to use checksums
fd702d653149603173349489c8354fb6493b1d97666890218ba99738d18bb269
BLAKE2b-256 checksum
How to use checksums
07f8e2bcab8bd3cc6d3893f77973a11d3f65b0c2c02e3cf30a0ebf38c562bc4e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.12

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

Download URL exodusii-26.10.29-py3-none-any.whl
Size 291.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
47aff97344b22e9c5ab2da4d6a5626f1884379fe0a128975045fe3541bed7b7b
BLAKE2b-256 checksum
How to use checksums
55ec4cdd75f2cc8ad5e07e61f02762d450f6baed248e9e59a13b000d204d0be6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.12

Security for exodusii 26.10.29

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.10.29 This release

2 release files No 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