Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ee54801
Add decay data to data download
LukeSeifert Nov 6, 2025
82fab1c
Add endf decay preprocessing
LukeSeifert Nov 6, 2025
8ecfe91
Add endf Pn capture and error handling
LukeSeifert Nov 6, 2025
0df4c67
Clean up preprocessing and add more half-life collection
LukeSeifert Nov 6, 2025
e7170c9
Fix cbar error and account for 0 lin regression
LukeSeifert Nov 6, 2025
5c5288a
Update git ignore to include new data sim
LukeSeifert Nov 6, 2025
58844f9
Only include data with halflives and emission probabilities
LukeSeifert Nov 6, 2025
3b32a9c
Add sensitivty subplot input option
LukeSeifert Nov 6, 2025
09c4608
Fix typo in defaults
LukeSeifert Nov 7, 2025
f293ed5
Greatly simplify data handling
LukeSeifert Nov 7, 2025
152b468
Account for multiple neutron emissions
LukeSeifert Nov 7, 2025
c19d85a
Add countrate warning suppression for ufloat uncertainties
LukeSeifert Nov 10, 2025
6c8bd5b
Add jeff preprocessing
LukeSeifert Nov 10, 2025
cec5991
Automate JEFF 3.1.1 data download
LukeSeifert Nov 10, 2025
afc65ff
Update gitignore and iaea matching and empty file
LukeSeifert Nov 11, 2025
1c88bf3
Merge remote-tracking branch 'upstream/main' into endf-data
LukeSeifert Nov 12, 2025
2156f9a
Update the readme to include new JEFF data
LukeSeifert Nov 12, 2025
e46fad1
Small change to readme for tests to pass
LukeSeifert Nov 12, 2025
76f50df
Change residual to chi-squared
LukeSeifert Nov 14, 2025
206915d
Update default params
LukeSeifert Nov 14, 2025
ccc9d46
Align all U_i values
LukeSeifert Nov 14, 2025
80871f5
Switch from chi-squared back to residual (numerical instabilities)
LukeSeifert Nov 14, 2025
380cb96
Undo chi square statistic due to numerical instability
LukeSeifert Nov 14, 2025
6d70e31
Update tests to be more lax for groups and to account for changes to …
LukeSeifert Nov 14, 2025
00bf9b0
Add condition number logging
LukeSeifert Nov 14, 2025
9b500b8
Adjust pie charts to have nuc,percent
LukeSeifert Nov 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ cython_debug/
/examples/msre/postproc.json
/examples/msre/images/*

/examples/pure_endfb71/*.csv
/examples/pure_endfb71/postproc.json
/examples/pure_endfb71/images/*

/examples/iaea_matching/*.csv
/examples/iaea_matching/postproc.json
/examples/iaea_matching/images/*


/examples/prelim_results/*
!/examples/prelim_results/input.json
!/examples/prelim_results/results_generator.py
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# MoSDeN
Molten Salt Delayed Neutron (MoSDeN) is a tool used for reconstruction of
Molten Salt Delayed Neutron (MoSDeN) is a tool used for generation of
delayed neutron precursor groups in molten salt reactors.
This tool can also be used for traditional light water reactors, as users
can adjust ex-core residence times of fissile samples to be zero and chemical
removal rates to be zero.

## History
This tool had a previous version in this repository accessible with
Expand All @@ -26,9 +29,12 @@ The exact organization of raw, unprocessed data is flexible, with some notable
exceptions:
- OpenMC chain files to be should all be in a subdirectory labeled with
"omcchain" (see `preprocessing.py` for all keywords)
- ENDF NFY data should all be in a subdirectory labeled `nfy`
- ENDF fission yield data should all be in a subdirectory labeled `nfy`
- JEFF fission yield data should all be in a subdirectory labeled `nfpy`
- ENDF NFY files should be named "nfy-<ZZZ>_<ID>_<AAA>.csv", so 235U would be
`nfy-092_U_235.csv`.
- JEFF NFPY files should be named "nfpy_<NUMS>_<ZZ>-<ID>-<AAA>.dat", so 235U
would be `nfpy_3542_92-U-235.dat` (the `NUMS` value meaning is unclear).
- IAEA beta-delayed neutron emission data should be in a directory `iaea` and
be called `eval.csv` (default when downloading data).

Expand Down
58 changes: 55 additions & 3 deletions download_data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ roman_to_int() {
}

DATA_DIR="mosden/data/unprocessed"
mkdir -p "$DATA_DIR"

# ENDF --------------------------------------------------------------------
ENDF_VERSION="VII.1"
Expand All @@ -29,8 +30,6 @@ if [[ ! " ${ALLOWED_VERSIONS[*]} " =~ " ${ENDF_VERSION} " ]]; then
exit 1
fi

mkdir -p "$DATA_DIR"

LOWERCASE_VERSION=$(echo "${ENDF_VERSION//./}" | tr '[:upper:]' '[:lower:]')

ROMAN_PART="${LOWERCASE_VERSION//[0-9]/}"
Expand All @@ -40,27 +39,80 @@ LOWERCASE_VERSION="${INTEGER_VALUE}${DIGIT_PART}"

ENDF_DIR="${DATA_DIR}/endfb${LOWERCASE_VERSION}"
NFY_DIR="${ENDF_DIR}"
decay_DIR="${ENDF_DIR}"
mkdir -p "$NFY_DIR"

if [[ "${ENDF_VERSION}" == "VII.1" ]]; then
SEPARATOR="-"
decay_SEPARATOR="-"
elif [[ "${ENDF_VERSION}" == "VIII.0" ]]; then
SEPARATOR="_"
decay_SEPARATOR="_"
fi

NFY_ZIP_NAME="ENDF-B-${ENDF_VERSION}${SEPARATOR}nfy.zip"
NFY_URL="https://www.nndc.bnl.gov/endf-b7.1/zips/${NFY_ZIP_NAME}"
NFY_URL="https://www.nndc.bnl.gov/endf-b${INTEGER_VALUE}.${DIGIT_PART}/zips/${NFY_ZIP_NAME}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are INTEGER_VALUE and DIGIT_PART coming from?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INTEGER_VALUE comes from line 37, and translates the roman numeral to an integer value. The DIGIT_PART comes from line 36, and just extracts the number after the period so that it is generically formatted (enabling using of different ENDF data)


echo "Downloading NFY data for ENDF/B-${ENDF_VERSION}..."
TEMP_ZIP="${NFY_DIR}/${NFY_ZIP_NAME}"
echo "Accessing ${NFY_URL}"
wget --show-progress -O "$TEMP_ZIP" "$NFY_URL"
echo "Extracting NFY data..."
unzip "$TEMP_ZIP" -d "$NFY_DIR"
rm "$TEMP_ZIP"
echo "NFY data handled"

decay_ZIP_NAME="ENDF-B-${ENDF_VERSION}${decay_SEPARATOR}decay.zip"
decay_URL="https://www.nndc.bnl.gov/endf-b${INTEGER_VALUE}.${DIGIT_PART}/zips/${decay_ZIP_NAME}"

echo "Downloading decay data for ENDF/B-${ENDF_VERSION}..."
TEMP_ZIP="${decay_DIR}/${decay_ZIP_NAME}"
echo "Accessing ${decay_URL}"
wget --show-progress -O "$TEMP_ZIP" "$decay_URL"
echo "Extracting decay data..."
unzip "$TEMP_ZIP" -d "$decay_DIR"
rm "$TEMP_ZIP"
echo "Decay data handled"

# /ENDF --------------------------------------------------------------------



# JEFF --------------------------------------------------------------------
JEFF_VERSION="3.1.1"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come JEFF only works as version 3.1.1?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's just the only version I've added so far! In a later PR I'll likely be adding more data

ALLOWED_VERSIONS=("3.1.1")

if [[ ! " ${ALLOWED_VERSIONS[*]} " =~ " ${JEFF_VERSION} " ]]; then
echo "Error: Invalid JEFF version '${JEFF_VERSION}'"
echo "Allowed versions: ${ALLOWED_VERSIONS[*]}"
exit 1
fi
JEFF_VERSION_NOP="${JEFF_VERSION//./}"

JEFF_DIR="${DATA_DIR}/jeff${JEFF_VERSION_NOP}"
NFY_DIR="${JEFF_DIR}/nfpy/"
mkdir -p "$NFY_DIR"
echo "Saving data to ${NFY_DIR}"

if [[ "${JEFF_VERSION}" == "3.1.1" ]]; then
JEFF_URL="https://www-nds.iaea.org/public/download-endf/JEFF-${JEFF_VERSION}/nfpy/"
fi


echo "Downloading NFY data for JEFF-${JEFF_VERSION}..."
echo "Accessing ${JEFF_URL}"
wget --show-progress --recursive --no-parent --accept "*.zip" --no-host-directories --cut-dirs=3 -P "${JEFF_DIR}" "$JEFF_URL"
echo "Extracting NFY data..."
for f in "$NFY_DIR"/*.zip; do
unzip "$f" -d "$NFY_DIR"
done
echo "Removing zip files..."
rm "$NFY_DIR"/*.zip
echo "NFY data handled"


# /JEFF --------------------------------------------------------------------

# IAEA --------------------------------------------------------------------
IAEA_DIR="${DATA_DIR}/iaea"
IAEA_FILE="$IAEA_DIR/eval.csv"
Expand Down
1 change: 1 addition & 0 deletions examples/empty_run/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
49 changes: 49 additions & 0 deletions examples/iaea_matching/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "IAEA",
"file_options": {
"overwrite": {
"preprocessing": true,
"concentrations": true,
"count_rate": true,
"group_fitting": true,
"postprocessing": true,
"logger": true
},
"unprocessed_data_dir": "/home/luke/github/mosden/mosden/data/unprocessed/",
"log_level": 20
},
"data_options": {
"half_life": "iaea/eval.csv",
"cross_section": "",
"emission_probability": "iaea/eval.csv",
"fission_yield": "jeff311/nfpy/",
"decay_time_spacing": "log",
"temperature_K": 920,
"density_g_cm3": 2.3275,
"energy_MeV": 0.0253e-6,
"fissile_fractions": {
"U235": 1.0
}
},
"modeling_options": {
"parent_feeding": false,
"concentration_handling": "CFY",
"count_rate_handling": "data",
"reprocessing_locations": ["excore"],
"reprocessing": {
"Xe": 0.0
},
"irrad_type": "saturation",
"incore_s": 10,
"excore_s": 0,
"net_irrad_s": 420,
"decay_time": 1200,
"num_decay_times": 800
},
"group_options": {
"num_groups": 6,
"method": "nlls",
"samples": 5000,
"sample_func": "normal"
}
}
15 changes: 5 additions & 10 deletions examples/prelim_results/contribution_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@

all_data = [yields, counts, concs]
all_nucs = [
'Other',
'Ge86',
'I137',
'As86',
Expand All @@ -45,7 +44,8 @@
'Br88',
'Br87',
'Cs141',
'Te136'
'Te136',
'Other'
]
all_nucs.reverse()
postobj = PostProcess(None)
Expand All @@ -63,18 +63,13 @@
colors.append(color_nucs[k])
if k != 'Other':
k = postobj._convert_nuc_to_latex(k)
labels.append(k)
labels.append(k + ', ' + str(round(v)) + '\%')

fig, ax = plt.subplots()
_, _, autotexts = ax.pie(sizes, labels=labels, autopct='%1.1f%%',
pctdistance=0.7, labeldistance=1.1,
fig, ax = plt.subplots(subplot_kw=dict(aspect='equal'))
_, _ = ax.pie(sizes, labels=labels, labeldistance=1.1,
colors=colors)
ax.axis('equal')

for i, autotext in enumerate(autotexts):
autotext.set_color('black')
autotext.set_path_effects([path_effects.Stroke(linewidth=2.0, foreground='white'),
path_effects.Normal()])
plt.tight_layout()
fig.savefig(f'{counter}dnp_yield.png')
plt.close()
Expand Down
55 changes: 55 additions & 0 deletions examples/pure_endfb71/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "ENDFB71 Data",
"file_options": {
"overwrite": {
"preprocessing": true,
"concentrations": true,
"count_rate": true,
"group_fitting": true,
"postprocessing": true,
"logger": true
},
"unprocessed_data_dir": "/home/luke/github/mosden/mosden/data/unprocessed/",
"processed_data_dir": "/home/luke/github/mosden/examples/pure_endfb71/",
"output_dir": "/home/luke/github/mosden/examples/pure_endfb71/",
"log_level": 20,
"log_file": "/home/luke/github/mosden/examples/pure_endfb71/log.log"
Comment on lines +12 to +16
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will these directory lines later be changed to a standardized format?

Copy link
Copy Markdown
Collaborator Author

@LukeSeifert LukeSeifert Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this raises a good point. The default arguments are available (if the user does not provide a parameter) that can auto-fill these parameters with the correct path for users, so the only reason to include this is to demonstrate that these parameters are available. I think for now this could be left as-is, but I will create an issue (#40) that will:

  1. Remove hard-coded paths from examples
  2. Add documentation that shows users what parameters exist in the input, what the options are, and what those options do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, sounds like a good plan.

},
"data_options": {
"half_life": "endfb71/decay/",
"cross_section": "",
"emission_probability": "endfb71/decay/",
"fission_yield": "endfb71/nfy/",
"decay_time_spacing": "log",
"temperature_K": 920,
"density_g_cm3": 2.3275,
"energy_MeV": 0.0253e-6,
"fissile_fractions": {
"U235": 1.0
}
},
"modeling_options": {
"parent_feeding": false,
"concentration_handling": "CFY",
"count_rate_handling": "data",
"reprocessing_locations": ["excore"],
"reprocessing": {
"Xe": 0.0
},
"irrad_type": "saturation",
"incore_s": 10,
"excore_s": 0,
"net_irrad_s": 420,
"decay_time": 1200,
"num_decay_times": 800
},
"group_options": {
"num_groups": 6,
"method": "nlls",
"samples": 5000,
"sample_func": "normal"
},
"post_options": {
"sensitivity_subplots": true
}
}
3 changes: 2 additions & 1 deletion mosden/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ def __init__(self, input_path: str) -> None:
Path to the input file
"""
self.omc_data_words: list[str] = ['omcchain']
self.endf_data_words: list[str] = ['nfy']
self.endf_data_words: list[str] = ['nfy', 'decay']
self.iaea_data_words: list[str] = ['iaea']
self.jeff_data_words: list[str] = ['jeff']

self.input_path: str = input_path
self.input_handler: InputHandler = InputHandler(input_path)
Expand Down
23 changes: 16 additions & 7 deletions mosden/groupfit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from time import time
import warnings
from tqdm import tqdm
from scipy.linalg import svd


class Grouper(BaseClass):
Expand Down Expand Up @@ -97,6 +98,7 @@ def _residual_function(
parameters: np.ndarray[float],
times: np.ndarray[float],
counts: np.ndarray[float],
count_err: np.ndarray[float],
fit_func: Callable) -> float:
"""
Calculate the residual of the current set of parameters
Expand All @@ -108,7 +110,9 @@ def _residual_function(
times : np.ndarray[float]
List of times
counts : np.ndarray[float]
List of nominal times
List of delayed neutron counts
count_err : np.ndarray[float]
List of count errors
fit_func : Callable
Function that takes times and parameters to return list of counts

Expand All @@ -117,7 +121,7 @@ def _residual_function(
residual : float
Value of the residual
"""
residual = (counts - fit_func(times, parameters)) / (counts + 1e-12)
residual = (counts - fit_func(times, parameters)) / (counts)
return residual

def _pulse_fit_function(self,
Expand Down Expand Up @@ -267,20 +271,24 @@ def _nonlinear_least_squares(self,
xtol=1e-12,
verbose=0,
max_nfev=1e5,
args=(times, counts, fit_function))

args=(times, counts, count_err, fit_function))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have docstrings for this function? I am not seeing anything...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! It isn't visible in the difference, but the fit_function variable is set to be either the Grouper._pulse_fit_function or Grouper._saturation_fit_function. Both of those functions do have docstrings (line 127 and line 161; the fit_function is set to one of them in lines 241-247).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my bad, I misread the code thinking this was a new function not a function call.

J = result.jac
s = svd(J, compute_uv=False)
condition_number = s[0] / s[-1]
self.logger.info(f'{condition_number = }')
sampled_params: list[float] = list()
tracked_counts: list[float] = list()
sorted_params = self._sort_params_by_half_life(result.x)
sampled_params.append(sorted_params)
countrate = CountRate(self.input_path)
self.logger.info(f'Currently using {self.sample_func} sampling')
for _ in tqdm(range(1, self.MC_samples), desc='Solving least-squares'):
data = countrate.calculate_count_rate(
MC_run=True, sampler_func=self.sample_func)
count_sample = data['counts']
with warnings.catch_warnings():
warnings.simplefilter('ignore')
data = countrate.calculate_count_rate(
MC_run=True, sampler_func=self.sample_func)
count_sample = data['counts']
count_sample_err = data['sigma counts']
result = least_squares(
self._residual_function,
result.x,
Expand All @@ -294,6 +302,7 @@ def _nonlinear_least_squares(self,
args=(
times,
count_sample,
count_sample_err,
fit_function))
tracked_counts.append([i for i in count_sample])
sorted_params = self._sort_params_by_half_life(result.x)
Expand Down
Loading
Loading