User Guide

Data model overview

ANDALUS is built around a small set of composable data classes that mirror the components of the GLLS workflow. Each item class has a corresponding suite class that aggregates multiple items and exposes combined pd.Series/pd.DataFrame properties.

Item class

Suite class

Holds

Benchmark

BenchmarkSuite

Experiment with measurement \(m\), uncertainty \(\sigma_m\), calculated value \(c\), and sensitivity \(S\)

Application

ApplicationSuite

System of interest — same shape as Benchmark but \(m\) is optional

Covariance

CovarianceSuite

Nuclear data covariance for one or more nuclides

AssimilationSuite

Orchestrator that holds all three suites and runs GLLS


Sensitivity

Sensitivity is a pd.DataFrame subclass with a four-level MultiIndex (ZAI, MT, E_min_eV, E_max_eV) and two columns: the sensitivity values and their standard deviations.

Loading from Serpent

from andalus import Sensitivity

s = Sensitivity.from_serpent(
    sens0_path="data/hmi001_sens0.m",
    title="HMI-001",
    kind="keff",
    zailist=[922350, 922380],   # optional subset of nuclides
    pertlist=[18, 102],         # optional subset of reactions (MT numbers)
)

Plotting

ax = s.plot_sensitivity(
    zais=[922350],
    perts=["total fission", "capture"],
)

Persistence

s.to_hdf5("sensitivities.h5")
s2 = Sensitivity.from_hdf5("sensitivities.h5", title="HMI-001")

Benchmark

Benchmark is a frozen dataclass. Once created it cannot be modified; use the suite-level update_c() method when you need to propagate new calculated values.

Fields

Field

Type

Description

title

str

Unique name

kind

str

Observable type (e.g. "keff")

m

float

Measured value

dm

float

Measurement uncertainty (\(1\sigma\))

c

float

Calculated value

dc

float

Calculation uncertainty (\(1\sigma\))

s

Sensitivity

Energy-group sensitivity profiles

flux

FluxSpectrum | None

Flux spectrum (optional)

Loading from Serpent

from andalus import Benchmark

b = Benchmark.from_serpent(
    title="HMI-001",
    m=1.0000,
    dm=0.0005,
    sens0_path="data/hmi001_sens0.m",
    results_path="data/hmi001_res.m",
)

Attach a flux spectrum by passing flux_det=(detector_name, det_path):

b = Benchmark.from_serpent(
    title="HMI-001",
    m=1.0000,
    dm=0.0005,
    sens0_path="data/hmi001_sens0.m",
    results_path="data/hmi001_res.m",
    flux_det=("FLUX", "data/hmi001_det0.m"),
)

Persistence

b.to_hdf5("benchmarks.h5")
b2 = Benchmark.from_hdf5("benchmarks.h5", title="HMI-001")

Application

Application has the same shape as Benchmark but m and dm are optional. Use it for any nuclear system whose response you want to predict (but may not have a measurement for).

from andalus import Application

app = Application.from_serpent(
    title="my-reactor",
    sens0_path="data/reactor_sens0.m",
    results_path="data/reactor_res.m",
    # m and dm are optional
)

Covariance

Covariance is a pd.DataFrame subclass that holds the multigroup cross-section covariance matrix for a single nuclide (ZAI). The index is a MultiIndex (MF, MT, E_min_eV, E_max_eV).

Loading from ERRORR files

NJOY ERRORR output files are read via SANDY:

from andalus import Covariance

cov = Covariance.from_errorr(
    files={"tape33": "data/u235.errorr"},
    zai=922350,
    mts=[18, 102],     # fission and capture
)

Persistence

cov.to_hdf5("covariances.h5")
cov2 = Covariance.from_hdf5("covariances.h5", zai=922350)

Diagnostics

print(cov.correlation())           # correlation matrix
print(cov.is_unrealistic_uncertainty(threshold=10))  # True if any σ > 1000 %

CovarianceSuite

CovarianceSuite wraps a block-diagonal covariance matrix that spans multiple nuclides and reactions. Its .matrix attribute is the global pd.DataFrame with a five-level MultiIndex (ZAI, MF, MT, E_min_eV, E_max_eV).

from andalus import CovarianceSuite

# Load from HDF5 (most common)
cov_suite = CovarianceSuite.from_hdf5(
    "covariances.h5",
    zais=[922350, 922380, 942390],
    mts=[2, 18, 102],
)

# Build from individual Covariance objects
from andalus import Covariance
cov_suite = CovarianceSuite.from_dict({
    922350: cov_u235,
    922380: cov_u238,
})

# Inspect uncertainties
print(cov_suite.get_uncertainties())

# Visualise uncertainty for U-235 capture (MT=102)
cov_suite.plot_uncertainty(zai=922350, mt=102)

AssimilationSuite

AssimilationSuite is the top-level orchestrator. It holds a BenchmarkSuite, an ApplicationSuite, and a CovarianceSuite, and exposes the full GLLS workflow.

Construction

from andalus import AssimilationSuite

