fusion-energy / neutronics-workshop

A workshop covering a range of fusion relevant analysis and simulations with OpenMC, DAGMC, Paramak and other open source fusion neutronics tools
MIT License
109 stars 49 forks source link

add more advanced nuclear data processing examples #272

Open shimwell opened 4 months ago

shimwell commented 4 months ago

we have cross section processing examples but no chain file examples

something like this could help find reactions only available via threshold reactions

from pathlib import Path
import os
import openmc
import openmc.deplete
import matplotlib.pyplot as plt
from openmc.data import NATURAL_ABUNDANCE
from openmc.deplete import REACTIONS
import pandas as pd 
import numpy as np
import tqdm 
# PR to change this to DADZ
from openmc.deplete import REACTIONS
from openmc.data import REACTION_MT
from openmc.data import REACTION_NAME

chain_file = Path(os.environ['OPENMC_CHAIN_FILE'])
chain = openmc.deplete.Chain.from_xml(chain_file)

cross_section_file = Path(os.environ['OPENMC_CROSS_SECTIONS'])
cross_section = openmc.data.DataLibrary().from_xml(cross_section_file)

def find_threshold_energy(cross_sections, mt):
    if mt not in cross_sections.reactions.keys():
        print(f'{cross_sections.name} mt {mt} not found')
        return False
    xs = cross_sections.reactions[mt].xs['1200K']
    energy = xs.x
    barns = xs.y
    index_of_threshold = np.where(barns > 0)[0]
    if len(index_of_threshold) > 0:
        first_index_over_threshold = index_of_threshold[0]
        energy_threshold = energy[first_index_over_threshold]
        return energy_threshold
    else:
        return 0.0

transmutation_reaction_names = list(REACTIONS.keys())

nuclide_names = []
reaction_types = []
reaction_targets = []
threshold_energies_needed = []

for nuclide in tqdm.tqdm(chain.nuclides): # 3820 nuclides
    if nuclide.name in NATURAL_ABUNDANCE.keys(): # 289 nuclides
        filename = cross_section.get_by_material(nuclide.name)['path']
        cross_sections = openmc.data.IncidentNeutron.from_hdf5(filename)
        for reaction in nuclide.reactions:
            # check if reaction results in a change to the proton or neutorn numbers or meta stable
            # this looks like is a duplicate check as we already check that a stable nuc is the target and unstable is the product. However it removes elastic heating dpa and other scores
            if reaction.type in transmutation_reaction_names:
                # check if target is unstable
                if reaction.target not in NATURAL_ABUNDANCE.keys():
                    # ruling out fission as they produce a wide range of products
                    if reaction.type != 'fission':
                        mt= REACTION_MT[reaction.type]
                        threshold_energy_needed = find_threshold_energy(cross_sections, mt)
                        if threshold_energy_needed: # can be false if mt not found
                            print(nuclide.name, reaction.type, reaction.target, threshold_energy_needed)
                            nuclide_names.append(nuclide.name)
                            reaction_types.append(reaction.type)
                            reaction_targets.append(reaction.target)
                            threshold_energies_needed.append(threshold_energy_needed)

dict = {'nuclide_names': nuclide_names, 'reaction_types': reaction_types, 'reaction_targets': reaction_targets, 'threshold_energies_needed': threshold_energies_needed} 
df = pd.DataFrame(dict)

all_products=df['reaction_targets'].unique()

threshold_energy = 5e6

options = []
unique_reactions = {}
for product in all_products[:40]:
    tmp_df = df.loc[df['reaction_targets'] == product]
    options_below_threshold = tmp_df.loc[tmp_df['threshold_energies_needed'] < threshold_energy]
    options_above_threshold = tmp_df.loc[tmp_df['threshold_energies_needed'] > threshold_energy]
    if len(options_above_threshold) >= 1 and len(options_below_threshold) == 0:
        options.append(tmp_df['reaction_targets'].values[0])
        all_reaction_paths = []
        for index, row in options_above_threshold.iterrows():
            all_reaction_paths.append(row.reaction_types)
        unique_reactions[row.nuclide_names] = all_reaction_paths

import matplotlib.pyplot as plt
openmc.plotter.plot_xs(
    reactions = unique_reactions
)
plt.xscale('linear')
plt.title('Isotopes that can only be produced with 5MeV neutrons')
plt.ylim(10e-9, 10e0)
plt.savefig(f'isotopes_{threshold_energy}.png')
shimwell commented 4 months ago

this can help find pathways to a specific isotope. It shows the isotopes and reactions that result in the target


import os
from pathlib import Path

import openmc
import openmc.deplete
import openmc_depletion_plotter
from openmc.data import DADZ

chain_file = Path(os.environ['OPENMC_CHAIN_FILE'])

chain = openmc.deplete.Chain.from_xml(chain_file)

pathways_to_target = {}
for nuclide in chain.nuclides:
    atomics_number, mass_number, metastable_state = openmc.data.zam(nuclide.name)
    if len(nuclide.reactions)>2:
        print(nuclide.reactions)
        reactions=nuclide.reactions
        for reaction in reactions:
            if reaction.type in DADZ.keys():
                delta_mass_number, delta_atomics_number = DADZ[reaction.type]
                target_mass_number = mass_number + delta_mass_number
                target_atomics_number = atomics_number + delta_atomics_number
                target = openmc.data.gnds_name(
                    Z=target_atomics_number,
                    A=target_mass_number
                )
                print(f'{nuclide.name}{reaction.type}{target}')
                pathways_to_target.setdefault(target, []).append((nuclide.name, reaction.type))
shimwell commented 4 months ago

These code above can be improved to account for targets that are metastable and made more concise


import os
from pathlib import Path

import openmc
import openmc.deplete
from openmc.data import DADZ

chain_file = Path(os.environ['OPENMC_CHAIN_FILE'])

chain = openmc.deplete.Chain.from_xml(chain_file)

pathways_to_target = {}
for nuclide in chain.nuclides:
    if len(nuclide.reactions)>0:
        for reaction in nuclide.reactions:
            # if the reaction changes the A or Z number
            if reaction.type in DADZ.keys():
                pathways_to_target.setdefault(reaction.target, []).append((nuclide.name, reaction.type))