Read ENDF-6 MF=3 Sections with endf¶
Set source_path to any ENDF-6 file, choose an MT, and run the last cell.
The selected MF=3 section is exported as an annotated .yaml file in the same folder as the input file.
In [1]:
Copied!
from pathlib import Path
import os
import re
try:
import endf
except ImportError as exc:
raise ImportError("Install endf first: python -m pip install endf") from exc
PROJECTILE_BY_NSUB = {
10: "N",
10010: "P",
10020: "D",
10030: "T",
20030: "HE3",
20040: "A",
}
PARTICLE_ZA = {
"G": (0, 0),
"N": (0, 1),
"P": (1, 1),
"D": (1, 2),
"T": (1, 3),
"HE3": (2, 3),
"A": (2, 4),
}
ELEMENT_BY_Z = {
0: "N",
1: "H",
2: "HE",
3: "LI",
4: "BE",
5: "B",
6: "C",
7: "N",
8: "O",
}
INTERPOLATION_BY_CODE = {
1: "histogram",
2: "lin-lin",
3: "lin-log",
4: "log-lin",
5: "log-log",
6: "charged-particle",
}
def normalize_path(path_like):
raw = str(path_like)
if os.name != "nt" and len(raw) >= 3 and raw[1:3] == ":\\":
drive = raw[0].lower()
tail = raw[3:].replace("\\", "/")
return Path(f"/mnt/{drive}/{tail}")
return Path(raw).expanduser()
def format_number(value):
number = float(value)
abs_number = abs(number)
if number != 0.0 and (abs_number >= 1.0e6 or abs_number < 1.0e-4):
scientific = format(number, ".6e")
mantissa, exponent = scientific.split("e")
mantissa = mantissa.rstrip("0").rstrip(".")
exponent = exponent[0] + exponent[1:].rjust(2, "0")
return f"{mantissa}e{exponent}"
return format(number, ".8g")
def available_mf3_mts(path_like):
material = endf.Material(str(normalize_path(path_like)))
return sorted(mt for mf, mt in material.sections if mf == 3)
def build_reaction_label(metadata, mt_value):
def normalize_nucleus_name(zsymam):
compact = str(zsymam).replace(" ", "")
match = re.match(r"(\d+)-([A-Za-z]+)-(\d+)", compact)
if not match:
raise ValueError(f"Could not parse ZSYMAM={zsymam!r}")
return f"{match.group(2).upper()}-{int(match.group(3))}"
def split_za(za_value):
za_int = int(round(float(za_value)))
z_value = za_int // 1000
a_value = za_int - 1000 * z_value
return z_value, a_value
def format_emitted_particles(emitted_particles):
pieces = []
current = None
count = 0
for particle in emitted_particles:
if particle == current:
count += 1
continue
if current is not None:
pieces.append(f"{count}{current}" if count > 1 else current)
current = particle
count = 1
if current is not None:
pieces.append(f"{count}{current}" if count > 1 else current)
return "+".join(pieces)
def reaction_descriptor(mt_number):
if mt_number == 2:
return {"kind": "elastic"}
if 50 <= mt_number <= 91:
return {"emitted": ["N"], "level": mt_number - 50, "discrete": True}
if 600 <= mt_number <= 649:
return {"emitted": ["P"], "level": mt_number - 600, "discrete": True}
if 650 <= mt_number <= 699:
return {"emitted": ["D"], "level": mt_number - 650, "discrete": True}
if 700 <= mt_number <= 749:
return {"emitted": ["T"], "level": mt_number - 700, "discrete": True}
if 750 <= mt_number <= 799:
return {"emitted": ["HE3"], "level": mt_number - 750, "discrete": True}
if 800 <= mt_number <= 849:
return {"emitted": ["A"], "level": mt_number - 800, "discrete": True}
emitted_lookup = {
16: ["N", "N"],
17: ["N", "N", "N"],
22: ["N", "A"],
28: ["N", "P"],
32: ["N", "D"],
33: ["N", "T"],
34: ["N", "HE3"],
41: ["N", "N", "P"],
44: ["N", "P", "P"],
102: ["G"],
103: ["P"],
104: ["D"],
105: ["T"],
106: ["HE3"],
107: ["A"],
111: ["P", "P"],
112: ["P", "A"],
115: ["P", "D"],
116: ["P", "T"],
117: ["D", "A"],
}
if mt_number in emitted_lookup:
return {"emitted": emitted_lookup[mt_number], "level": None, "discrete": False}
return None
def residual_nucleus_name(target_za, projectile, emitted_particles):
target_z, target_a = split_za(target_za)
projectile_z, projectile_a = PARTICLE_ZA[projectile]
emitted_z = sum(PARTICLE_ZA[particle][0] for particle in emitted_particles)
emitted_a = sum(PARTICLE_ZA[particle][1] for particle in emitted_particles)
residual_z = target_z + projectile_z - emitted_z
residual_a = target_a + projectile_a - emitted_a
if residual_a <= 0 or residual_z < 0:
return None
return f"{ELEMENT_BY_Z.get(residual_z, f'Z{residual_z}')}-{residual_a}"
nucleus = normalize_nucleus_name(metadata["ZSYMAM"])
projectile = PROJECTILE_BY_NSUB.get(metadata["NSUB"])
if projectile is None:
raise ValueError(f"Unsupported NSUB={metadata['NSUB']}")
descriptor = reaction_descriptor(mt_value)
if descriptor is None:
return f"{nucleus}({projectile},MT{mt_value}),SIG", nucleus
if descriptor.get("kind") == "elastic":
return f"{nucleus}({projectile},EL),SIG", nucleus
emitted_particles = descriptor["emitted"]
emitted_label = format_emitted_particles(emitted_particles)
residual = residual_nucleus_name(metadata["ZA"], projectile, emitted_particles)
if descriptor["discrete"]:
residual_label = residual or "RES"
reaction = (
f"{nucleus}({projectile},{emitted_label}`)"
f"{residual_label}-L{descriptor['level']},SIG"
)
return reaction, nucleus
if residual is not None:
return f"{nucleus}({projectile},{emitted_label}){residual},SIG", nucleus
return f"{nucleus}({projectile},{emitted_label}),SIG", nucleus
def export_mf3_to_txt(path_like, mt_value, out_path=None):
path = normalize_path(path_like)
material = endf.Material(str(path))
try:
metadata = material[1, 451]
section = material[3, mt_value]
except KeyError as exc:
raise ValueError(f"MF=3, MT={mt_value} not found in {path}") from exc
sigma = section["sigma"]
reaction, nucleus = build_reaction_label(metadata, mt_value)
library = path.open("r", encoding="utf-8").readline().split()[0]
interpolation_labels = []
start_index = 0
for end_index_1based, interpolation_code in zip(sigma.breakpoints, sigma.interpolation):
end_index = int(end_index_1based)
interpolation_code = int(interpolation_code)
label = f"{interpolation_code}:{INTERPOLATION_BY_CODE.get(interpolation_code, 'unknown')}"
interpolation_labels.extend([label] * max(0, end_index - start_index))
start_index = end_index
if len(interpolation_labels) < len(sigma.x):
fallback = interpolation_labels[-1] if interpolation_labels else "unknown"
interpolation_labels.extend([fallback] * (len(sigma.x) - len(interpolation_labels)))
output_path = (
path.with_name(f"{path.stem}_MF3_MT{mt_value}.yaml")
if out_path is None
else normalize_path(out_path)
)
header_lines = [
f"#LIBRARY {library}",
f"#REACTION {reaction}",
f"#NUCLEUS {nucleus}",
f"#MF 3",
f"#MT {mt_value}",
f"#EN-MIN {format_number(sigma.x[0])}",
f"#EN-MAX {format_number(sigma.x[-1])}",
"#E,unit Sig,b,unit Interpolation",
]
with output_path.open("w", encoding="utf-8") as handle:
handle.write("\n".join(header_lines) + "\n")
for energy_eV, sigma_b, interpolation in zip(sigma.x, sigma.y, interpolation_labels):
handle.write(
f"{format_number(energy_eV):>12} "
f"{format_number(sigma_b):>14} "
f"{interpolation}\n"
)
return output_path
from pathlib import Path
import os
import re
try:
import endf
except ImportError as exc:
raise ImportError("Install endf first: python -m pip install endf") from exc
PROJECTILE_BY_NSUB = {
10: "N",
10010: "P",
10020: "D",
10030: "T",
20030: "HE3",
20040: "A",
}
PARTICLE_ZA = {
"G": (0, 0),
"N": (0, 1),
"P": (1, 1),
"D": (1, 2),
"T": (1, 3),
"HE3": (2, 3),
"A": (2, 4),
}
ELEMENT_BY_Z = {
0: "N",
1: "H",
2: "HE",
3: "LI",
4: "BE",
5: "B",
6: "C",
7: "N",
8: "O",
}
INTERPOLATION_BY_CODE = {
1: "histogram",
2: "lin-lin",
3: "lin-log",
4: "log-lin",
5: "log-log",
6: "charged-particle",
}
def normalize_path(path_like):
raw = str(path_like)
if os.name != "nt" and len(raw) >= 3 and raw[1:3] == ":\\":
drive = raw[0].lower()
tail = raw[3:].replace("\\", "/")
return Path(f"/mnt/{drive}/{tail}")
return Path(raw).expanduser()
def format_number(value):
number = float(value)
abs_number = abs(number)
if number != 0.0 and (abs_number >= 1.0e6 or abs_number < 1.0e-4):
scientific = format(number, ".6e")
mantissa, exponent = scientific.split("e")
mantissa = mantissa.rstrip("0").rstrip(".")
exponent = exponent[0] + exponent[1:].rjust(2, "0")
return f"{mantissa}e{exponent}"
return format(number, ".8g")
def available_mf3_mts(path_like):
material = endf.Material(str(normalize_path(path_like)))
return sorted(mt for mf, mt in material.sections if mf == 3)
def build_reaction_label(metadata, mt_value):
def normalize_nucleus_name(zsymam):
compact = str(zsymam).replace(" ", "")
match = re.match(r"(\d+)-([A-Za-z]+)-(\d+)", compact)
if not match:
raise ValueError(f"Could not parse ZSYMAM={zsymam!r}")
return f"{match.group(2).upper()}-{int(match.group(3))}"
def split_za(za_value):
za_int = int(round(float(za_value)))
z_value = za_int // 1000
a_value = za_int - 1000 * z_value
return z_value, a_value
def format_emitted_particles(emitted_particles):
pieces = []
current = None
count = 0
for particle in emitted_particles:
if particle == current:
count += 1
continue
if current is not None:
pieces.append(f"{count}{current}" if count > 1 else current)
current = particle
count = 1
if current is not None:
pieces.append(f"{count}{current}" if count > 1 else current)
return "+".join(pieces)
def reaction_descriptor(mt_number):
if mt_number == 2:
return {"kind": "elastic"}
if 50 <= mt_number <= 91:
return {"emitted": ["N"], "level": mt_number - 50, "discrete": True}
if 600 <= mt_number <= 649:
return {"emitted": ["P"], "level": mt_number - 600, "discrete": True}
if 650 <= mt_number <= 699:
return {"emitted": ["D"], "level": mt_number - 650, "discrete": True}
if 700 <= mt_number <= 749:
return {"emitted": ["T"], "level": mt_number - 700, "discrete": True}
if 750 <= mt_number <= 799:
return {"emitted": ["HE3"], "level": mt_number - 750, "discrete": True}
if 800 <= mt_number <= 849:
return {"emitted": ["A"], "level": mt_number - 800, "discrete": True}
emitted_lookup = {
16: ["N", "N"],
17: ["N", "N", "N"],
22: ["N", "A"],
28: ["N", "P"],
32: ["N", "D"],
33: ["N", "T"],
34: ["N", "HE3"],
41: ["N", "N", "P"],
44: ["N", "P", "P"],
102: ["G"],
103: ["P"],
104: ["D"],
105: ["T"],
106: ["HE3"],
107: ["A"],
111: ["P", "P"],
112: ["P", "A"],
115: ["P", "D"],
116: ["P", "T"],
117: ["D", "A"],
}
if mt_number in emitted_lookup:
return {"emitted": emitted_lookup[mt_number], "level": None, "discrete": False}
return None
def residual_nucleus_name(target_za, projectile, emitted_particles):
target_z, target_a = split_za(target_za)
projectile_z, projectile_a = PARTICLE_ZA[projectile]
emitted_z = sum(PARTICLE_ZA[particle][0] for particle in emitted_particles)
emitted_a = sum(PARTICLE_ZA[particle][1] for particle in emitted_particles)
residual_z = target_z + projectile_z - emitted_z
residual_a = target_a + projectile_a - emitted_a
if residual_a <= 0 or residual_z < 0:
return None
return f"{ELEMENT_BY_Z.get(residual_z, f'Z{residual_z}')}-{residual_a}"
nucleus = normalize_nucleus_name(metadata["ZSYMAM"])
projectile = PROJECTILE_BY_NSUB.get(metadata["NSUB"])
if projectile is None:
raise ValueError(f"Unsupported NSUB={metadata['NSUB']}")
descriptor = reaction_descriptor(mt_value)
if descriptor is None:
return f"{nucleus}({projectile},MT{mt_value}),SIG", nucleus
if descriptor.get("kind") == "elastic":
return f"{nucleus}({projectile},EL),SIG", nucleus
emitted_particles = descriptor["emitted"]
emitted_label = format_emitted_particles(emitted_particles)
residual = residual_nucleus_name(metadata["ZA"], projectile, emitted_particles)
if descriptor["discrete"]:
residual_label = residual or "RES"
reaction = (
f"{nucleus}({projectile},{emitted_label}`)"
f"{residual_label}-L{descriptor['level']},SIG"
)
return reaction, nucleus
if residual is not None:
return f"{nucleus}({projectile},{emitted_label}){residual},SIG", nucleus
return f"{nucleus}({projectile},{emitted_label}),SIG", nucleus
def export_mf3_to_txt(path_like, mt_value, out_path=None):
path = normalize_path(path_like)
material = endf.Material(str(path))
try:
metadata = material[1, 451]
section = material[3, mt_value]
except KeyError as exc:
raise ValueError(f"MF=3, MT={mt_value} not found in {path}") from exc
sigma = section["sigma"]
reaction, nucleus = build_reaction_label(metadata, mt_value)
library = path.open("r", encoding="utf-8").readline().split()[0]
interpolation_labels = []
start_index = 0
for end_index_1based, interpolation_code in zip(sigma.breakpoints, sigma.interpolation):
end_index = int(end_index_1based)
interpolation_code = int(interpolation_code)
label = f"{interpolation_code}:{INTERPOLATION_BY_CODE.get(interpolation_code, 'unknown')}"
interpolation_labels.extend([label] * max(0, end_index - start_index))
start_index = end_index
if len(interpolation_labels) < len(sigma.x):
fallback = interpolation_labels[-1] if interpolation_labels else "unknown"
interpolation_labels.extend([fallback] * (len(sigma.x) - len(interpolation_labels)))
output_path = (
path.with_name(f"{path.stem}_MF3_MT{mt_value}.yaml")
if out_path is None
else normalize_path(out_path)
)
header_lines = [
f"#LIBRARY {library}",
f"#REACTION {reaction}",
f"#NUCLEUS {nucleus}",
f"#MF 3",
f"#MT {mt_value}",
f"#EN-MIN {format_number(sigma.x[0])}",
f"#EN-MAX {format_number(sigma.x[-1])}",
"#E,unit Sig,b,unit Interpolation",
]
with output_path.open("w", encoding="utf-8") as handle:
handle.write("\n".join(header_lines) + "\n")
for energy_eV, sigma_b, interpolation in zip(sigma.x, sigma.y, interpolation_labels):
handle.write(
f"{format_number(energy_eV):>12} "
f"{format_number(sigma_b):>14} "
f"{interpolation}\n"
)
return output_path
In [ ]:
Copied!
source_path = None
mt = None
# MF = 3 reaction cross sections
if source_path is None:
print("Set source_path to an ENDF-6 file path")
else:
source_path = normalize_path(source_path)
print(f"Available MF=3 MT sections: {available_mf3_mts(source_path)}")
if mt is None:
print("Set mt. Quick mt reference:\n"
"MT = 16 Production of two neutrons and a residual nucleus\n"
"MT = 28 Production of a neutron and a proton, plus a residual.\n"
"MT = 50-90 single particle reaction emitting n\n"
"MT = 111 Production of 2 protons, plus a residual.\n"
"MT = 600-648 single particle reaction emitting p\n"
"MT = 650-698 single particle reaction emitting d\n"
"MT = 700-748 single particle reaction emitting t\n"
"MT = 750-798 single particle reaction emitting HE3\n"
"MT = 800-848 single particle reaction emitting alpha")
else:
output_path = export_mf3_to_txt(source_path, mt)
print(f"Wrote {output_path}")
source_path = None
mt = None
# MF = 3 reaction cross sections
if source_path is None:
print("Set source_path to an ENDF-6 file path")
else:
source_path = normalize_path(source_path)
print(f"Available MF=3 MT sections: {available_mf3_mts(source_path)}")
if mt is None:
print("Set mt. Quick mt reference:\n"
"MT = 16 Production of two neutrons and a residual nucleus\n"
"MT = 28 Production of a neutron and a proton, plus a residual.\n"
"MT = 50-90 single particle reaction emitting n\n"
"MT = 111 Production of 2 protons, plus a residual.\n"
"MT = 600-648 single particle reaction emitting p\n"
"MT = 650-698 single particle reaction emitting d\n"
"MT = 700-748 single particle reaction emitting t\n"
"MT = 750-798 single particle reaction emitting HE3\n"
"MT = 800-848 single particle reaction emitting alpha")
else:
output_path = export_mf3_to_txt(source_path, mt)
print(f"Wrote {output_path}")
Available MF=3 MT sections: [2, 50, 600] Wrote /mnt/c/Users/alessandro.morandi/Downloads/deuterons-version.VIII.1/d-001_H_002_MF3_MT50.yaml