User Guide
This guide covers practical examples for querying element data, comparing elements, plotting, and working with generated HDF5 files.
Querying a Single Element
The EPICSClient is the main entry point for
interactive exploration. Point it at the directory containing your ENDF
files:
from pyepics import EPICSClient
client = EPICSClient("data/endf")
# Look up iron by symbol, name, or atomic number — all equivalent:
fe = client.get_element("Fe")
fe = client.get_element("Iron")
fe = client.get_element(26)
# Inspect scalar metadata
print(fe.Z) # 26
print(fe.symbol) # "Fe"
print(fe.name) # "Iron"
# List available electron (EEDL) cross-section keys
print(fe.electron_cross_section_labels)
# ['xs_tot', 'xs_el', 'xs_lge', 'xs_brem', 'xs_exc', 'xs_ion', 'xs_K', ...]
# List available photon (EPDL) cross-section keys
print(fe.photon_cross_section_labels)
# ['xs_tot', 'xs_coherent', 'xs_incoherent', 'xs_photoelectric', ...]
# Subshell binding energies from EADL
print(fe.binding_energies)
# {'K': 7112.0, 'L1': 844.6, 'L2': 719.9, 'L3': 706.8, ...}
# Number of subshells and their labels
print(fe.n_subshells, fe.subshells)
# Get everything as a flat dictionary
summary = fe.to_dict()
print(summary.keys())
Retrieving Cross-Section Arrays
To get the raw energy/cross-section NumPy arrays:
energy, xs = client.get_cross_section("Fe", "xs_tot", library="EEDL")
print(energy.shape, xs.shape) # e.g. (92,) (92,)
# Same for photon cross sections
energy, xs = client.get_cross_section("Fe", "xs_tot", library="EPDL")
Comparing Multiple Elements
Compare scalar properties across elements:
# Returns a list of dicts
rows = client.compare(["Fe", "Cu", "Au"])
for r in rows:
print(f"{r['symbol']:>3s} Z={r['Z']:>3d} subshells={r['n_subshells']}")
# Filter to specific properties
rows = client.compare(
["H", "He", "Li", "Be", "B", "C"],
properties=["Z", "symbol", "n_subshells"],
)
If pandas is installed, get a DataFrame directly:
df = client.compare_df(["Fe", "Cu", "Au"])
print(df[["symbol", "Z", "n_subshells"]])
# symbol Z n_subshells
# 0 Fe 26 11
# 1 Cu 29 12
# 2 Au 79 25
Binding Energy Table
Build a binding-energy table across elements (requires pandas):
df = client.binding_energy_table(range(26, 31))
print(df[["Z", "K", "L1", "L2", "L3"]])
Plotting
The pyepics.plotting module provides convenience wrappers for
common plots. Requires matplotlib:
pip install matplotlib
Cross Sections for One Element
from pyepics.plotting import plot_cross_sections
# All electron cross sections for iron
plot_cross_sections(client, "Fe")
# Specific labels only
plot_cross_sections(client, "Fe", labels=["xs_tot", "xs_el", "xs_brem"])
# Photon cross sections
plot_cross_sections(client, "Fe", library="EPDL")
Compare a Cross Section Across Elements
from pyepics.plotting import compare_cross_sections
compare_cross_sections(client, ["C", "Al", "Fe", "Cu", "Au"], "xs_tot")
Binding Energies vs. Atomic Number
from pyepics.plotting import plot_binding_energies
# All subshells
plot_binding_energies(client, range(1, 100))
# Only K shell
plot_binding_energies(client, range(1, 100), subshell="K")
Shell-by-Shell Bar Chart
from pyepics.plotting import plot_shell_binding_energies
plot_shell_binding_energies(client, "Au")
Using Custom Axes
All plotting functions accept an ax parameter so you can compose
multi-panel figures:
import matplotlib.pyplot as plt
from pyepics.plotting import plot_cross_sections
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
plot_cross_sections(client, "Fe", ax=axes[0], show=False)
plot_cross_sections(client, "Au", ax=axes[1], show=False)
plt.tight_layout()
plt.show()
Reading Generated HDF5 Files
After running the pipeline (see Data Pipeline), PyEPICS produces HDF5 files in two output directories:
data/
├── raw/
│ ├── electron/ ← H.h5, He.h5, … (EEDL)
│ ├── photon/ ← H.h5, He.h5, … (EPDL)
│ └── atomic/ ← H.h5, He.h5, … (EADL)
└── mcdc/
├── electron/ ← H.h5, He.h5, … (EEDL)
├── photon/ ← H.h5, He.h5, … (EPDL)
└── atomic/ ← H.h5, He.h5, … (EADL)
Each file is named <Symbol>.h5 (e.g. Fe.h5 for iron, Au.h5 for
gold).
Locating Files
from pathlib import Path
raw_electron_dir = Path("data/raw/electron")
files = sorted(raw_electron_dir.glob("*.h5"))
print(files) # [PosixPath('data/raw/electron/Ac.h5'), ...]
Inspecting HDF5 Structure
Use h5py to inspect the internal structure:
import h5py
with h5py.File("data/raw/electron/Fe.h5", "r") as f:
# Print all top-level groups
print(list(f.keys()))
# e.g. ['metadata', 'total_cross_section', 'elastic_scatter', ...]
# Walk every group and dataset
def print_tree(name, obj):
indent = " " * name.count("/")
if isinstance(obj, h5py.Dataset):
print(f"{indent}{name} shape={obj.shape} dtype={obj.dtype}")
else:
print(f"{indent}{name}/")
f.visititems(print_tree)
Raw Electron (EEDL) HDF5 Schema
/metadata
Z (int), symbol (str), library (str="EEDL"), format (str="raw")
/total_cross_section
energy (N,) float64 [eV]
xs (N,) float64 [barns]
/elastic_scatter/total
energy, xs
/elastic_scatter/large_angle
energy, xs
/elastic_scatter/large_angle_angular_distribution
inc_energy, cosine, probability
/bremsstrahlung/cross_section
energy, xs
/bremsstrahlung/spectra
inc_energy, photon_energy, probability
/bremsstrahlung/average_energy_loss
energy, avg_loss
/excitation/cross_section
energy, xs
/excitation/average_energy_loss
energy, avg_loss
/ionization/total
energy, xs
/ionization/<subshell>/cross_section (e.g. /ionization/K/cross_section)
energy, xs
/ionization/<subshell>/energy_spectrum
inc_energy, secondary_energy, probability
MCDC Electron (EEDL) HDF5 Schema
/metadata
Z, symbol, library="EEDL", format="mcdc"
/electron_reactions/energy_grid (M,) float64 [eV]
/electron_reactions/total_cross_section (M,)
/electron_reactions/elastic_scatter (M,)
/electron_reactions/large_angle_elastic (M,)
/electron_reactions/bremsstrahlung (M,)
/electron_reactions/excitation (M,)
/electron_reactions/ionization (M,)
/electron_reactions/large_angle_scattering_cosine/
grid, offset, cosine, pdf
/electron_reactions/small_angle_scattering_cosine/
grid, offset, cosine, pdf
/electron_reactions/bremsstrahlung_spectra/
grid, offset, photon_energy, pdf
/electron_reactions/excitation_average_energy_loss (M,)
/electron_reactions/bremsstrahlung_average_energy_loss (M,)
/electron_reactions/<subshell>/cross_section (M,)
/electron_reactions/<subshell>/binding_energy scalar
/electron_reactions/<subshell>/energy_spectrum/
grid, offset, secondary_energy, pdf
Reading Cross Sections into a DataFrame
import h5py
import numpy as np
with h5py.File("data/raw/electron/Fe.h5", "r") as f:
energy = f["total_cross_section/energy"][:]
xs = f["total_cross_section/xs"][:]
# If pandas is available:
import pandas as pd
df = pd.DataFrame({"energy_eV": energy, "xs_barns": xs})
print(df.head())
# Plot directly
df.plot(x="energy_eV", y="xs_barns", logx=True, logy=True,
title="Fe Total Electron Cross Section")
Reading MCDC Data
import h5py
import numpy as np
with h5py.File("data/mcdc/electron/Fe.h5", "r") as f:
energy = f["electron_reactions/energy_grid"][:]
xs_tot = f["electron_reactions/total_cross_section"][:]
xs_el = f["electron_reactions/elastic_scatter"][:]
# All cross sections are on the same energy grid
assert energy.shape == xs_tot.shape == xs_el.shape
Reading Atomic Relaxation Data
import h5py
with h5py.File("data/raw/atomic/Fe.h5", "r") as f:
for subshell in f.keys():
if subshell == "metadata":
continue
grp = f[subshell]
be = grp.attrs.get("binding_energy_eV", "N/A")
n = grp.attrs.get("n_electrons", "N/A")
nt = grp["transition_energy"].shape[0] if "transition_energy" in grp else 0
print(f"{subshell}: BE={be} eV, electrons={n}, transitions={nt}")
Reading Photon Data
import h5py
with h5py.File("data/raw/photon/Fe.h5", "r") as f:
# Cross sections
energy = f["total_cross_section/energy"][:]
xs = f["total_cross_section/xs"][:]
# Form factors
x = f["form_factors/coherent/x"][:]
y = f["form_factors/coherent/y"][:]
# MCDC format
with h5py.File("data/mcdc/photon/Fe.h5", "r") as f:
energy = f["photon_reactions/energy_grid"][:]
xs_tot = f["photon_reactions/total_cross_section"][:]
Converting to pandas and Plotting
import h5py
import pandas as pd
import matplotlib.pyplot as plt
with h5py.File("data/raw/electron/Fe.h5", "r") as f:
df = pd.DataFrame({
"energy": f["total_cross_section/energy"][:],
"total": f["total_cross_section/xs"][:],
})
# Add more cross sections if available
if "elastic_scatter/total" in f:
e = f["elastic_scatter/total/energy"][:]
xs = f["elastic_scatter/total/xs"][:]
df_el = pd.DataFrame({"energy": e, "elastic": xs})
df = df.merge(df_el, on="energy", how="outer").sort_values("energy")
fig, ax = plt.subplots(figsize=(8, 5))
ax.loglog(df["energy"], df["total"], label="Total")
if "elastic" in df.columns:
ax.loglog(df["energy"], df["elastic"], label="Elastic")
ax.set_xlabel("Energy (eV)")
ax.set_ylabel("Cross Section (barns)")
ax.set_title("Fe — Electron Cross Sections (raw)")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()