Sandwich tutorial
This notebook combines Sensitivity, Covariance, and Sandwich. It demonstrates similarity without covariance data, and uncertainty plus representativity when sandy is available to load the ERRORR files.
[1]:
import os
import sys
import tempfile
from pathlib import Path
os.environ.setdefault("MPLCONFIGDIR", str(Path(tempfile.gettempdir()) / "matplotlib"))
def find_project_root(start: Path | None = None) -> Path:
"""Find the pyNDUS repository root."""
start = (start or Path.cwd()).resolve()
for directory in (start, *start.parents):
if (
(directory / "setup.py").is_file()
and (directory / "src" / "pyNDUS").is_dir()
and (directory / "docs" / "tutorials").is_dir()
):
return directory
raise FileNotFoundError(
f"Could not find the pyNDUS repository root starting from {start}"
)
PROJECT_ROOT = find_project_root()
SRC_ROOT = PROJECT_ROOT / "src"
if str(SRC_ROOT) not in sys.path:
sys.path.insert(0, str(SRC_ROOT))
DATA_ROOT = PROJECT_ROOT / "docs" / "tutorials"
SENS_ROOT = DATA_ROOT / "MG_sensitivities"
COV_ROOT = DATA_ROOT / "MG_covariances"
assert SENS_ROOT.is_dir(), f"Sensitivity directory not found: {SENS_ROOT}"
assert COV_ROOT.is_dir(), f"Covariance directory not found: {COV_ROOT}"
[2]:
import importlib.util
import numpy as np
import pandas as pd
from pyNDUS import Sensitivity, Sandwich
HAS_SANDY = importlib.util.find_spec("sandy") is not None
if HAS_SANDY:
from pyNDUS import Covariance
else:
Covariance = None
print("sandy is not installed; covariance-weighted sandwich examples are skipped.")
Sensitivity inputs
[3]:
godiva = Sensitivity(SENS_ROOT / "serpent" / "godiva_sens0.m")
jezebel = Sensitivity(SENS_ROOT / "serpent" / "jezebel_sens0.m")
assert godiva.reader == "serpent"
assert jezebel.reader == "serpent"
assert godiva.n_groups == jezebel.n_groups == 33
pd.DataFrame({
"godiva": [", ".join(godiva.zais.keys()), godiva.n_MTs],
"jezebel": [", ".join(jezebel.zais.keys()), jezebel.n_MTs],
}, index=["nuclides", "n_MTs"])
Reading /Users/nicoloabrate/mycodes/pyNDUS/docs/tutorials/MG_sensitivities/serpent/godiva_sens0.m
- done
Reading /Users/nicoloabrate/mycodes/pyNDUS/docs/tutorials/MG_sensitivities/serpent/jezebel_sens0.m
- done
[3]:
| godiva | jezebel | |
|---|---|---|
| nuclides | U-235, U-238, U-234 | Pu-239, Pu-240, Pu-241, Ga-69, Ga-71 |
| n_MTs | 55 | 55 |
Similarity
Similarity does not need covariance data. Each matrix element is normalized with exactly the two MT sensitivity vectors in its numerator; no global norm over the other reactions is used. Here the same Jezebel object is used on both sides, so every non-zero diagonal element must be one. The off-diagonal elements compare different MT profiles and may differ from one. For a real cross-system comparison, choose two sensitivity objects with a common ZA/MT set.
[4]:
similarity = Sandwich(jezebel, sens2=jezebel, similarity=True, list_resp=["keff"],
list_za=[942390], list_MTs=[18, 102],
)
assert similarity.calculation_type == "similarity"
assert not similarity.similarity.empty
for mt in (18, 102):
value = similarity.similarity.loc[("keff", "Pu-239", mt), mt]
assert np.isclose(value.nominal_value, 1.0)
similarity.similarity
[4]:
| MT_col | 18 | 102 | ||
|---|---|---|---|---|
| RESPONSE | ZA | MT_row | ||
| keff | Pu-239 | 18 | 0.99999999999999977796+/-0.00000000000000000004 | -0.5986+/-0.0005 |
| 102 | -0.5986+/-0.0005 | 1.0+/-0 |
Covariance input for uncertainty and representativity
[5]:
LIB = "endfb_80"
EGRID = "ECCO-33"
TEMPERATURE = 300
if HAS_SANDY:
jezebel_zaids = [942390]
jezebel_cov = {
zaid: Covariance(zaid, TEMPERATURE, group_structure=jezebel.group_structure_as("eV"),
energy_unit="eV", egridname=EGRID, lib=LIB,
database=True, cwd=COV_ROOT,
)
for zaid in jezebel_zaids
}
assert set(jezebel_cov) == set(jezebel_zaids)
assert "errorr33" in jezebel_cov[942390].MFs2MTs
Uncertainty on Jezebel
The sandwich products are variance contributions. With uncertainty_output="signed_sqrt", each displayed matrix term is \(\operatorname{sign}(q_{ij})\sqrt{|q_{ij}|}\), so negative covariance contributions remain negative. These signed roots are not additive: the total standard deviation shown separately is obtained as \(\sqrt{\sum_{ij}q_{ij}}\) from the original contributions.
[6]:
if HAS_SANDY:
uncertainty = Sandwich(jezebel, covmat=jezebel_cov, list_resp=["keff"],
list_mat=["total"], list_za=[942390], list_MTs=[2, 16, 18, 102],
list_MFs=[33], include_MF=True,
uncertainty_output="signed_sqrt",)
assert uncertainty.calculation_type == "uncertainty"
assert not uncertainty.uncertainty.empty
# Signed square roots of the individual MT-MT contributions.
print("Uncertainty as std. dev. in [pcm]:")
display(uncertainty.uncertainty*1E5)
# The physically additive variance matrix and the total standard deviation.
print("Uncertainty as variance:")
display(uncertainty.uncertainty_variance)
print("Total uncertainty in pcm:")
display(uncertainty.uncertainty_standard_deviation*1E5)
Uncertainty as std. dev. in [pcm]:
| MT_col | 2 | 18 | 102 | ||||
|---|---|---|---|---|---|---|---|
| RESPONSE | MATERIAL | ZA | MF | MT_row | |||
| keff | total | Pu-239 | 33 | 2 | 483.6+/-0.7 | -159.07+/-0.13 | 25.603+/-0.022 |
| 18 | -159.07+/-0.13 | 905.98+/-0.09 | 0.0+/-0 | ||||
| 102 | 25.603+/-0.022 | 0.0+/-0 | 74.40+/-0.08 |
Uncertainty as variance:
| MT_col | 2 | 18 | 102 | ||||
|---|---|---|---|---|---|---|---|
| RESPONSE | MATERIAL | ZA | MF | MT_row | |||
| keff | total | Pu-239 | 33 | 2 | (2.338+/-0.007)e-05 | (-2.530+/-0.004)e-06 | (6.555+/-0.011)e-08 |
| 18 | (-2.530+/-0.004)e-06 | (8.2079+/-0.0016)e-05 | 0.0+/-0 | ||||
| 102 | (6.555+/-0.011)e-08 | 0.0+/-0 | (5.536+/-0.011)e-07 |
Total uncertainty in pcm:
| variance | standard_deviation | ||
|---|---|---|---|
| RESPONSE | MATERIAL | ||
| keff | total | 10.109+/-0.007 | 1005.4+/-0.4 |
Representativity
The same Jezebel sensitivity is used as reference and application. This is a compact self-test: the result should be defined wherever the covariance and sensitivities overlap. For this calculation we let list_za default to all Jezebel nuclides, consistently with the normalization terms used internally.
representativity_total is the physical coefficient, obtained by summing all normalized ZA/MF/MT-pair contributions. The matrix and long table are retained only to diagnose how individual nuclear-data terms contribute to that total.
[8]:
if HAS_SANDY:
representativity = Sandwich(jezebel, sens2=jezebel, covmat=jezebel_cov,
representativity=True, list_resp=["keff"],
list_za=None,
list_MTs=[18, 102], list_MFs=[33], include_MF=True,
)
assert representativity.calculation_type == "representativity"
assert not representativity.representativity.empty
assert np.isclose(representativity.representativity_total.loc["keff"].nominal_value, 1.0)
print("Total representativity between Jezebel and Jezebel itself:")
display(representativity.representativity_total)
print("Contribution to representativity:")
display(representativity.representativity)
display(representativity.representativity_table.head())
Total representativity between Jezebel and Jezebel itself:
RESPONSE
keff 1.00000+/-0.00020
Name: representativity_total, dtype: object
Contribution to representativity:
| MT_col | 18 | 102 | |||
|---|---|---|---|---|---|
| RESPONSE | ZA | MF | MT_row | ||
| keff | Pu-239 | 33 | 18 | 0.99330+/-0.00020 | 0.0+/-0 |
| 102 | 0.0+/-0 | 0.006699+/-0.000010 |
| value | |||||
|---|---|---|---|---|---|
| RESPONSE | ZA | MF | MT_row | MT_col | |
| keff | Pu-239 | 33 | 18 | 18 | 0.99330+/-0.00020 |
| 102 | 0.0+/-0 | ||||
| 102 | 18 | 0.0+/-0 | |||
| 102 | 0.006699+/-0.000010 |