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 |
|---|---|---|
|
|
Experiment with measurement \(m\), uncertainty \(\sigma_m\), calculated value \(c\), and sensitivity \(S\) |
|
|
System of interest — same shape as |
|
|
Nuclear data covariance for one or more nuclides |
— |
|
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 |
|---|---|---|
|
|
Unique name |
|
|
Observable type (e.g. |
|
|
Measured value |
|
|
Measurement uncertainty (\(1\sigma\)) |
|
|
Calculated value |
|
|
Calculation uncertainty (\(1\sigma\)) |
|
|
Energy-group sensitivity profiles |
|
|
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 |
|---|---|
|
Keeps benchmarks where \(\chi^2 \le\) threshold |
|
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)