-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FENDL-3.2b Retrofitting #42
base: main
Are you sure you want to change the base?
Changes from 27 commits
dbef6ca
cdd7bcd
f3d010f
6c5ac24
03e3af3
fa4f29e
0190096
413ae46
2eb9ffd
1247db3
1d35b79
de3cbb4
d20eed8
58a4ede
b83be55
e135311
29528dd
0abb51b
582424b
77e9a65
493a35c
a29bd66
69fe5f0
d0f7d3b
4318ae4
b1b63f9
1edd251
fb2d548
4065d00
4250c44
93d469f
98dcc93
2460d72
6fcf5e5
5ec6bbf
f490d38
45df27f
b76634f
121e57a
fb1b796
c8e6cea
4d99f41
e0529dc
cc064b6
14c5730
95815b2
a5997b5
f83a646
98f23c3
498c824
a807f1e
76b9aa1
eebeea3
912530f
c374494
f50b617
4ba725e
6257033
4921dba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# Import packages | ||
import ENDFtk | ||
import gendf_tools as GENDFtk | ||
import pandas as pd | ||
import sys | ||
import subprocess | ||
from logging_config import logger | ||
import groupr_tools as GRPRtk | ||
|
||
# Load MT table | ||
# Data for MT table collected from | ||
# https://www.oecd-nea.org/dbdata/data/manual-endf/endf102_MT.pdf | ||
mt_table = GENDFtk.read_csv('mt_table.csv') | ||
|
||
def main(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This main method is doing too many things and I don't know why. You probably need some more modularity, with each function having a docstring to clarify what it's purpose is |
||
|
||
# Parse command line arguments | ||
args = GENDFtk.fendl_args() | ||
|
||
# Set conditionals for local file input | ||
if args.method == 'I': | ||
gendf_path = args.local_path | ||
pKZA = GENDFtk.gendf_pkza_extract(gendf_path) | ||
|
||
# Set conditionals for file download | ||
elif args.method == 'D': | ||
element = args.element | ||
A = args.A | ||
# Check isomeric state | ||
if 'm' not in A: | ||
gendf_path, pKZA = GENDFtk.gendf_download(element, A) | ||
else: | ||
# Use NJOY GROUPR to convert the isomer's TENDL 2017 data to a GENDF file | ||
|
||
# Download ENDF and PENDF files for the isomer | ||
endf_path = GRPRtk.tendl_download(element, A, 'endf') | ||
pendf_path = GRPRtk.tendl_download(element, A, 'pendf') | ||
|
||
# Extract necessary MT and MAT data from the ENDF file | ||
matb, MTs = GRPRtk.endf_specs(endf_path) | ||
|
||
# Write out the GROUPR input file | ||
card_deck = GRPRtk.groupr_input_file_format(matb, MTs, element, A, mt_table) | ||
GRPRtk.groupr_input_file_writer(card_deck, MTs) | ||
|
||
# Run NJOY with GROUPR to create a GENDF file for the isomer | ||
gendf_path = GRPRtk.run_njoy(card_deck, element, A) | ||
|
||
# Save pKZA value | ||
pKZA = GENDFtk.gendf_pkza_extract(gendf_path, M = 1) | ||
|
||
# Clean up repository from unnecessary intermediate files from GROUPR run | ||
groupr_files = ['groupr.inp', 'groupr.out', 'tape20', 'tape21', 'tape31'] | ||
for file in groupr_files: | ||
subprocess.run(['rm', file]) | ||
|
||
logger.info(f"GENDF file path: {gendf_path}") | ||
logger.info(f"Parent KZA (pKZA): {pKZA}") | ||
|
||
# Read in data with ENDFtk | ||
with GRPRtk.redirect_ENDFtk_output(): | ||
tape = ENDFtk.tree.Tape.from_file(gendf_path) | ||
mat_ids = tape.material_numbers | ||
mat = mat_ids[0] | ||
xs_MF = 3 | ||
file = tape.material(mat).file(xs_MF) | ||
|
||
# Extract the MT numbers that are present in the file | ||
MTs = [MT.MT for MT in file.sections.to_list()] | ||
|
||
# Initialize lists | ||
cross_sections_by_MT = [] | ||
emitted_particles_list = [] | ||
dKZAs = [] | ||
|
||
# Extract data for each MT | ||
for MT in MTs: | ||
try: | ||
sigma_list = GENDFtk.extract_cross_sections(file, MT) | ||
if not sigma_list: | ||
continue | ||
dKZA, emitted_particles = GENDFtk.reaction_calculator(MT, mt_table, pKZA) | ||
if dKZA is None: | ||
continue | ||
cross_sections_by_MT.append(sigma_list) | ||
dKZAs.append(dKZA) | ||
emitted_particles_list.append(emitted_particles) | ||
except Exception as e: | ||
logger.error(f"Error processing MT {MT}: {e}") | ||
continue | ||
|
||
# Store data in a Pandas DataFrame | ||
gendf_data = pd.DataFrame({ | ||
'Parent KZA': [pKZA] * len(dKZAs), | ||
'Daughter KZA': dKZAs, | ||
'Emitted Particles': emitted_particles_list, | ||
'Cross Sections': cross_sections_by_MT | ||
}) | ||
|
||
# Save to CSV | ||
gendf_data.to_csv('gendf_data.csv', index=False) | ||
logger.info("Saved gendf_data.csv") | ||
logger.info(gendf_data.head()) | ||
|
||
# Execute main() function based on arguments | ||
if __name__ == '__main__': | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# Import packages | ||
import argparse | ||
import csv | ||
import requests | ||
import sys | ||
from groupr_tools import elements | ||
from logging_config import logger | ||
|
||
# Define an argument parser | ||
def fendl_args(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's most common to put the argument parser in the file that has the main method. Is there a reason it's here? |
||
# Temporarily reset stdout and stderr to the defaults | ||
# so that arg messages (i.e. --help) print out to terminal | ||
original_stdout = sys.stdout | ||
original_stderr = sys.stderr | ||
|
||
try: | ||
sys.stdout = sys.__stdout__ | ||
sys.stderr = sys.__stderr__ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need this? It seems pretty unusual for an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried commenting this out, but it had the effect of sending the |
||
|
||
parser = argparse.ArgumentParser() | ||
|
||
# Subparsers for 'I' and 'D' | ||
subparsers = parser.add_subparsers(dest='method', required=True) | ||
parser_I = subparsers.add_parser('I', help='Local file input') | ||
parser_I.add_argument('--local-path', | ||
required=True, | ||
help='Path to the local GENDF file.') | ||
parser_D = subparsers.add_parser('D', help='Download GENDF file') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This downloads tendl/pendf files, not GENDF |
||
parser_D.add_argument('--element', '-e', | ||
required=True, | ||
help= 'Chemical symbol for selected element (i.e. Ti).') | ||
parser_D.add_argument('--A', '-a', | ||
required=True, | ||
help='Mass number for selected isotope (i.e. 48). If the target is an isomer, "m" after the mass number (i.e. 48m)') | ||
|
||
args = parser.parse_args() | ||
return args | ||
|
||
finally: | ||
# Restore stdout and stderr to the logger | ||
sys.stdout = original_stdout | ||
sys.stderr = original_stderr | ||
|
||
# Define a function to read CSV files | ||
def read_csv(csv_path): | ||
|
||
# Initialize an empty dictionary | ||
data_dict = {} | ||
|
||
# Open the CSV file | ||
with open(csv_path, mode='r') as file: | ||
# Create a CSV reader object | ||
csv_reader = csv.DictReader(file) | ||
|
||
# Initialize the dictionary keys with empty lists | ||
for column in csv_reader.fieldnames: | ||
data_dict[column] = [] | ||
|
||
# Populate the dictionary with the CSV data | ||
for row in csv_reader: | ||
for column in csv_reader.fieldnames: | ||
data_dict[column].append(row[column]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't need to happen inside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is the best data structure for this data. When I look at how you use it, it seems you'd rather have a dictionary with key of the MT number and data of the other columns, esp. the 2nd column. Data structure choice is important in python for a few reasons:
I think you want to translate these rows into a single dict with something like:
|
||
return data_dict | ||
|
||
# Define a function to download the GENDF file from nds.iaea.org | ||
def gendf_download(element, A, M=None, save_path=None): | ||
# Initialize parameters | ||
Z = str(elements[element]).zfill(2) | ||
A = str(A).zfill(3) | ||
gendf_gen_url = 'https://www-nds.iaea.org/fendl/data/neutron/group/' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah! These GENDF files are NOT activation files. They are transport files and not reliable for our activation purposes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll hold off reviewing the GENDF stuff until we are sure it's useful |
||
download_url = f'{gendf_gen_url}{Z}{element}_{A}.g' | ||
save_path = save_path or f'./fendl3_{element}{A}' | ||
|
||
# Check to see if the URL is valid | ||
response = requests.head(download_url) | ||
if response.status_code == 404: | ||
raise FileNotFoundError(f'{download_url} not found') | ||
elif response.status_code == 301: | ||
download_url = f'{gendf_gen_url}{Z}{element}{A}.g' | ||
|
||
# Download the file from the URL | ||
logger.info(f'Downloading file from: {download_url}') | ||
response = requests.get(download_url, stream=True) | ||
with open(save_path, 'w') as f: | ||
f.write(response.content.decode('utf-8')) | ||
logger.info(f'Downloaded file saved to: {save_path}') | ||
|
||
# Write out the pKZA | ||
M = M or '0' | ||
pKZA = int(Z + A + M) | ||
|
||
return save_path, pKZA | ||
|
||
# Extract pKZA data from a GENDF file | ||
def gendf_pkza_extract(gendf_path, M=None): | ||
with open(gendf_path, 'r') as f: | ||
first_line = f.readline() | ||
logger.info(f"First line of GENDF file: {first_line}") | ||
Z, element, A = first_line.split('-')[:3] | ||
A = A.split(' ')[0] | ||
if 'm' in A: | ||
m_index = A.find('m') | ||
A = A[:m_index] | ||
M = str(str(M).count('m')) or '0' | ||
pKZA = int(Z + A + M) | ||
return pKZA | ||
|
||
# Extract cross-section data for a given MT | ||
def extract_cross_sections(file, MT): | ||
try: | ||
section = file.section(MT).content | ||
lines = section.split('\n')[2:-2:2] | ||
sigma_list = [] | ||
for line in lines: | ||
sigma = line.split(' ')[2] | ||
sign = 1 if '+' in sigma else -1 | ||
mantissa, exponent = sigma.split('+') if sign == 1 else sigma.split('-') | ||
sigma_list.append(float(mantissa) * (10 ** (sign * int(exponent)))) | ||
return sigma_list | ||
except Exception as e: | ||
logger.error(f"Error extracting cross sections for MT {MT}: {e}") | ||
return [] | ||
|
||
# Count emitted particles | ||
def emitted_particle_count(particle, emitted_particle_string): | ||
particle_index = emitted_particle_string.find(particle) | ||
number_str = '' | ||
for i in range(particle_index - 1, -1, -1): | ||
if emitted_particle_string[i].isdigit(): | ||
number_str = emitted_particle_string[i] + number_str | ||
else: | ||
break | ||
return int(number_str) if number_str else 1 if particle in emitted_particle_string else None | ||
|
||
# Check for isomers | ||
def isomer_check(emitted_particle_string): | ||
last_digits_str = '' | ||
for char in reversed(emitted_particle_string): | ||
if char.isdigit(): | ||
last_digits_str = char + last_digits_str | ||
else: | ||
break | ||
return int(last_digits_str) if last_digits_str else 0 | ||
|
||
# Calculate reaction | ||
def reaction_calculator(MT, mt_table, pKZA): | ||
try: | ||
nucleus_protons = int(str(pKZA)[:2]) | ||
A = int(str(pKZA)[2:5]) | ||
index = mt_table['MT'].index(str(MT)) | ||
reaction = mt_table['Reaction'][index] | ||
emitted_particles = reaction.split(',')[1][:-1] | ||
particle_types = ['n', 'd', 'alpha', 'p', 't', '3He'] | ||
emission_tuples = [(emitted_particle_count(p, emitted_particles), p) for p in particle_types if emitted_particle_count(p, emitted_particles)] | ||
|
||
nucleus_neutrons = A - nucleus_protons + 1 | ||
for num_particle, particle in emission_tuples: | ||
if particle == 'n': | ||
nucleus_neutrons -= num_particle | ||
if particle == 'p': | ||
nucleus_protons -= num_particle | ||
|
||
residual_A = str(nucleus_neutrons + nucleus_protons).zfill(3) | ||
M = isomer_check(emitted_particles) | ||
if M != 0: | ||
emitted_particles = emitted_particles[:-len(str(M))] | ||
|
||
dKZA = int(f"{str(nucleus_protons)}{residual_A}{str(M)}") | ||
return dKZA, emitted_particles | ||
except Exception as e: | ||
logger.error(f"Error in reaction calculation for MT {MT}: {e}") | ||
return None, None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separation of concerns - make this more modular with each function performing a specific task.