suite = AssimilationSuite(
    benchmarks=benchmark_suite,
    applications=application_suite,
    covariances=covariance_suite,
)

The recommended approach for reproducible workflows is to use from_yaml():

suite = AssimilationSuite.from_yaml("assimilation.yaml")

Aggregated properties

All suite classes expose combined properties as pd.Series or pd.DataFrame:

suite.m     # measured values (benchmarks only)
suite.dm    # measurement uncertainties
suite.c     # calculated values (benchmarks + applications)
suite.dc    # calculation uncertainties
suite.s     # sensitivity matrix (rows = cases, cols = nuclear data)

Running GLLS

posterior = suite.glls()

Pass include_sensitivity_uncertainty=True to account for the statistical uncertainty on the sensitivity profiles themselves:

posterior = suite.glls(include_sensitivity_uncertainty=True)

Inspecting the posterior

posterior.summarize()

# Per-benchmark chi-squared
print(posterior.individual_chi_squared())

# Total chi-squared (with and without nuclear data term)
print(posterior.chi_squared(nuclear_data=False))
print(posterior.chi_squared(nuclear_data=True))

# Adjusted calculated values for applications
print(posterior.applications.c)

# Nuclear data adjustment vector
print(posterior.xs_adjustment)

Similarity analysis

The \(c_k\) matrix quantifies how similar two cases are in terms of their nuclear data sensitivity:

ck = suite.ck_matrix()   # square DataFrame, rows and cols = all case titles
print(ck)

# Similarity of all cases with a specific application
print(suite.ck_target("my-reactor"))

Uncertainty propagation

Propagate the prior nuclear data uncertainty to the calculated response vector:

uncertainty = suite.propagate_nuclear_data_uncertainty()
print(uncertainty)   # pd.Series, one value per case

Exporting to ACE

After running GLLS, export the adjusted nuclear data to ACE format for use in Serpent or other Monte Carlo codes (requires NJOY on PATH):

posterior.to_ace(
    library="jeff_40",
    temperature=300,
    create_xsdata=True,
    parallel=True,
)

Use only_zais_applications=True to limit the export to nuclides that actually appear in the application sensitivities.


Filtering benchmarks

The filter() method on suites accepts any callable Benchmark bool, including the built-in filter objects.

Built-in filters

Filter

Description

Chi2Filter(threshold)

Keeps benchmarks where \(\chi^2 \le\) threshold

Chi2NuclearDataFilter(threshold, cov_matrix)

Keeps benchmarks where the nuclear-data-inclusive \(\chi^2 \le\) threshold

Composing filters

Filter objects support boolean operators:

from andalus import Chi2Filter, Chi2NuclearDataFilter

f_exp   = Chi2Filter(3.0)
f_ndata = Chi2NuclearDataFilter(3.0, suite.covariances.matrix)

# Both conditions must hold
filtered = suite.filter(f_exp & f_ndata)

# At least one condition must hold
filtered = suite.filter(f_exp | f_ndata)

# Negate a filter
filtered = suite.filter(~f_exp)

Custom filters

Any callable that accepts a Benchmark and returns a bool works:

# Keep only fast-spectrum benchmarks
fast_only = lambda b: b.flux is not None and b.flux.ealf > 1e4  # EALF > 10 keV
filtered = suite.filter(fast_only)

HDF5 storage

ANDALUS uses pandas HDF5 tables as its on-disk format, making it easy to accumulate large libraries of benchmarks and covariances across sessions.

# Save
benchmark.to_hdf5("benchmarks.h5")
application.to_hdf5("applications.h5")
covariance.to_hdf5("covariances.h5")

# Load individual items
b = Benchmark.from_hdf5("benchmarks.h5", title="HMI-001")
a = Application.from_hdf5("applications.h5", title="my-reactor")
c = Covariance.from_hdf5("covariances.h5", zai=922350)

# Load entire suites
bs = BenchmarkSuite.from_hdf5("benchmarks.h5", titles=None)          # all
bs = BenchmarkSuite.from_hdf5("benchmarks.h5", titles=["HMI-001"])   # subset

The HDF5 layout is:

{kind}/
  {title}/
    sensitivity    ← pandas table (Sensitivity)
    flux           ← pandas table (FluxSpectrum, optional)
    attrs: m, dm, c, dc

Utility functions

sandwich

The core \(\mathbf{s}_1^\top \mathbf{C} \, \mathbf{s}_2\) operation used throughout uncertainty propagation:

from andalus import sandwich

# Propagate covariance through two sensitivity vectors
result = sandwich(s1, cov_matrix, s2)

# Auto-transpose: s2 defaults to s1 when omitted
variance = sandwich(s, cov_matrix)

sandwich_binwise / uncertainty_reactionwise

Decompose the total uncertainty into contributions per energy bin or per reaction (ZAI/MT pair):

from andalus.utils import sandwich_binwise, uncertainty_reactionwise

bin_contributions  = sandwich_binwise(s, cov)
rxn_contributions  = uncertainty_reactionwise(s, cov)