Submodule manage_mc_materials (v1.6.1)
This submodule is used for managing the MC materials database found in the "MC_materials" directory, which is located in the same directory as PHITS_tools.py. The MC materials database consists of a list of materials with the necessary compositional information needed for particle transport simulations, and it uses the PNNL Compendium of Material Composition Data for Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, document as its foundational source, on top of which additional materials can be added freely.
The MC materials database files created and managed by this module's functions are also those retrieved by the
fetch_MC_material() function in PHITS Tools.
Assuming you have installed PHITS Tools via pip, you may access the functions within this submodule in your
Python scripts with from PHITS_tools.manage_mc_materials import * or import PHITS_tools.manage_mc_materials as mcmats
(or "as" whatever alias you wish to give it in your programs); otherwise, you can just import this module as you would
any other local module.
MC materials database structure
The MC materials database consists of named "materials sets" (SET_NAME = 'Compiled_MC_materials' and 'PNNL_materials_compendium'
as the defaults included with PHITS Tools) each with the following files for each set:
SET_NAME.json= This is the main database file containing all the information for each material and is what is used by PHITS Tools and this submodule for extracting/adding/modifying materials database information.SET_NAME.txt= A human-readable plaintext file containing all the information inSET_NAME.jsonSET_NAME_index.txt= A text file listing all materials contained in the set, along with their ID numbers and data sourceSET_NAME_by_*_for_*.txt= Text files providing PHITS/MCNP-formatted materials cards for all materials in the set:SET_NAME_by_atom_fraction_for_neutrons.txt= compositions by atom fraction with specific isotopes listedSET_NAME_by_atom_fraction_for_photons.txt= compositions by atom fraction with only elements listed (natural abundances assumed)SET_NAME_by_weight_fraction_for_neutrons.txt= compositions by weight fraction with specific isotopes listedSET_NAME_by_weight_fraction_for_photons.txt= compositions by weight fraction with only elements listed (natural abundances assumed)
SET_NAME_general.txt= A composite of the fourSET_NAME_by_*_for_*.txtfiles where each material's entry is selected following a fairly simple set of rules outlined in thewrite_general_mc_file()function's documentation.
Updating/creating MC materials databases
The main function of this submodule is update_materials_database_files(); while the other functions in this submodule
can be used independently, most can be called automatically within update_materials_database_files(). This function can
be used to updated all of the files of an existing database or to create a new database based off of an existing one.
Executing update_materials_database_files() with information with a new material to be added (or modified) will do the following:
- (if
prefer_user_data_folder=True)setup_local_mc_materials_directory()is executed, doing the following:- Confirmation of existence of a local
MC_materialsdirectory; if not found: - Creation of local
MC_materialsdirectory:- In the same directory as
PHITS_tools.py(or one directory higher) is aMC_materialsdirectory. If it does not exist already, a new local directory, outside the PHITS Tools installation, will be created atpathlib.Path.home()/ '.PHITS-Tools' / 'MC_materials'.- On Windows, this defaults to
C:\Users\USERNAME\.PHITS-Tools\MC_materials - On MacOS and Linux, this defaults to
/home/USERNAME/.PHITS-Tools/MC_materials
- On Windows, this defaults to
- The contents of the original
MC_materialsdirectory are copied to this new local directory.
- In the same directory as
- This directory will be used as the data source for not only
update_materials_database_files()but also as the new default data source for thefetch_MC_material()function in PHITS Tools. - This local directory is created to allow creation and curation of user MC materials databases locally without fear
of being overwritten/deleted when updating PHITS Tools to a new version via
pip install PHITS-Tools --upgrade.
- Confirmation of existence of a local
- The JSON database file will be updated with the new/updated materials information provided to the function.
- If
new_database_base_nameis not left asNone, instead of updating the existing database, a new databased usingnew_database_base_nameas its "SET_NAME" is created from the existing database, applying the specified materials addition/updates to it too. This new database is then used for the remaining operations. - If
save_backup_list=True, a timestamped copy of the updated/new JSON file is placed in theMC_materials/backup_lists/directory.
- If
- (if
update_MC_formated_files=True)write_descriptive_file()is called, updating/creatingSET_NAME.txtandSET_NAME_index.txt - (if
update_descriptive_file=True)write_mc_formated_files()is called, updating/creating the fourSET_NAME_by_*_for_*.txtfiles - (if
update_general_MC_file=True)write_general_mc_file()is called, updating/creatingSET_NAME_general.txt
Main function for updating MC materials database files
update_materials_database_files(): add/modify materials entries, create new database files
Additional helper functions
setup_local_mc_materials_directory(): on first run, creates copy of MC materials outside the PHITS Tools installpnnl_lib_csv_to_dict_list(): converts the PNNL materials_compendium.csv file to a list of dictionariesmaterials_dict_list_to_json(): save a list of dictionaries to a JSON filematerials_json_to_dict_list(): read a JSON file into a list of dictionarieswrite_descripive_material_entry(): write a plaintext material entry for a material's dictionary objectwrite_mc_material_entry(): write a PHITS/MCNP-formatted text entry for a material's dictionary objectwrite_descriptive_file(): write a file of plaintext material entries for all material dictionaries in a listwrite_mc_formated_files(): write 4 files of PHITS/MCNP-formatted materials for all material dictionaries in a listwrite_general_mc_file(): write a file of PHITS/MCNP-formatted materials (choosing "most sensible" of 4 for each)
Usage examples
To create a new database from an existing one, one can use update_materials_database_files() as follows, in this case
showing how the 'Compiled_MC_materials' materials set files were initially created from the PNNL_materials_compendium.json file
with the addition of a material for the atmospheric composition on Mars.
from PHITS_tools.manage_mc_materials import *
# Using the PNNL compendium as a base, create and add to the "Compiled_MC_materials" database
json_filepath = 'PNNL_materials_compendium'
name = 'Martian Atmosphere'
mat_str = '6000 -2.62E-01 8000 -7.00E-01 18000 -1.93E-02 7000 -1.89E-02'
update_materials_database_files(json_filepath,
name,
mat_str,
matid=None,
density='function of altitude and time',
source='Mahaffy 2013, DOI: 10.1126/science.1237966',
source_short='Mahaffy 2013',
formula=None,
molecular_weight=None,
total_atom_density=None,
new_database_base_name="Compiled_MC_materials",
update_descriptive_file=True,
update_MC_formated_files=True,
update_general_MC_file=True,
save_backup_list=True,
prefer_user_data_folder=True)
To just add a new material to an existing database, in this case adding Martian regolith to
Compiled_MC_materials.json and its text files, one can use update_materials_database_files() as follows:
from PHITS_tools.manage_mc_materials import *
# Update the existing "Compiled_MC_materials" database
json_filepath = "Compiled_MC_materials"
name = 'Martian Regolith'
mat_str = '1000 0.151069196 11000 0.033300262 12000 0.016650131 13000 0.033300262 14000 0.156699215 18000 0.537611902 19000 0.033300262 20000 0.016650131 26000 0.021418638'
update_materials_database_files(json_filepath,
name,
mat_str,
matid=None,
density=1.7,
source='McKenna-Lawlor 2012, DOI: 10.1016/j.icarus.2011.04.004',
source_short='McKenna-Lawlor 2012',
formula=None,
molecular_weight=None,
total_atom_density=None,
new_database_base_name=None,
update_descriptive_file=True,
update_MC_formated_files=True,
update_general_MC_file=True,
save_backup_list=True,
prefer_user_data_folder=True)
And, finally, if you wish to recreate the JSON file and text files for the initial PNNL database 'PNNL_materials_compendium',
which uses a CSV file of the PNNL data from PYNE
as its initial source of data (as may be useful if another such PNNL database is published in the future),
this can be done as follows:
from pathlib import Path
from PHITS_tools.manage_mc_materials import *
# Generate the JSON file and descriptive text file for the PNNL library
mat_list = pnnl_lib_csv_to_dict_list()
materials_dict_list_to_json(mat_list,json_filepath=Path(Path.cwd(),'PNNL_materials_compendium.json'))
header_text = 'This file was assembled from the Compendium of Material Composition Data for' + '\n'
header_text += 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n'
header_text += 'Pacific Northwest National Laboratory.' + '\n'
header_text += 'This file seeks to just compile the core information of the compendium in an' + '\n'
header_text += 'easily accessible plain-text format. The full document can be found at: ' + '\n'
header_text += r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n'
write_descriptive_file(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium.txt'),header_text=header_text)
write_mc_formated_files(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium.txt'),header_text=header_text)
write_general_mc_file(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium_general.txt'),header_text=header_text)
Expand source code
r'''
This submodule is used for managing the MC materials database found in the "MC_materials" directory, which is located
in the same directory as PHITS_tools.py. The MC materials database consists of a list of materials with the necessary
compositional information needed for particle transport simulations, and it uses the PNNL
Compendium of Material Composition Data for Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, document as
its foundational source, on top of which additional materials can be added freely.
The MC materials database files created and managed by this module's functions are also those retrieved by the
[`fetch_MC_material()`](https://lindt8.github.io/PHITS-Tools/#PHITS_tools.fetch_MC_material) function in PHITS Tools.
Assuming you have installed PHITS Tools via `pip`, you may access the functions within this submodule in your
Python scripts with `from PHITS_tools.manage_mc_materials import *` or `import PHITS_tools.manage_mc_materials as mcmats`
(or "as" whatever alias you wish to give it in your programs); otherwise, you can just import this module as you would
any other local module.
### **MC materials database structure**
The MC materials database consists of named "materials sets" (`SET_NAME` = `'Compiled_MC_materials'` and `'PNNL_materials_compendium'`
as the defaults included with PHITS Tools) each with the following files for each set:
- `SET_NAME.json` = This is the main database file containing all the information for each material and is what is used
by PHITS Tools and this submodule for extracting/adding/modifying materials database information.
- `SET_NAME.txt` = A human-readable plaintext file containing all the information in `SET_NAME.json`
- `SET_NAME_index.txt` = A text file listing all materials contained in the set, along with their ID numbers and data source
- `SET_NAME_by_*_for_*.txt` = Text files providing PHITS/MCNP-formatted materials cards for all materials in the set:
- `SET_NAME_by_atom_fraction_for_neutrons.txt` = compositions by atom fraction with specific isotopes listed
- `SET_NAME_by_atom_fraction_for_photons.txt` = compositions by atom fraction with only elements listed (natural abundances assumed)
- `SET_NAME_by_weight_fraction_for_neutrons.txt` = compositions by weight fraction with specific isotopes listed
- `SET_NAME_by_weight_fraction_for_photons.txt` = compositions by weight fraction with only elements listed (natural abundances assumed)
- `SET_NAME_general.txt` = A composite of the four `SET_NAME_by_*_for_*.txt` files where each material's entry is selected
following a fairly simple set of rules outlined in the `write_general_mc_file` function's documentation.
### **Updating/creating MC materials databases**
The main function of this submodule is `update_materials_database_files`; while the other functions in this submodule
can be used independently, most can be called automatically within `update_materials_database_files`. This function can
be used to updated all of the files of an existing database or to create a new database based off of an existing one.
Executing `update_materials_database_files` with information with a new material to be added (or modified) will do the following:
1. (if `prefer_user_data_folder=True`) `setup_local_mc_materials_directory` is executed, doing the following:
- Confirmation of existence of a local `MC_materials` directory; if not found:
- Creation of local `MC_materials` directory:
- In the same directory as `PHITS_tools.py` (or one directory higher) is a `MC_materials` directory. If it does
not exist already, a new local directory, outside the PHITS Tools installation, will be created at
[`pathlib.Path.home()`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.home)` / '.PHITS-Tools' / 'MC_materials'`.
- On Windows, this defaults to `C:\Users\USERNAME\.PHITS-Tools\MC_materials`
- On MacOS and Linux, this defaults to `/home/USERNAME/.PHITS-Tools/MC_materials`
- The contents of the original `MC_materials` directory are copied to this new local directory.
- This directory will be used as the data source for not only `update_materials_database_files` but also as the new
default data source for the [`fetch_MC_material()`](https://lindt8.github.io/PHITS-Tools/#PHITS_tools.fetch_MC_material) function in PHITS Tools.
- This local directory is created to allow creation and curation of user MC materials databases locally without fear
of being overwritten/deleted when updating PHITS Tools to a new version via `pip install PHITS-Tools --upgrade`.
2. The JSON database file will be updated with the new/updated materials information provided to the function.
- If `new_database_base_name` is not left as `None`, instead of updating the existing database, a new databased using
`new_database_base_name` as its "`SET_NAME`" is created from the existing database, applying the specified
materials addition/updates to it too. This new database is then used for the remaining operations.
- If `save_backup_list=True`, a timestamped copy of the updated/new JSON file is placed in the `MC_materials/backup_lists/` directory.
3. (if `update_MC_formated_files=True`) `write_descriptive_file()` is called, updating/creating `SET_NAME.txt` and `SET_NAME_index.txt`
4. (if `update_descriptive_file=True`) `write_mc_formated_files()` is called, updating/creating the four `SET_NAME_by_*_for_*.txt` files
5. (if `update_general_MC_file=True`) `write_general_mc_file()` is called, updating/creating `SET_NAME_general.txt`
### **Main function for updating MC materials database files**
- `update_materials_database_files` : add/modify materials entries, create new database files
### **Additional helper functions**
- `setup_local_mc_materials_directory` : on first run, creates copy of MC materials outside the PHITS Tools install
- `pnnl_lib_csv_to_dict_list` : converts the PNNL materials_compendium.csv file to a list of dictionaries
- `materials_dict_list_to_json` : save a list of dictionaries to a JSON file
- `materials_json_to_dict_list` : read a JSON file into a list of dictionaries
- `write_descripive_material_entry` : write a plaintext material entry for a material's dictionary object
- `write_mc_material_entry` : write a PHITS/MCNP-formatted text entry for a material's dictionary object
- `write_descriptive_file` : write a file of plaintext material entries for all material dictionaries in a list
- `write_mc_formated_files` : write 4 files of PHITS/MCNP-formatted materials for all material dictionaries in a list
- `write_general_mc_file` : write a file of PHITS/MCNP-formatted materials (choosing "most sensible" of 4 for each)
### **Usage examples**
To create a new database from an existing one, one can use `update_materials_database_files` as follows, in this case
showing how the `'Compiled_MC_materials'` materials set files were initially created from the `PNNL_materials_compendium.json` file
with the addition of a material for the atmospheric composition on Mars.
```python
from PHITS_tools.manage_mc_materials import *
# Using the PNNL compendium as a base, create and add to the "Compiled_MC_materials" database
json_filepath = 'PNNL_materials_compendium'
name = 'Martian Atmosphere'
mat_str = '6000 -2.62E-01 8000 -7.00E-01 18000 -1.93E-02 7000 -1.89E-02'
update_materials_database_files(json_filepath,
name,
mat_str,
matid=None,
density='function of altitude and time',
source='Mahaffy 2013, DOI: 10.1126/science.1237966',
source_short='Mahaffy 2013',
formula=None,
molecular_weight=None,
total_atom_density=None,
new_database_base_name="Compiled_MC_materials",
update_descriptive_file=True,
update_MC_formated_files=True,
update_general_MC_file=True,
save_backup_list=True,
prefer_user_data_folder=True)
```
To just add a new material to an existing database, in this case adding Martian regolith to
`Compiled_MC_materials.json` and its text files, one can use `update_materials_database_files` as follows:
```python
from PHITS_tools.manage_mc_materials import *
# Update the existing "Compiled_MC_materials" database
json_filepath = "Compiled_MC_materials"
name = 'Martian Regolith'
mat_str = '1000 0.151069196 11000 0.033300262 12000 0.016650131 13000 0.033300262 14000 0.156699215 18000 0.537611902 19000 0.033300262 20000 0.016650131 26000 0.021418638'
update_materials_database_files(json_filepath,
name,
mat_str,
matid=None,
density=1.7,
source='McKenna-Lawlor 2012, DOI: 10.1016/j.icarus.2011.04.004',
source_short='McKenna-Lawlor 2012',
formula=None,
molecular_weight=None,
total_atom_density=None,
new_database_base_name=None,
update_descriptive_file=True,
update_MC_formated_files=True,
update_general_MC_file=True,
save_backup_list=True,
prefer_user_data_folder=True)
```
And, finally, if you wish to recreate the JSON file and text files for the initial PNNL database `'PNNL_materials_compendium'`,
which uses a [CSV file of the PNNL data from PYNE](https://github.com/pyne/pyne/blob/develop/pyne/dbgen/materials_compendium.csv)
as its initial source of data (as may be useful if another such PNNL database is published in the future),
this can be done as follows:
```python
from pathlib import Path
from PHITS_tools.manage_mc_materials import *
# Generate the JSON file and descriptive text file for the PNNL library
mat_list = pnnl_lib_csv_to_dict_list()
materials_dict_list_to_json(mat_list,json_filepath=Path(Path.cwd(),'PNNL_materials_compendium.json'))
header_text = 'This file was assembled from the Compendium of Material Composition Data for' + '\n'
header_text += 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n'
header_text += 'Pacific Northwest National Laboratory.' + '\n'
header_text += 'This file seeks to just compile the core information of the compendium in an' + '\n'
header_text += 'easily accessible plain-text format. The full document can be found at: ' + '\n'
header_text += r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n'
write_descriptive_file(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium.txt'),header_text=header_text)
write_mc_formated_files(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium.txt'),header_text=header_text)
write_general_mc_file(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium_general.txt'),header_text=header_text)
```
'''
import re
import os
import csv
import sys
import json
from pathlib import Path
def update_materials_database_files(json_filepath,name,mat_str,matid=None,density=None,source=None,
source_short=None,formula=None,molecular_weight=None,total_atom_density=None,
new_database_base_name=None,update_descriptive_file=True,
update_MC_formated_files=True,update_general_MC_file=True,
save_backup_list=True,prefer_user_data_folder=True):
r'''
Description:
Add or modify a material in a MC materials database JSON file (optionally updating text files too).
Inputs:
(required)
- `json_filepath` = string or Path object denoting the path to the JSON materials file. If a string is provided
that does not end in `'.json'`, this function will search to see if a JSON file of the same basename exists
in a directory called "MC_materials" located in
- if `prefer_user_data_folder=True`: your local [`"$HOME`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.home)`/.PHITS-Tools/"`
directory, creating it and copying the distributed MC_materials data to it if not yet existing via
the `setup_local_mc_materials_directory` function.
- if `prefer_user_data_folder=False`: the same directory as the PHITS_tools.py module (or one directory up),
which requires either PHITS Tools to be installed via `pip` or locatable within your PYTHONPATH system variable).
The JSON libraries that are shipped with PHITS Tools by default are titled `'Compiled_MC_materials'`
and `'PNNL_materials_compendium'`.
- `name` = string of the name of the material to be added / updated. See the "Notes" section further below for
more information on how entry identification is handled.
- `mat_str` = string designating the composition of the material. This should be provided as a series of
alternating nuclide IDs and concentrations.
Valid nuclide ID's include ZZZAAA values (1000*Z + A) or any format supported by [nuclide_plain_str_to_ZZZAAAM()](https://lindt8.github.io/PHITS-Tools/#PHITS_tools.nuclide_plain_str_to_ZZZAAAM) (no spaces permitted).
Concentrations can be provided as either mass fractions (negative) or atom fractions (positive).
If they do not sum to unity, they will be automatically normalized such that they will.
An example for water: `"1000 2 8000 1"` or `"H 2 O 1"` or `"1000 0.66666 8000 0.33333"`
Inputs:
(optional)
- `matid` = (D=`None`) integer denoting material number in the materials database. If left as default `None`,
this function will nominally assume that a new material is being added to the database.
If an integer is provided, the function will assume the intent is to overwrite an existing entry.
See the "Notes" section further below for more information on how entry identification is handled.
- `density` = (D=`None`) a float denoting the density of the material in g/cm3 (can be a string if variable)
- `source` = (D=`None`) a string denoting the data source, e.g., `'PNNL'`, `'NIST'`, `'Mahaffy 2013, DOI: 10.1126/science.1237966'`, etc. [STRONGLY ENCOURAGED]
- `source_short` = (D=`None`) a string denoting the data source in shorter/abbreviated form, e.g., `'Mahaffy 2013'`
- `formula` = (D=`None`) a string denoting a material's chemical formula
- `molecular_weight` = (D=`None`) a float denoting the molecular weight in g/mole
- `total_atom_density` = (D=`None`) a float denoting the total atom density in atoms/(barn-cm)
- `new_database_base_name` = (D=`None`) a string specifying the base database name to be used for all files written
by this function. If left as default `None`, the base name from `json_filepath` will be used. Otherwise,
this can be used to create new database files, rather than rewriting existing ones.
- `update_descriptive_file` = (D=`True`) Boolean denoting whether the descriptive file for the database,
as generated by `write_descriptive_file()`, should be updated/(re)written.
- `update_MC_formated_files` = (D=`True`) Boolean denoting whether the four MCNP/PHITS-formatted files for the database,
as generated by `write_mc_formated_files()`, should be updated/(re)written.
- `update_general_MC_file` = (D=`True`) Boolean denoting whether the updated descriptive file for the database,
as generated by `write_general_mc_file()`, should be updated/(re)written.
- `save_backup_list` = (D=`True`) Boolean denoting whether an extra (timestamped) copy of the produced JSON
materials list database file should also be saved separately in a "backup_lists" directory located in
the same directory as `json_filepath`.
- `prefer_user_data_folder` = (D=`True`) Boolean denoting whether this function should prioritize the local
MC materials databases in your local [`"$HOME`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.home)`/.PHITS-Tools/"`
directory over those in the PHITS Tools distribution. If the local user directory does not yet exist,
setting this to `True` will cause it to be created with a call of `setup_local_mc_materials_directory`.
This local directory is created to allow creation and curation of user MC materials databases locally without fear
of being overwritten/deleted when updating PHITS Tools to a new version via `pip install PHITS-Tools --upgrade`.
Outputs:
- `None`; the materials data from `json_filepath` will be updated with the provided new material (or written to
a new JSON database named with `new_database_base_name`), and derived text files are optionally updated
too as designated by `update_descriptive_file`, `update_MC_formated_files`, and `update_general_MC_file`.
Notes:
Entries in the database are uniquely identified by their `matid` or the combination of `name` and `source`.
If provided with a `matid` or `name` and `source` combination already present within the database, a prompt
window, as pictured below (in this case, with `matid` specified), will appear showing both the old and new versions
of the entry and asking for confirmation on whether the existing entry should be overwritten with the new one.

'''
import datetime
import tkinter
from tkinter import messagebox
from PHITS_tools import Element_Z_to_Sym, Element_Sym_to_Z, nuclide_plain_str_to_ZZZAAAM
add_new_material = True
rewrite_existing_material = False
rewrite_matid = None # material ID (=index + 1) to overwrite
if matid is not None:
rewrite_matid = matid
rewrite_existing_material = True
add_new_material = False
# Ensure specified JSON database exists
if prefer_user_data_folder and isinstance(json_filepath, str):
json_filepath = setup_local_mc_materials_directory() / (json_filepath + '.json')
elif isinstance(json_filepath, str) and (len(json_filepath) <= 5 or (len(json_filepath) > 5 and json_filepath.lower()[:-5] != '.json')):
# basename is provided, need to find location of PHITS_tools.py and MC_materials/
database_filename = json_filepath + '.json'
import pkgutil
lib_file = None
try:
try: # First, check MC_materials folder distributed with PHITS Tools
phits_tools_module_path = pkgutil.get_loader("PHITS_tools").get_filename()
mc_materials_dir_path = Path(phits_tools_module_path).parent / 'MC_materials/'
mc_materials_updir_path = Path(phits_tools_module_path).parent / '..' / 'MC_materials/'
if mc_materials_dir_path.exists():
lib_file = Path(mc_materials_dir_path,database_filename)
elif mc_materials_updir_path.exists():
lib_file = Path(mc_materials_updir_path, database_filename)
else:
print('Could not find the "MC_materials/" directory via pkgutil.get_loader("PHITS_tools").')
raise FileNotFoundError(mc_materials_dir_path)
except: # Failing that, check PYTHONPATH
user_paths = os.environ['PYTHONPATH'].split(os.pathsep)
for i in user_paths: # first try looking explicitly for MC_materials dir in PYTHONPATH
if 'MC_materials' in i:
lib_file = Path(i, database_filename)
if lib_file is None: # check for PHITS Tools in general
for i in user_paths:
if 'phits_tools' in i.lower() or 'phits-tools' in i.lower():
lib_file = Path(i, 'MC_materials', database_filename)
if lib_file is None:
print('Could not find "PHITS-Tools", "PHITS_tools", nor "MC_materials" folders in PYTHONPATH; this folder contains the vital "MC_materials/" directory where the JSON libraries are stored.')
raise FileNotFoundError('$PYTHONPATH/PHITS-Tools/MC_materials/'+database_filename+
' nor '+'$PYTHONPATH/PHITS_tools/MC_materials/'+database_filename+
' nor '+'$PYTHONPATH/MC_materials/'+database_filename)
except:
print('Failed to locate "MC_materials/" directory.')
print('ERROR: If PHITS Tools is not installed with pip, the PYTHONPATH environmental variable must be defined and contain the path to the directory holding "MC_materials/*.json" files')
return None
json_filepath = lib_file
else:
json_filepath = Path(json_filepath)
if not json_filepath.exists():
json_filepath_caps = json_filepath.parent / (json_filepath.stem + '.JSON')
if json_filepath_caps.exists():
json_filepath = json_filepath_caps
else:
print('Specified materials library could not be located at:', lib_file)
raise FileNotFoundError(json_filepath)
return None
# load in the materials dictionary list
all_mats_list = materials_json_to_dict_list(json_filepath=json_filepath)
# If requested, use new name for database
if new_database_base_name is not None:
json_filepath = Path(json_filepath.parent, new_database_base_name + '.json')
def Element_ZorSym_to_mass(Z):
r'''
Description:
Returns an element's average atomic mass provided its atomic number Z or elemental symbol
Inputs:
- `Z` = string of elemental symbol or atomic number Z
Outputs:
- `A_avg` = average atomic mass
'''
average_atomic_masses = [1.008664,1.007,4.002602,6.941,9.012182,10.811,12.0107,14.0067,15.9994,18.9984032,
20.1797,22.98976928,24.305,26.9815386,28.0855,30.973762,32.065,35.453,39.948,39.0983,
40.078,44.955912,47.867,50.9415,51.9961,54.938045,55.845,58.933195,58.6934,63.546,65.38,
69.723,72.63,74.9216,78.96,79.904,83.798,85.4678,87.62,88.90585,91.224,92.90638,95.96,98,
101.07,102.9055,106.42,107.8682,112.411,114.818,118.71,121.76,127.6,126.90447,131.293,
132.9054519,137.327,138.90547,140.116,140.90765,144.242,145,150.36,151.964,157.25,
158.92535,162.5,164.93032,167.259,168.93421,173.054,174.9668,178.49,180.94788,183.84,
186.207,190.23,192.217,195.084,196.966569,200.59,204.3833,207.2,208.9804,209,210,222,
223,226,227,232.03806,231.03588,238.02891,237,244,243,247,247,251,252,257,258,259,
266,267,268,269,270,277,278,281,282,285,286,289,290,293,294,294]
try:
zi = int(Z)
except:
zi = Element_Sym_to_Z(Z)
return average_atomic_masses[zi]
if add_new_material:
override_duplicate_name = True
# First, check existing list to see if there are any conflicting materials
duplicate_entry_found = False
duplicate_entry_index = None
if any([all_mats_list[i]['name'] == name for i in range(len(all_mats_list))]):
print("Found this material name in the database already:")
for i in range(len(all_mats_list)):
if all_mats_list[i]['name'] == name:
print('\tentry number {} from source = {}'.format(str(i + 1), all_mats_list[i]['source']))
if all_mats_list[i]['source'] == source or all_mats_list[i]['source_short'] == source_short:
print('\tMultiple materials with identical names and sources not allowed!')
duplicate_entry_found = True
duplicate_entry_index = i
if not override_duplicate_name:
print('\tIf your intent is to overwrite that existing entry, please set "override_duplicate_name = True"')
sys.exit()
else:
print('\tOverwriting existing entry with new one, if "Yes" is selected.')
else:
if not override_duplicate_name:
print('\tNo sources conflict with this one, but stopping anyways. Override this option by setting "override_duplicate_name = True"')
sys.exit()
else:
print('\tNo sources conflict with this one, so this entry will be written separately as a new entry.')
if rewrite_existing_material:
if rewrite_matid > len(all_mats_list):
print('Invalid rewrite_matid {} entered; valid entries are integers from 1 to {}'.format(str(rewrite_matid), str(len(all_mats_list))))
sys.exit()
new_mat = {}
new_mat.update({'name': name})
new_mat.update({'density': density})
if source:
new_mat.update({'source': source})
if source_short:
new_mat.update({'source_short': source})
else:
new_mat.update({'source': '-'})
if formula:
new_mat.update({'formula': formula})
else:
new_mat.update({'formula': '-'})
if molecular_weight:
new_mat.update({'molecular weight': molecular_weight})
else:
new_mat.update({'molecular weight': '-'})
if total_atom_density: # else part taken care of later since this is calculated from normal density
new_mat.update({'total atom density': total_atom_density})
# process mat_str
mat_elements = mat_str.split()
ZZZAAA_ids = []
concentrations = []
for i in range(len(mat_elements)):
mel = mat_elements[i]
if i % 2 == 0: # ZA part
try:
ZZZAAA = int(mel)
except:
# contains characters, must be converted
if mel.isalpha(): # only element symbol is listed
Z = Element_Sym_to_Z(mel)
ZZZAAA = 1000 * Z
else:
ZZZAAA = int(nuclide_plain_str_to_ZZZAAAM(mel) / 10)
ZZZAAA_ids.append(ZZZAAA)
else: # concentration part
concentrations.append(float(mel))
# reorder to be in order of increasing ZZZAAA
z1 = [x for x, _ in sorted(zip(ZZZAAA_ids, concentrations))]
z2 = [x for _, x in sorted(zip(ZZZAAA_ids, concentrations))]
ZZZAAA_ids = z1
concentrations = z2
avg_masses = []
# normalize concentrations
con_sum = sum(concentrations)
for i in range(len(concentrations)):
concentrations[i] = concentrations[i] / abs(con_sum)
if con_sum < 0:
provided_mass_fracs = True
mass_fracs = concentrations
# must calculate atom fractions
atom_fracs = []
for i in range(len(mass_fracs)):
A = int(str(ZZZAAA_ids[i])[-3:])
if A == 0:
A = Element_ZorSym_to_mass(int(str(ZZZAAA_ids[i])[:-3]))
atom_fracs.append(mass_fracs[i] / A)
avg_masses.append(A)
sum_fracs = sum(atom_fracs)
# now renormalize
for i in range(len(mass_fracs)):
atom_fracs[i] = atom_fracs[i] / sum_fracs
else:
provided_mass_fracs = False # atom fracs instead
atom_fracs = concentrations
# must calculate atom fractions
mass_fracs = []
for i in range(len(atom_fracs)):
A = int(str(ZZZAAA_ids[i])[-3:])
if A == 0:
A = Element_ZorSym_to_mass(int(str(ZZZAAA_ids[i])[:-3]))
mass_fracs.append(atom_fracs[i] * A)
avg_masses.append(A)
sum_fracs = sum(mass_fracs)
# now renormalize
for i in range(len(atom_fracs)):
mass_fracs[i] = -1 * mass_fracs[i] / sum_fracs
def isfloat(value):
try:
float(value)
return True
except ValueError:
return False
# calculate atom densities if provided numerical density
atom_densities = []
if density and isfloat(density):
# calculate mass of 1 unit of the material
Av = 6.02214076e23
tot_atom_density = 0.0 # g/molecule
for i in range(len(mass_fracs)):
atom_densities.append(float(density) * Av * abs(mass_fracs[i]) / (avg_masses[i] * (1e24))) # in atoms/b-cm
tot_atom_density += abs(atom_densities[-1])
else:
for i in range(len(mass_fracs)):
atom_densities.append(0.0)
# generate summary information which is by element
Z_list = []
sym_list = []
summary_mf = []
summary_af = []
summary_ad = []
photon_ZZZAAA_ids = []
elm_ZA_list = []
for i in range(len(ZZZAAA_ids)):
Z = int(str(ZZZAAA_ids[i])[:-3])
photon_ZZZAAA_ids.append(1000 * Z)
if Z not in Z_list:
Z_list.append(Z)
elm_ZA_list.append(1000 * Z)
sym_list.append(Element_Z_to_Sym(Z))
summary_mf.append(mass_fracs[i])
summary_af.append(atom_fracs[i])
summary_ad.append(atom_densities[i])
else:
zi = Z_list.index(Z)
summary_mf[zi] += mass_fracs[i]
summary_af[zi] += atom_fracs[i]
summary_ad[zi] += atom_densities[i]
# compile into dictionary
new_mat.update({'summary': {'element': sym_list, 'neutron ZA': elm_ZA_list, 'photon ZA': elm_ZA_list,
'weight fraction': summary_mf, 'atom fraction': summary_af,
'atom density': summary_ad}})
if not total_atom_density:
if density and isfloat(density):
new_mat.update({'total atom density': tot_atom_density})
else:
new_mat.update({'total atom density': '-'})
pars = ['neutrons', 'photons']
par_ZAs = [ZZZAAA_ids, photon_ZZZAAA_ids]
for pi in range(len(pars)):
par = pars[pi]
ZA_list = par_ZAs[pi]
new_mat.update({par: {'weight fraction': {'ZA': ZA_list, 'value': mass_fracs},
'atom fraction': {'ZA': ZA_list, 'value': atom_fracs},
'atom density': {'ZA': ZA_list, 'value': atom_densities}}})
# With GUI, ask user to confirm overwriting of existing material
if rewrite_existing_material or (add_new_material and duplicate_entry_found and override_duplicate_name):
if rewrite_existing_material:
this_matid = rewrite_matid
else:
this_matid = duplicate_entry_index+1
existing_mat_str = write_descripive_material_entry(all_mats_list[this_matid - 1], this_matid)
new_mat_str = write_descripive_material_entry(new_mat, this_matid)
r = tkinter.Tk()
r.title("Overwrite existing entry in "+str(json_filepath)+" ?")
l1 = tkinter.Label(r, text="Would you like to overwrite the old entry (left) with the new entry (right)?")
l1.grid(row=0, column=0, sticky=tkinter.W, padx=10, pady=10)
def clickButton1():
global yn_response
yn_response = True
r.destroy()
return yn_response
def clickButton2():
global yn_response
yn_response = False
r.destroy()
return yn_response
f1 = tkinter.Frame(r)
# button widget
b1 = tkinter.Button(f1, text="Yes", command=clickButton1)
b2 = tkinter.Button(f1, text="No", command=clickButton2)
# arranging button widgets
f1.grid(row=0, column=1, padx=10, pady=10, sticky=tkinter.W)
b1.pack(side="left", padx=10)
b2.pack(side="right", padx=10)
# Text of old and new entries
old_entry = tkinter.Label(r, text=existing_mat_str)
new_entry = tkinter.Label(r, text=new_mat_str) # , justify='left'
old_entry.grid(row=1, column=0, sticky=tkinter.W + tkinter.N, padx=10, pady=10)
new_entry.grid(row=1, column=1, sticky=tkinter.W + tkinter.N, padx=10, pady=10)
# b1.grid(row = 2, column = 0, padx = 10, pady = 10, sticky = tkinter.E)
# b2.grid(row = 2, column = 1, padx = 10, pady = 10, sticky = tkinter.W)
# infinite loop which can be terminated
# by keyboard or mouse interrupt
tkinter.mainloop()
# r.option_add('*Dialog.msg.font', 'Courier New 16')
# response = messagebox.askyesno(r,"Python - material overwrite","Would you like to overwrite the following entry:\n"+'\nwith the new one below?\n')
if not yn_response:
sys.exit()
if add_new_material:
# add the new material to the library list
if duplicate_entry_found:
if override_duplicate_name:
all_mats_list[duplicate_entry_index] = new_mat
else:
all_mats_list.append(new_mat)
elif rewrite_existing_material:
all_mats_list[rewrite_matid - 1] = new_mat
# Save new materials list
materials_dict_list_to_json(all_mats_list, json_filepath=json_filepath)
# Update text files
if update_descriptive_file:
lib_filepath = Path(json_filepath.parent, json_filepath.stem + '.txt')
write_descriptive_file(all_mats_list, lib_filepath=lib_filepath)
if update_MC_formated_files:
lib_filepath = Path(json_filepath.parent, json_filepath.stem + '.txt')
write_mc_formated_files(all_mats_list, lib_filepath=lib_filepath)
if update_general_MC_file:
lib_filepath = Path(json_filepath.parent, json_filepath.stem + '_general.txt')
write_general_mc_file(all_mats_list, lib_filepath=lib_filepath)
if save_backup_list:
def time_stamped(fname, fmt='{fname}_%Y-%m-%d-%H%M'):
'''
requires import datetime
'''
return datetime.datetime.now().strftime(fmt).format(fname=fname)
backup_lib_filename = Path(json_filepath.parent, 'backup_lists/', time_stamped(json_filepath.stem) + '.json')
backup_lib_filename.parent.mkdir(parents=True, exist_ok=True) # create backup_lists directory if it doesn't already exist
materials_dict_list_to_json(all_mats_list, json_filepath=backup_lib_filename)
return None
def setup_local_mc_materials_directory(phits_tools_module_path=None):
r'''
Description:
This function copies the "MC_materials" directory distributed with PHITS Tools to a different local directory
(outside of the "PHITS-Tools" directory created when installing PHITS Tools via `pip`); specifically,
it does the following:
- Confirmation of existence of a local `MC_materials` directory; if not found:
- Creation of local `MC_materials` directory:
- In the same directory as `PHITS_tools.py` (or one directory up) is a `MC_materials/` directory. If it does
not exist already, a new local directory, outside the PHITS Tools installation, will be created at
[`pathlib.Path.home()`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.home)` / '.PHITS-Tools' / 'MC_materials'`.
- On Windows, this defaults to `C:\Users\USERNAME\.PHITS-Tools\MC_materials`
- On MacOS and Linux, this defaults to `/home/USERNAME/.PHITS-Tools/MC_materials`
- The contents of the original [distributed `MC_materials` directory](https://github.com/Lindt8/PHITS-Tools/tree/main/MC_materials)
are copied to this new local directory.
- This directory will be used as the data source for not only `update_materials_database_files` but also as the new
default data source for the [`fetch_MC_material()`](https://lindt8.github.io/PHITS-Tools/#PHITS_tools.fetch_MC_material) function in PHITS Tools.
- This local directory is created to allow creation and curation of user MC materials databases locally without fear
of being overwritten/deleted when updating PHITS Tools to a new version via `pip install PHITS-Tools --upgrade`.
Inputs:
- `phits_tools_module_path` = string or Path object denoting the path to the `PHITS_tools.py` Python module file.
If this is left as `None`, an attempt will be made to find it automatically via [`pkgutil.get_loader`](https://docs.python.org/3/library/pkgutil.html#pkgutil.get_loader)`("PHITS_tools").get_filename()`.
Outputs:
- `user_data_dir` = Path object pointing to the found existing or newly created local `MC_materials` directory
'''
user_data_dir = Path.home() / '.PHITS-Tools' / 'MC_materials/'
if not user_data_dir.exists():
import pkgutil
import shutil
# First check to see if MC_materials distributed directory exists (this script should be sitting in it...)
if phits_tools_module_path is None:
phits_tools_module_path = pkgutil.get_loader("PHITS_tools").get_filename()
mc_materials_dist_path = Path(phits_tools_module_path).parent / 'MC_materials/'
if not mc_materials_dist_path.exists():
mc_materials_dist_path = Path(phits_tools_module_path).parent / '..' / 'MC_materials/'
if mc_materials_dist_path.exists():
# Need to create initial local user data directory
#user_data_dir.mkdir(parents=True, exist_ok=True)
# and then copy contents of the distributed MC_materials directory into it
shutil.copytree(mc_materials_dist_path, user_data_dir, ignore=shutil.ignore_patterns('__*__'))
print('Copied contents of', mc_materials_dist_path, 'to new local directory', user_data_dir)
# With only JSON files distributed, go ahead and create the text files for the local database
print('Creating local .txt versions of default MC materials databases...')
# PNNL base library
#mat_list = pnnl_lib_csv_to_dict_list(path_to_materials_compendium_csv=mc_materials_dist_path/'materials_compendium.csv')
#materials_dict_list_to_json(mat_list, json_filepath=mc_materials_dist_path/'PNNL_materials_compendium.json')
mat_list = materials_json_to_dict_list(json_filepath=user_data_dir/'PNNL_materials_compendium.json')
header_text = 'This file was assembled from the Compendium of Material Composition Data for' + '\n'
header_text += 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n'
header_text += 'Pacific Northwest National Laboratory.' + '\n'
header_text += 'This file seeks to just compile the core information of the compendium in an' + '\n'
header_text += 'easily accessible plain-text format. The full document can be found at: ' + '\n'
header_text += r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n'
if not (user_data_dir/'PNNL_materials_compendium.txt').exists():
write_descriptive_file(mat_list, lib_filepath=user_data_dir/'PNNL_materials_compendium.txt', header_text=header_text)
if not (user_data_dir / 'PNNL_materials_compendium_by_atom_fraction_for_neutrons.txt').exists():
write_mc_formated_files(mat_list, lib_filepath=user_data_dir/'PNNL_materials_compendium.txt', header_text=header_text)
if not (user_data_dir / 'PNNL_materials_compendium_general.txt').exists():
write_general_mc_file(mat_list, lib_filepath=user_data_dir/'PNNL_materials_compendium_general.txt', header_text=header_text)
# User-customized library
mat_list = materials_json_to_dict_list(json_filepath=user_data_dir/'Compiled_MC_materials.json')
if not (user_data_dir/'Compiled_MC_materials.txt').exists():
write_descriptive_file(mat_list, lib_filepath=user_data_dir/'Compiled_MC_materials.txt')
if not (user_data_dir / 'Compiled_MC_materials_by_atom_fraction_for_neutrons.txt').exists():
write_mc_formated_files(mat_list, lib_filepath=user_data_dir/'Compiled_MC_materials.txt')
if not (user_data_dir / 'Compiled_MC_materials_general.txt').exists():
write_general_mc_file(mat_list, lib_filepath=user_data_dir/'Compiled_MC_materials_general.txt')
return user_data_dir
def pnnl_lib_csv_to_dict_list(path_to_materials_compendium_csv=(Path.cwd()/'materials_compendium.csv')):
r'''
Description:
This function converts a CSV file of the PNNL
Compendium of Material Composition Data for Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1,
to a Python dictionary object.
The PNNL library's CSV file was obtained from PYNE:
https://github.com/pyne/pyne/blob/develop/pyne/dbgen/materials_compendium.csv
Inputs:
- `path_to_materials_compendium_csv` = string or Path object denoting the path to the "materials_compendium.csv" file
Outputs:
- `mat_list` = a list of dictionary objects of the extracted materials information
'''
def is_line_not_empty(line):
if any([i != '' for i in line]):
return True
return False
f = open(path_to_materials_compendium_csv, 'r', newline='', encoding="utf-8")
csv_reader = csv.reader(f, delimiter=',', quotechar='"')
lines = list(filter(is_line_not_empty, csv_reader))
f.close()
# isolate just the lines we care about
mat_dict_list = []
matno = 1
in_core_lines = False
in_element_section = False
in_mcnp_neutrons = False
in_mcnp_photons = False
for line in lines:
nostr = (str(matno) + '.')
if nostr in line[0][:len(nostr)]:
mat = {}
mat.update({'PNNL_number': int(line[0].replace('.', ''))})
mat.update({'name': line[1]})
mat.update({'source': 'PNNL-15870 Rev. 1'})
mat.update({'source_short': 'PNNL'})
matno += 1
in_core_lines = True
elif line[0] == 'Formula =':
mat.update({'formula': line[1]})
mat.update({'molecular weight': line[6]})
elif line[0] == 'Density (g/cm3) =':
mat.update({'density': line[2]})
mat.update({'total atom density': line[6]})
elif line[0] == 'Element':
in_element_section = True
mat.update({'summary': {'element': [], 'neutron ZA': [], 'photon ZA': [], 'weight fraction': [],
'atom fraction': [], 'atom density': []}})
continue
elif in_element_section:
if line[0] == 'Total':
in_element_section = False
elif line[0] == 'MCNP Form':
in_mcnp_section = True
elif line[0] == 'Neutrons' and in_mcnp_section:
in_mcnp_neutrons = True
mat.update({'neutrons': {'weight fraction': {'ZA': [], 'value': []},
'atom fraction': {'ZA': [], 'value': []},
'atom density': {'ZA': [], 'value': []}}})
if in_element_section:
mat['summary']['element'].append(line[0])
mat['summary']['neutron ZA'].append(line[1])
mat['summary']['photon ZA'].append(line[2])
mat['summary']['weight fraction'].append(line[3])
mat['summary']['atom fraction'].append(line[4])
mat['summary']['atom density'].append(line[5])
if in_mcnp_neutrons:
if line[0] == 'Photons':
in_mcnp_neutrons = False
in_mcnp_photons = True
mat.update({'photons': {'weight fraction': {'ZA': [], 'value': []},
'atom fraction': {'ZA': [], 'value': []},
'atom density': {'ZA': [], 'value': []}}})
else:
mat['neutrons']['weight fraction']['ZA'].append(line[1])
mat['neutrons']['weight fraction']['value'].append(line[2])
mat['neutrons']['atom fraction']['ZA'].append(line[3])
mat['neutrons']['atom fraction']['value'].append(line[4])
mat['neutrons']['atom density']['ZA'].append(line[5])
mat['neutrons']['atom density']['value'].append(line[6])
if in_mcnp_photons:
if line[0] == 'CEPXS Form:':
in_mcnp_photons = False
in_mcnp_section = False
# wrap up entry
mat_dict_list.append(mat)
else:
mat['photons']['weight fraction']['ZA'].append(line[1])
mat['photons']['weight fraction']['value'].append(line[2])
mat['photons']['atom fraction']['ZA'].append(line[3])
mat['photons']['atom fraction']['value'].append(line[4])
mat['photons']['atom density']['ZA'].append(line[5])
mat['photons']['atom density']['value'].append(line[6])
return mat_dict_list
def materials_dict_list_to_json(mat_list,json_filepath=(Path.cwd()/'materials_compendium.json')):
r'''
Description:
This function converts the materials list of dictionaries into a JSON file.
Inputs:
- `mat_dict_list` = a list of dictionary objects of the extracted materials information
- `json_filepath` = string or Path object denoting the path to the JSON materials file to be written
Outputs:
- `None`; the materials data will be saved to `json_filepath`.
'''
with open(json_filepath, 'w') as f:
json.dump(mat_list, f)
print('Materials library file written:', json_filepath)
return None
def materials_json_to_dict_list(json_filepath=(Path.cwd()/'materials_compendium.json')):
r'''
Description:
This function converts the materials list of dictionaries into a JSON file.
Inputs:
- `json_filepath` = string or Path object denoting the path to the JSON materials file
Outputs:
- `mat_list` = a list of dictionary objects of the extracted materials information
'''
with open(json_filepath, "r") as f:
mat_list = json.load(f)
return mat_list
def write_descripive_material_entry(mat,mati):
r'''
Description:
Generate a descriptive text block for a material dictionary object.
Inputs:
- `mat` = a dictionary object formatted like all entries in the library
- `mati` = integer specifying material number within the database
Outputs:
- `entry_text` = a string of formatted text with all information about the material
'''
banner_width = 80
if 'PNNL_number' in mat: # entries are strings already
summary_table_format_string = ' {:9} {:12} {:12} {:13} {:13} {:13} \n'
mcnp_table_format_string = ' {:7} {:13} {:7} {:13} {:7} {:13} \n'
else: # entries are ints/floats
summary_table_format_string = ' {:9} {:<12d} {:<12d} {:<13.6f} {:<13.6f} {:<13.6f} \n'
mcnp_table_format_string = ' {:<7d} {:<13.6f} {:<7d} {:<13.6f} {:<7d} {:<13.6f} \n'
entry_text = '\n' + '*' * banner_width + '\n'
entry_text += ' {:<3d} : {} \n'.format(mati, mat['name'])
entry_text += ' Source = {} \n'.format(mat['source'])
entry_text += ' Formula = {} \n'.format(mat['formula'])
entry_text += ' Molecular weight (g/mole) = {} \n'.format(mat['molecular weight'])
entry_text += ' Density (g/cm3) = {} \n'.format(mat['density'])
if isinstance(mat['total atom density'], str):
entry_text += ' Total atom density (atoms/b-cm) = {} \n'.format(mat['total atom density'])
else:
entry_text += ' Total atom density (atoms/b-cm) = {:<13.4E} \n'.format(mat['total atom density'])
entry_text += '-' * banner_width + '\n'
entry_text += ' Elemental composition \n'
entry_text += ' {:9} {:12} {:12} {:13} {:13} {:13} \n'.format("Element", "Neutron ZA", "Photon ZA", "Weight frac.",
"Atom frac.", "Atom density")
for j in range(len(mat['summary']['element'])):
entry_text += summary_table_format_string.format(mat['summary']['element'][j], mat['summary']['neutron ZA'][j],
mat['summary']['photon ZA'][j],
mat['summary']['weight fraction'][j],
mat['summary']['atom fraction'][j],
mat['summary']['atom density'][j])
pars = ['neutrons', 'photons']
for pi in range(len(pars)):
par = pars[pi]
entry_text += '-' * banner_width + '\n'
entry_text += ' PHITS/MCNP formatted ({}) \n'.format(par)
entry_text += ' {:^17} {:^17} {:^17} \n'.format("Weight Fractions", "Atom Fractions",
"Atom Densities")
for j in range(len(mat[par]['weight fraction']['ZA'])):
entry_text += mcnp_table_format_string.format(mat[par]['weight fraction']['ZA'][j],
mat[par]['weight fraction']['value'][j],
mat[par]['atom fraction']['ZA'][j],
mat[par]['atom fraction']['value'][j],
mat[par]['atom density']['ZA'][j],
mat[par]['atom density']['value'][j])
entry_text += '*' * banner_width + '\n'
return entry_text
def write_mc_material_entry(mat,mati,particle_format='neutrons',concentration_format='weight fraction',comment_char='$'):
r'''
Description:
Generate a MCNP/PHITS-formatted text block for a material dictionary object.
Inputs:
- `mat` = a dictionary object formatted like all entries in the library
- `mati` = integer specifying material number within the database
- `particle_format` = (D=`'neutrons'`) string denoting how material compositions are formatted; select `'neutrons'`
for full isotopic composition or `'photons'` for just elemental compositions (natural abundances)
- `concentration_format` = (D=`'weight fraction'`) string denoting how material concentrations are formatted;
select either `'weight fraction'` or `'atom fraction'`.
- `comment_char` = (D=`'$'`) string denoting the comment character to use (Note: `'$'` works for both PHITS and MCNP.)
Outputs:
- `entry_text` = a string of MCNP/PHITS-formatted text with material composition information
'''
from PHITS_tools import Element_Z_to_Sym
concentration_formats = ['atom fraction', 'weight fraction']
particle_formats = ['neutrons', 'photons']
par = particle_format
conctype = concentration_format
if conctype not in concentration_formats:
raise ValueError("Invalid concentration format. Expected `conctype` to be one of: %s" % concentration_formats)
if par not in particle_formats:
raise ValueError("Invalid particle format. Expected `par` to be one of: %s" % particle_formats)
cc = comment_char
#mati = i + 1 # counting number for material
banner_width = 60
entry_text = '\n' + cc + '*' * banner_width + '\n'
entry_text += cc + ' {:<3d} : {} \n'.format(mati, mat['name'])
if mat['source'] and mat['source'] != '-':
entry_text += cc + ' Source = {} \n'.format(mat['source'])
if mat['formula'] and mat['formula'] != '-':
entry_text += cc + ' Formula = {} \n'.format(mat['formula'])
if mat['molecular weight'] and mat['molecular weight'] != '-':
entry_text += cc + ' Molecular weight (g/mole) = {} \n'.format(mat['molecular weight'])
if mat['density'] and mat['density'] != '-':
entry_text += cc + ' Density (g/cm3) = {} \n'.format(mat['density'])
if mat['total atom density'] and mat['total atom density'] != '-':
if isinstance(mat['total atom density'], str):
entry_text += cc + ' Total atom density (atoms/b-cm) = {} \n'.format(mat['total atom density'])
else:
entry_text += cc + ' Total atom density (atoms/b-cm) = {:<13.4E} \n'.format(mat['total atom density'])
entry_text += cc + ' Composition by {} \n'.format(conctype)
for j in range(len(mat[par][conctype]['ZA'])):
if isinstance(mat[par][conctype]['value'][j], str):
entry_format = '{:4} {:>7} {:13} ' + cc + ' {}' + '\n'
else:
entry_format = '{:4} {:>7d} {:<13.6f} ' + cc + ' {}' + '\n'
if j == 0:
mstr = 'M{:<3}'.format(mati)
else:
mstr = ' ' * 4
ZZZAAA = mat[par][conctype]['ZA'][j]
if ZZZAAA == '-':
ZZZAAA = mat['photons'][conctype]['ZA'][j]
Z = int(str(ZZZAAA)[:-3])
A = str(ZZZAAA)[-3:]
sym = Element_Z_to_Sym(Z)
if A != '000':
isotope = sym + '-' + A.lstrip('0')
else:
isotope = sym
entry_text += entry_format.format(mstr, ZZZAAA, mat[par][conctype]['value'][j], isotope)
entry_text += cc + '*' * banner_width + '\n'
return entry_text
def write_descriptive_file(mat_list,lib_filepath=Path(Path.cwd(),'MC_materials.txt'),header_text='',write_index_file=True):
r'''
Description:
Generates a text file of descriptive text blocks for a list of material dictionary objects.
Inputs:
- `mat_list` = a list of dictionary objects of the extracted materials information
- `lib_filepath` = string or Path object denoting the path to the materials library text file to be saved
- `header_text` = a string of text appearing at the very top of the output text file
If left blank (`''`), a default header string as defined at the start of this function will be used.
- `write_index_file` = (D=`True`) Boolean specifying if an index file, just listing the materials contained
in the outputted file along with their ID number and data source (tab delimited), should also be written.
If `True`, this file will have the same filepath as `lib_filepath` but with `'_index'` appended to its basename.
Outputs:
- `None`; the materials text data will be saved to `lib_filepath`.
'''
lib_text = ''
if header_text=='':
header_text = ' ' + 'This file was assembled from a variety of sources over time;' + '\n'
header_text += ' ' + 'it just seeks to compile information for materials to be used in Monte Carlo ' + '\n'
header_text += ' ' + 'particle transport calculations in an easily accessible plain-text format. ' + '\n'
header_text += ' ' + 'The first 372 entries are from the Compendium of Material Composition Data for' + '\n'
header_text += ' ' + 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n'
header_text += ' ' + 'Pacific Northwest National Laboratory. That document can be found at:' + '\n'
header_text += ' ' + r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n'
header_text += ' ' + 'The sources for other entries are specified.' + '\n'
lib_text += header_text
for i in range(len(mat_list)):
mat = mat_list[i]
mati = i + 1
entry_text = write_descripive_material_entry(mat, mati)
lib_text += entry_text
# save file
f = open(lib_filepath, 'w+')
f.write(lib_text)
f.close()
print('Materials library file written:', lib_filepath)
# Make an index
if write_index_file:
index_text = 'ID\tName\tSource\n'
for i in range(len(mat_list)):
mat = mat_list[i]
index_text += '{}\t{}\t{}\n'.format(str(i + 1), mat['name'], mat['source'])
lib_filepath = Path(lib_filepath)
fpath = Path(lib_filepath.parent, lib_filepath.stem + '_index' + lib_filepath.suffix)
f = open(fpath, 'w+')
f.write(index_text)
f.close()
return None
def write_mc_formated_files(mat_list, lib_filepath=(Path.cwd()/'MC_materials.txt'), comment_char='$', header_text=''):
r'''
Description:
Generates four text files of MCNP/PHITS-formatted materials section text blocks for a list of material dictionary objects.
The four files cover every combination of concentration formats `['atom fraction', 'weight fraction']` and
particle formats `['neutrons', 'photons']`.
Inputs:
- `mat_list` = a list of dictionary objects of the extracted materials information
- `lib_filepath` = string or Path object denoting the basic path to the materials library text files to be saved
- `comment_char` = (D=`'$'`) string denoting the comment character to use (Note: `'$'` works for both PHITS and MCNP.)
- `header_text` = a string of text appearing at the very top of the output text files.
If left blank (`''`), a default header string as defined at the start of this function will be used.
Outputs:
- `None`; the materials text data will be saved to four files at `lib_filepath` with
`_by_[atom/weight]_fraction_for_[neutrons/photons]` appended to the end of the filename.
'''
cc = comment_char
if header_text=='':
header_text = cc + ' ' + 'This file was assembled from a variety of sources over time;' + '\n'
header_text += cc + ' ' + 'it just seeks to compile information for materials to be used in Monte Carlo ' + '\n'
header_text += cc + ' ' + 'particle transport calculations in an easily accessible plain-text format. ' + '\n'
header_text += cc + ' ' + 'The first 372 entries are from the Compendium of Material Composition Data for' + '\n'
header_text += cc + ' ' + 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n'
header_text += cc + ' ' + 'Pacific Northwest National Laboratory. That document can be found at:' + '\n'
header_text += cc + ' ' + r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n'
header_text += cc + ' ' + 'The sources for other entries are specified.' + '\n'
lib_filepath = Path(lib_filepath)
concentration_formats = ['atom fraction', 'weight fraction']
particle_formats = ['neutrons', 'photons']
for cfi in range(len(concentration_formats)):
for pfi in range(len(particle_formats)):
conctype = concentration_formats[cfi]
par = particle_formats[pfi]
lib_text = header_text
for i in range(len(mat_list)):
mat = mat_list[i]
mati = i + 1
entry_text = write_mc_material_entry(mat,mati,particle_format=par,concentration_format=conctype,comment_char=cc)
lib_text += entry_text
# save file
fpath = Path(lib_filepath.parent, lib_filepath.stem + '_by_' + conctype.replace(' ', '_') + '_for_' + par + lib_filepath.suffix)
f = open(fpath, 'w+')
f.write(lib_text)
f.close()
print('Materials library file written:', fpath)
return None
def write_general_mc_file(mat_list,lib_filepath=Path(Path.cwd(),'MC_materials_general.txt'),comment_char='$',header_text=''):
r'''
Description:
Generates a text file of MCNP/PHITS-formatted materials section text blocks for a list of material dictionary objects.
This single file is a mix of concentration and particle formats as automatically selected for most general situations.
If a material entry contains a chemical formula, its concentration will be specified by atom fraction; otherwise,
weight fraction will be used. If any of the "neutron keywords" (`['depleted', 'enriched', ' heu', ' leu', 'uranium', 'plutonium', 'uranyl']`)
appear in a material's name (case insensitive), the "neutrons" particle-formatted data is used (isotopic compositions specified);
otherwise, the "photons" particle format (natural abundances for elements) is used.
Inputs:
- `mat_list` = a list of dictionary objects of the extracted materials information
- `lib_filepath` = string or Path object denoting the basic path to the materials library text file to be saved
- `comment_char` = (D=`'$'`) string denoting the comment character to use (Note: `'$'` works for both PHITS and MCNP.)
- `header_text` = a string of text appearing at the very top of the output text files.
If left blank (`''`), a default header string as defined at the start of this function will be used.
Outputs:
- `None`; the materials text data will be saved to `lib_filepath`.
'''
from PHITS_tools import fetch_MC_material
cc = comment_char
if header_text=='':
header_text = cc + ' ' + 'This file was assembled from a variety of sources over time;' + '\n'
header_text += cc + ' ' + 'it just seeks to compile information for materials to be used in Monte Carlo ' + '\n'
header_text += cc + ' ' + 'particle transport calculations in an easily accessible plain-text format. ' + '\n'
header_text += cc + ' ' + 'The first 372 entries are from the Compendium of Material Composition Data for' + '\n'
header_text += cc + ' ' + 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n'
header_text += cc + ' ' + 'Pacific Northwest National Laboratory. That document can be found at:' + '\n'
header_text += cc + ' ' + r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n'
header_text += cc + ' ' + 'The sources for other entries are specified.' + '\n'
lib_text = header_text
for i in range(len(mat_list)):
lib_text += fetch_MC_material(i + 1, matdict=mat_list[i])
# save file
f = open(lib_filepath, 'w+')
f.write(lib_text)
f.close()
print('Materials library file written:', lib_filepath)
return None
'''
# Generate the JSON file and descriptive text file for the PNNL library
mat_list = pnnl_lib_csv_to_dict_list()
materials_dict_list_to_json(mat_list,json_filepath=Path(Path.cwd(),'PNNL_materials_compendium.json'))
header_text = 'This file was assembled from the Compendium of Material Composition Data for' + '\n'
header_text += 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n'
header_text += 'Pacific Northwest National Laboratory.' + '\n'
header_text += 'This file seeks to just compile the core information of the compendium in an' + '\n'
header_text += 'easily accessible plain-text format. The full document can be found at: ' + '\n'
header_text += r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n'
write_descriptive_file(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium.txt'),header_text=header_text)
write_mc_formated_files(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium.txt'),header_text=header_text)
write_general_mc_file(mat_list,lib_filepath=Path(Path.cwd(),'PNNL_materials_compendium_general.txt'),header_text=header_text)
'''
'''
# Using the PNNL compendium as a base, create and add to the "Compiled_MC_materials" database
json_filepath = 'PNNL_materials_compendium'
name = 'Martian Atmosphere'
mat_str = '6000 -2.62E-01 8000 -7.00E-01 18000 -1.93E-02 7000 -1.89E-02'
update_materials_database_files(json_filepath,
name,
mat_str,
matid=None,
density='function of altitude and time',
source='Mahaffy 2013, DOI: 10.1126/science.1237966',
source_short='Mahaffy 2013',
formula=None,
molecular_weight=None,
total_atom_density=None,
new_database_base_name="Compiled_MC_materials",
update_descriptive_file=True,
update_MC_formated_files=True,
update_general_MC_file=True,
save_backup_list=True,
prefer_user_data_folder=True
)
'''
'''
# Update the existing "Compiled_MC_materials" database
json_filepath = "Compiled_MC_materials"
name = 'Martian Regolith'
mat_str = '1000 0.151069196 11000 0.033300262 12000 0.016650131 13000 0.033300262 14000 0.156699215 18000 0.537611902 19000 0.033300262 20000 0.016650131 26000 0.021418638'
update_materials_database_files(json_filepath,
name,
mat_str,
matid=None,
density=1.7,
source='McKenna-Lawlor 2012, DOI: 10.1016/j.icarus.2011.04.004',
source_short='McKenna-Lawlor 2012',
formula=None,
molecular_weight=None,
total_atom_density=None,
new_database_base_name=None,
update_descriptive_file=True,
update_MC_formated_files=True,
update_general_MC_file=True,
save_backup_list=True,
prefer_user_data_folder=True)
'''
'''
# Update the existing "Compiled_MC_materials" database
json_filepath = "Compiled_MC_materials"
name = 'Wood (Southern Pine, DOUBLE DENSITY) '
mat_str = '1001 -0.059642 6000 -0.497018 7014 -0.004970 8016 -0.427435 12000 -0.001988 16000 -0.004970 19000 -0.001988 20000 -0.001988'
update_materials_database_files(json_filepath,
name,
mat_str,
matid=359,
density=2*0.64,
source='DEMONSTRATION OF CHANGED MATERIAL ENTRY',
source_short='TEST_DEMO',
formula=None,
molecular_weight=None,
total_atom_density=None,
new_database_base_name=None,
update_descriptive_file=True,
update_MC_formated_files=True,
update_general_MC_file=True,
save_backup_list=True,
prefer_user_data_folder=True)
'''
Functions
def update_materials_database_files(json_filepath, name, mat_str, matid=None, density=None, source=None, source_short=None, formula=None, molecular_weight=None, total_atom_density=None, new_database_base_name=None, update_descriptive_file=True, update_MC_formated_files=True, update_general_MC_file=True, save_backup_list=True, prefer_user_data_folder=True)-
Description
Add or modify a material in a MC materials database JSON file (optionally updating text files too).
Inputs
(required)
json_filepath= string or Path object denoting the path to the JSON materials file. If a string is provided that does not end in'.json', this function will search to see if a JSON file of the same basename exists in a directory called "MC_materials" located in- if
prefer_user_data_folder=True: your local"$HOME/.PHITS-Tools/"directory, creating it and copying the distributed MC_materials data to it if not yet existing via thesetup_local_mc_materials_directory()function. - if
prefer_user_data_folder=False: the same directory as the PHITS_tools.py module (or one directory up), which requires either PHITS Tools to be installed viapipor locatable within your PYTHONPATH system variable). The JSON libraries that are shipped with PHITS Tools by default are titled'Compiled_MC_materials'and'PNNL_materials_compendium'.
- if
name= string of the name of the material to be added / updated. See the "Notes" section further below for more information on how entry identification is handled.mat_str= string designating the composition of the material. This should be provided as a series of alternating nuclide IDs and concentrations. Valid nuclide ID's include ZZZAAA values (1000*Z + A) or any format supported by nuclide_plain_str_to_ZZZAAAM() (no spaces permitted). Concentrations can be provided as either mass fractions (negative) or atom fractions (positive). If they do not sum to unity, they will be automatically normalized such that they will. An example for water:"1000 2 8000 1"or"H 2 O 1"or"1000 0.66666 8000 0.33333"
Inputs
(optional)
matid= (D=None) integer denoting material number in the materials database. If left as defaultNone, this function will nominally assume that a new material is being added to the database. If an integer is provided, the function will assume the intent is to overwrite an existing entry. See the "Notes" section further below for more information on how entry identification is handled.density= (D=None) a float denoting the density of the material in g/cm3 (can be a string if variable)source= (D=None) a string denoting the data source, e.g.,'PNNL','NIST','Mahaffy 2013, DOI: 10.1126/science.1237966', etc. [STRONGLY ENCOURAGED]source_short= (D=None) a string denoting the data source in shorter/abbreviated form, e.g.,'Mahaffy 2013'formula= (D=None) a string denoting a material's chemical formulamolecular_weight= (D=None) a float denoting the molecular weight in g/moletotal_atom_density= (D=None) a float denoting the total atom density in atoms/(barn-cm)new_database_base_name= (D=None) a string specifying the base database name to be used for all files written by this function. If left as defaultNone, the base name fromjson_filepathwill be used. Otherwise, this can be used to create new database files, rather than rewriting existing ones.update_descriptive_file= (D=True) Boolean denoting whether the descriptive file for the database, as generated bywrite_descriptive_file(), should be updated/(re)written.update_MC_formated_files= (D=True) Boolean denoting whether the four MCNP/PHITS-formatted files for the database, as generated bywrite_mc_formated_files(), should be updated/(re)written.update_general_MC_file= (D=True) Boolean denoting whether the updated descriptive file for the database, as generated bywrite_general_mc_file(), should be updated/(re)written.save_backup_list= (D=True) Boolean denoting whether an extra (timestamped) copy of the produced JSON materials list database file should also be saved separately in a "backup_lists" directory located in the same directory asjson_filepath.prefer_user_data_folder= (D=True) Boolean denoting whether this function should prioritize the local MC materials databases in your local"$HOME/.PHITS-Tools/"directory over those in the PHITS Tools distribution. If the local user directory does not yet exist, setting this toTruewill cause it to be created with a call ofsetup_local_mc_materials_directory(). This local directory is created to allow creation and curation of user MC materials databases locally without fear of being overwritten/deleted when updating PHITS Tools to a new version viapip install PHITS-Tools --upgrade.
Outputs
None; the materials data fromjson_filepathwill be updated with the provided new material (or written to a new JSON database named withnew_database_base_name), and derived text files are optionally updated too as designated byupdate_descriptive_file,update_MC_formated_files, andupdate_general_MC_file.
Notes
Entries in the database are uniquely identified by their
matidor the combination ofnameandsource. If provided with amatidornameandsourcecombination already present within the database, a prompt window, as pictured below (in this case, withmatidspecified), will appear showing both the old and new versions of the entry and asking for confirmation on whether the existing entry should be overwritten with the new one.
Expand source code
def update_materials_database_files(json_filepath,name,mat_str,matid=None,density=None,source=None, source_short=None,formula=None,molecular_weight=None,total_atom_density=None, new_database_base_name=None,update_descriptive_file=True, update_MC_formated_files=True,update_general_MC_file=True, save_backup_list=True,prefer_user_data_folder=True): r''' Description: Add or modify a material in a MC materials database JSON file (optionally updating text files too). Inputs: (required) - `json_filepath` = string or Path object denoting the path to the JSON materials file. If a string is provided that does not end in `'.json'`, this function will search to see if a JSON file of the same basename exists in a directory called "MC_materials" located in - if `prefer_user_data_folder=True`: your local [`"$HOME`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.home)`/.PHITS-Tools/"` directory, creating it and copying the distributed MC_materials data to it if not yet existing via the `setup_local_mc_materials_directory` function. - if `prefer_user_data_folder=False`: the same directory as the PHITS_tools.py module (or one directory up), which requires either PHITS Tools to be installed via `pip` or locatable within your PYTHONPATH system variable). The JSON libraries that are shipped with PHITS Tools by default are titled `'Compiled_MC_materials'` and `'PNNL_materials_compendium'`. - `name` = string of the name of the material to be added / updated. See the "Notes" section further below for more information on how entry identification is handled. - `mat_str` = string designating the composition of the material. This should be provided as a series of alternating nuclide IDs and concentrations. Valid nuclide ID's include ZZZAAA values (1000*Z + A) or any format supported by [nuclide_plain_str_to_ZZZAAAM()](https://lindt8.github.io/PHITS-Tools/#PHITS_tools.nuclide_plain_str_to_ZZZAAAM) (no spaces permitted). Concentrations can be provided as either mass fractions (negative) or atom fractions (positive). If they do not sum to unity, they will be automatically normalized such that they will. An example for water: `"1000 2 8000 1"` or `"H 2 O 1"` or `"1000 0.66666 8000 0.33333"` Inputs: (optional) - `matid` = (D=`None`) integer denoting material number in the materials database. If left as default `None`, this function will nominally assume that a new material is being added to the database. If an integer is provided, the function will assume the intent is to overwrite an existing entry. See the "Notes" section further below for more information on how entry identification is handled. - `density` = (D=`None`) a float denoting the density of the material in g/cm3 (can be a string if variable) - `source` = (D=`None`) a string denoting the data source, e.g., `'PNNL'`, `'NIST'`, `'Mahaffy 2013, DOI: 10.1126/science.1237966'`, etc. [STRONGLY ENCOURAGED] - `source_short` = (D=`None`) a string denoting the data source in shorter/abbreviated form, e.g., `'Mahaffy 2013'` - `formula` = (D=`None`) a string denoting a material's chemical formula - `molecular_weight` = (D=`None`) a float denoting the molecular weight in g/mole - `total_atom_density` = (D=`None`) a float denoting the total atom density in atoms/(barn-cm) - `new_database_base_name` = (D=`None`) a string specifying the base database name to be used for all files written by this function. If left as default `None`, the base name from `json_filepath` will be used. Otherwise, this can be used to create new database files, rather than rewriting existing ones. - `update_descriptive_file` = (D=`True`) Boolean denoting whether the descriptive file for the database, as generated by `write_descriptive_file()`, should be updated/(re)written. - `update_MC_formated_files` = (D=`True`) Boolean denoting whether the four MCNP/PHITS-formatted files for the database, as generated by `write_mc_formated_files()`, should be updated/(re)written. - `update_general_MC_file` = (D=`True`) Boolean denoting whether the updated descriptive file for the database, as generated by `write_general_mc_file()`, should be updated/(re)written. - `save_backup_list` = (D=`True`) Boolean denoting whether an extra (timestamped) copy of the produced JSON materials list database file should also be saved separately in a "backup_lists" directory located in the same directory as `json_filepath`. - `prefer_user_data_folder` = (D=`True`) Boolean denoting whether this function should prioritize the local MC materials databases in your local [`"$HOME`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.home)`/.PHITS-Tools/"` directory over those in the PHITS Tools distribution. If the local user directory does not yet exist, setting this to `True` will cause it to be created with a call of `setup_local_mc_materials_directory`. This local directory is created to allow creation and curation of user MC materials databases locally without fear of being overwritten/deleted when updating PHITS Tools to a new version via `pip install PHITS-Tools --upgrade`. Outputs: - `None`; the materials data from `json_filepath` will be updated with the provided new material (or written to a new JSON database named with `new_database_base_name`), and derived text files are optionally updated too as designated by `update_descriptive_file`, `update_MC_formated_files`, and `update_general_MC_file`. Notes: Entries in the database are uniquely identified by their `matid` or the combination of `name` and `source`. If provided with a `matid` or `name` and `source` combination already present within the database, a prompt window, as pictured below (in this case, with `matid` specified), will appear showing both the old and new versions of the entry and asking for confirmation on whether the existing entry should be overwritten with the new one.  ''' import datetime import tkinter from tkinter import messagebox from PHITS_tools import Element_Z_to_Sym, Element_Sym_to_Z, nuclide_plain_str_to_ZZZAAAM add_new_material = True rewrite_existing_material = False rewrite_matid = None # material ID (=index + 1) to overwrite if matid is not None: rewrite_matid = matid rewrite_existing_material = True add_new_material = False # Ensure specified JSON database exists if prefer_user_data_folder and isinstance(json_filepath, str): json_filepath = setup_local_mc_materials_directory() / (json_filepath + '.json') elif isinstance(json_filepath, str) and (len(json_filepath) <= 5 or (len(json_filepath) > 5 and json_filepath.lower()[:-5] != '.json')): # basename is provided, need to find location of PHITS_tools.py and MC_materials/ database_filename = json_filepath + '.json' import pkgutil lib_file = None try: try: # First, check MC_materials folder distributed with PHITS Tools phits_tools_module_path = pkgutil.get_loader("PHITS_tools").get_filename() mc_materials_dir_path = Path(phits_tools_module_path).parent / 'MC_materials/' mc_materials_updir_path = Path(phits_tools_module_path).parent / '..' / 'MC_materials/' if mc_materials_dir_path.exists(): lib_file = Path(mc_materials_dir_path,database_filename) elif mc_materials_updir_path.exists(): lib_file = Path(mc_materials_updir_path, database_filename) else: print('Could not find the "MC_materials/" directory via pkgutil.get_loader("PHITS_tools").') raise FileNotFoundError(mc_materials_dir_path) except: # Failing that, check PYTHONPATH user_paths = os.environ['PYTHONPATH'].split(os.pathsep) for i in user_paths: # first try looking explicitly for MC_materials dir in PYTHONPATH if 'MC_materials' in i: lib_file = Path(i, database_filename) if lib_file is None: # check for PHITS Tools in general for i in user_paths: if 'phits_tools' in i.lower() or 'phits-tools' in i.lower(): lib_file = Path(i, 'MC_materials', database_filename) if lib_file is None: print('Could not find "PHITS-Tools", "PHITS_tools", nor "MC_materials" folders in PYTHONPATH; this folder contains the vital "MC_materials/" directory where the JSON libraries are stored.') raise FileNotFoundError('$PYTHONPATH/PHITS-Tools/MC_materials/'+database_filename+ ' nor '+'$PYTHONPATH/PHITS_tools/MC_materials/'+database_filename+ ' nor '+'$PYTHONPATH/MC_materials/'+database_filename) except: print('Failed to locate "MC_materials/" directory.') print('ERROR: If PHITS Tools is not installed with pip, the PYTHONPATH environmental variable must be defined and contain the path to the directory holding "MC_materials/*.json" files') return None json_filepath = lib_file else: json_filepath = Path(json_filepath) if not json_filepath.exists(): json_filepath_caps = json_filepath.parent / (json_filepath.stem + '.JSON') if json_filepath_caps.exists(): json_filepath = json_filepath_caps else: print('Specified materials library could not be located at:', lib_file) raise FileNotFoundError(json_filepath) return None # load in the materials dictionary list all_mats_list = materials_json_to_dict_list(json_filepath=json_filepath) # If requested, use new name for database if new_database_base_name is not None: json_filepath = Path(json_filepath.parent, new_database_base_name + '.json') def Element_ZorSym_to_mass(Z): r''' Description: Returns an element's average atomic mass provided its atomic number Z or elemental symbol Inputs: - `Z` = string of elemental symbol or atomic number Z Outputs: - `A_avg` = average atomic mass ''' average_atomic_masses = [1.008664,1.007,4.002602,6.941,9.012182,10.811,12.0107,14.0067,15.9994,18.9984032, 20.1797,22.98976928,24.305,26.9815386,28.0855,30.973762,32.065,35.453,39.948,39.0983, 40.078,44.955912,47.867,50.9415,51.9961,54.938045,55.845,58.933195,58.6934,63.546,65.38, 69.723,72.63,74.9216,78.96,79.904,83.798,85.4678,87.62,88.90585,91.224,92.90638,95.96,98, 101.07,102.9055,106.42,107.8682,112.411,114.818,118.71,121.76,127.6,126.90447,131.293, 132.9054519,137.327,138.90547,140.116,140.90765,144.242,145,150.36,151.964,157.25, 158.92535,162.5,164.93032,167.259,168.93421,173.054,174.9668,178.49,180.94788,183.84, 186.207,190.23,192.217,195.084,196.966569,200.59,204.3833,207.2,208.9804,209,210,222, 223,226,227,232.03806,231.03588,238.02891,237,244,243,247,247,251,252,257,258,259, 266,267,268,269,270,277,278,281,282,285,286,289,290,293,294,294] try: zi = int(Z) except: zi = Element_Sym_to_Z(Z) return average_atomic_masses[zi] if add_new_material: override_duplicate_name = True # First, check existing list to see if there are any conflicting materials duplicate_entry_found = False duplicate_entry_index = None if any([all_mats_list[i]['name'] == name for i in range(len(all_mats_list))]): print("Found this material name in the database already:") for i in range(len(all_mats_list)): if all_mats_list[i]['name'] == name: print('\tentry number {} from source = {}'.format(str(i + 1), all_mats_list[i]['source'])) if all_mats_list[i]['source'] == source or all_mats_list[i]['source_short'] == source_short: print('\tMultiple materials with identical names and sources not allowed!') duplicate_entry_found = True duplicate_entry_index = i if not override_duplicate_name: print('\tIf your intent is to overwrite that existing entry, please set "override_duplicate_name = True"') sys.exit() else: print('\tOverwriting existing entry with new one, if "Yes" is selected.') else: if not override_duplicate_name: print('\tNo sources conflict with this one, but stopping anyways. Override this option by setting "override_duplicate_name = True"') sys.exit() else: print('\tNo sources conflict with this one, so this entry will be written separately as a new entry.') if rewrite_existing_material: if rewrite_matid > len(all_mats_list): print('Invalid rewrite_matid {} entered; valid entries are integers from 1 to {}'.format(str(rewrite_matid), str(len(all_mats_list)))) sys.exit() new_mat = {} new_mat.update({'name': name}) new_mat.update({'density': density}) if source: new_mat.update({'source': source}) if source_short: new_mat.update({'source_short': source}) else: new_mat.update({'source': '-'}) if formula: new_mat.update({'formula': formula}) else: new_mat.update({'formula': '-'}) if molecular_weight: new_mat.update({'molecular weight': molecular_weight}) else: new_mat.update({'molecular weight': '-'}) if total_atom_density: # else part taken care of later since this is calculated from normal density new_mat.update({'total atom density': total_atom_density}) # process mat_str mat_elements = mat_str.split() ZZZAAA_ids = [] concentrations = [] for i in range(len(mat_elements)): mel = mat_elements[i] if i % 2 == 0: # ZA part try: ZZZAAA = int(mel) except: # contains characters, must be converted if mel.isalpha(): # only element symbol is listed Z = Element_Sym_to_Z(mel) ZZZAAA = 1000 * Z else: ZZZAAA = int(nuclide_plain_str_to_ZZZAAAM(mel) / 10) ZZZAAA_ids.append(ZZZAAA) else: # concentration part concentrations.append(float(mel)) # reorder to be in order of increasing ZZZAAA z1 = [x for x, _ in sorted(zip(ZZZAAA_ids, concentrations))] z2 = [x for _, x in sorted(zip(ZZZAAA_ids, concentrations))] ZZZAAA_ids = z1 concentrations = z2 avg_masses = [] # normalize concentrations con_sum = sum(concentrations) for i in range(len(concentrations)): concentrations[i] = concentrations[i] / abs(con_sum) if con_sum < 0: provided_mass_fracs = True mass_fracs = concentrations # must calculate atom fractions atom_fracs = [] for i in range(len(mass_fracs)): A = int(str(ZZZAAA_ids[i])[-3:]) if A == 0: A = Element_ZorSym_to_mass(int(str(ZZZAAA_ids[i])[:-3])) atom_fracs.append(mass_fracs[i] / A) avg_masses.append(A) sum_fracs = sum(atom_fracs) # now renormalize for i in range(len(mass_fracs)): atom_fracs[i] = atom_fracs[i] / sum_fracs else: provided_mass_fracs = False # atom fracs instead atom_fracs = concentrations # must calculate atom fractions mass_fracs = [] for i in range(len(atom_fracs)): A = int(str(ZZZAAA_ids[i])[-3:]) if A == 0: A = Element_ZorSym_to_mass(int(str(ZZZAAA_ids[i])[:-3])) mass_fracs.append(atom_fracs[i] * A) avg_masses.append(A) sum_fracs = sum(mass_fracs) # now renormalize for i in range(len(atom_fracs)): mass_fracs[i] = -1 * mass_fracs[i] / sum_fracs def isfloat(value): try: float(value) return True except ValueError: return False # calculate atom densities if provided numerical density atom_densities = [] if density and isfloat(density): # calculate mass of 1 unit of the material Av = 6.02214076e23 tot_atom_density = 0.0 # g/molecule for i in range(len(mass_fracs)): atom_densities.append(float(density) * Av * abs(mass_fracs[i]) / (avg_masses[i] * (1e24))) # in atoms/b-cm tot_atom_density += abs(atom_densities[-1]) else: for i in range(len(mass_fracs)): atom_densities.append(0.0) # generate summary information which is by element Z_list = [] sym_list = [] summary_mf = [] summary_af = [] summary_ad = [] photon_ZZZAAA_ids = [] elm_ZA_list = [] for i in range(len(ZZZAAA_ids)): Z = int(str(ZZZAAA_ids[i])[:-3]) photon_ZZZAAA_ids.append(1000 * Z) if Z not in Z_list: Z_list.append(Z) elm_ZA_list.append(1000 * Z) sym_list.append(Element_Z_to_Sym(Z)) summary_mf.append(mass_fracs[i]) summary_af.append(atom_fracs[i]) summary_ad.append(atom_densities[i]) else: zi = Z_list.index(Z) summary_mf[zi] += mass_fracs[i] summary_af[zi] += atom_fracs[i] summary_ad[zi] += atom_densities[i] # compile into dictionary new_mat.update({'summary': {'element': sym_list, 'neutron ZA': elm_ZA_list, 'photon ZA': elm_ZA_list, 'weight fraction': summary_mf, 'atom fraction': summary_af, 'atom density': summary_ad}}) if not total_atom_density: if density and isfloat(density): new_mat.update({'total atom density': tot_atom_density}) else: new_mat.update({'total atom density': '-'}) pars = ['neutrons', 'photons'] par_ZAs = [ZZZAAA_ids, photon_ZZZAAA_ids] for pi in range(len(pars)): par = pars[pi] ZA_list = par_ZAs[pi] new_mat.update({par: {'weight fraction': {'ZA': ZA_list, 'value': mass_fracs}, 'atom fraction': {'ZA': ZA_list, 'value': atom_fracs}, 'atom density': {'ZA': ZA_list, 'value': atom_densities}}}) # With GUI, ask user to confirm overwriting of existing material if rewrite_existing_material or (add_new_material and duplicate_entry_found and override_duplicate_name): if rewrite_existing_material: this_matid = rewrite_matid else: this_matid = duplicate_entry_index+1 existing_mat_str = write_descripive_material_entry(all_mats_list[this_matid - 1], this_matid) new_mat_str = write_descripive_material_entry(new_mat, this_matid) r = tkinter.Tk() r.title("Overwrite existing entry in "+str(json_filepath)+" ?") l1 = tkinter.Label(r, text="Would you like to overwrite the old entry (left) with the new entry (right)?") l1.grid(row=0, column=0, sticky=tkinter.W, padx=10, pady=10) def clickButton1(): global yn_response yn_response = True r.destroy() return yn_response def clickButton2(): global yn_response yn_response = False r.destroy() return yn_response f1 = tkinter.Frame(r) # button widget b1 = tkinter.Button(f1, text="Yes", command=clickButton1) b2 = tkinter.Button(f1, text="No", command=clickButton2) # arranging button widgets f1.grid(row=0, column=1, padx=10, pady=10, sticky=tkinter.W) b1.pack(side="left", padx=10) b2.pack(side="right", padx=10) # Text of old and new entries old_entry = tkinter.Label(r, text=existing_mat_str) new_entry = tkinter.Label(r, text=new_mat_str) # , justify='left' old_entry.grid(row=1, column=0, sticky=tkinter.W + tkinter.N, padx=10, pady=10) new_entry.grid(row=1, column=1, sticky=tkinter.W + tkinter.N, padx=10, pady=10) # b1.grid(row = 2, column = 0, padx = 10, pady = 10, sticky = tkinter.E) # b2.grid(row = 2, column = 1, padx = 10, pady = 10, sticky = tkinter.W) # infinite loop which can be terminated # by keyboard or mouse interrupt tkinter.mainloop() # r.option_add('*Dialog.msg.font', 'Courier New 16') # response = messagebox.askyesno(r,"Python - material overwrite","Would you like to overwrite the following entry:\n"+'\nwith the new one below?\n') if not yn_response: sys.exit() if add_new_material: # add the new material to the library list if duplicate_entry_found: if override_duplicate_name: all_mats_list[duplicate_entry_index] = new_mat else: all_mats_list.append(new_mat) elif rewrite_existing_material: all_mats_list[rewrite_matid - 1] = new_mat # Save new materials list materials_dict_list_to_json(all_mats_list, json_filepath=json_filepath) # Update text files if update_descriptive_file: lib_filepath = Path(json_filepath.parent, json_filepath.stem + '.txt') write_descriptive_file(all_mats_list, lib_filepath=lib_filepath) if update_MC_formated_files: lib_filepath = Path(json_filepath.parent, json_filepath.stem + '.txt') write_mc_formated_files(all_mats_list, lib_filepath=lib_filepath) if update_general_MC_file: lib_filepath = Path(json_filepath.parent, json_filepath.stem + '_general.txt') write_general_mc_file(all_mats_list, lib_filepath=lib_filepath) if save_backup_list: def time_stamped(fname, fmt='{fname}_%Y-%m-%d-%H%M'): ''' requires import datetime ''' return datetime.datetime.now().strftime(fmt).format(fname=fname) backup_lib_filename = Path(json_filepath.parent, 'backup_lists/', time_stamped(json_filepath.stem) + '.json') backup_lib_filename.parent.mkdir(parents=True, exist_ok=True) # create backup_lists directory if it doesn't already exist materials_dict_list_to_json(all_mats_list, json_filepath=backup_lib_filename) return None def setup_local_mc_materials_directory(phits_tools_module_path=None)-
Description
This function copies the "MC_materials" directory distributed with PHITS Tools to a different local directory (outside of the "PHITS-Tools" directory created when installing PHITS Tools via
pip); specifically, it does the following:- Confirmation of existence of a local
MC_materialsdirectory; if not found: - Creation of local
MC_materialsdirectory:- In the same directory as
PHITS_tools.py(or one directory up) is aMC_materials/directory. If it does not exist already, a new local directory, outside the PHITS Tools installation, will be created atpathlib.Path.home()/ '.PHITS-Tools' / 'MC_materials'.- On Windows, this defaults to
C:\Users\USERNAME\.PHITS-Tools\MC_materials - On MacOS and Linux, this defaults to
/home/USERNAME/.PHITS-Tools/MC_materials
- On Windows, this defaults to
- The contents of the original distributed
MC_materialsdirectory are copied to this new local directory.
- In the same directory as
- This directory will be used as the data source for not only
update_materials_database_files()but also as the new default data source for thefetch_MC_material()function in PHITS Tools. - This local directory is created to allow creation and curation of user MC materials databases locally without fear
of being overwritten/deleted when updating PHITS Tools to a new version via
pip install PHITS-Tools --upgrade.
Inputs
phits_tools_module_path= string or Path object denoting the path to thePHITS_tools.pyPython module file. If this is left asNone, an attempt will be made to find it automatically viapkgutil.get_loader("PHITS_tools").get_filename().
Outputs
user_data_dir= Path object pointing to the found existing or newly created localMC_materialsdirectory
Expand source code
def setup_local_mc_materials_directory(phits_tools_module_path=None): r''' Description: This function copies the "MC_materials" directory distributed with PHITS Tools to a different local directory (outside of the "PHITS-Tools" directory created when installing PHITS Tools via `pip`); specifically, it does the following: - Confirmation of existence of a local `MC_materials` directory; if not found: - Creation of local `MC_materials` directory: - In the same directory as `PHITS_tools.py` (or one directory up) is a `MC_materials/` directory. If it does not exist already, a new local directory, outside the PHITS Tools installation, will be created at [`pathlib.Path.home()`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.home)` / '.PHITS-Tools' / 'MC_materials'`. - On Windows, this defaults to `C:\Users\USERNAME\.PHITS-Tools\MC_materials` - On MacOS and Linux, this defaults to `/home/USERNAME/.PHITS-Tools/MC_materials` - The contents of the original [distributed `MC_materials` directory](https://github.com/Lindt8/PHITS-Tools/tree/main/MC_materials) are copied to this new local directory. - This directory will be used as the data source for not only `update_materials_database_files` but also as the new default data source for the [`fetch_MC_material()`](https://lindt8.github.io/PHITS-Tools/#PHITS_tools.fetch_MC_material) function in PHITS Tools. - This local directory is created to allow creation and curation of user MC materials databases locally without fear of being overwritten/deleted when updating PHITS Tools to a new version via `pip install PHITS-Tools --upgrade`. Inputs: - `phits_tools_module_path` = string or Path object denoting the path to the `PHITS_tools.py` Python module file. If this is left as `None`, an attempt will be made to find it automatically via [`pkgutil.get_loader`](https://docs.python.org/3/library/pkgutil.html#pkgutil.get_loader)`("PHITS_tools").get_filename()`. Outputs: - `user_data_dir` = Path object pointing to the found existing or newly created local `MC_materials` directory ''' user_data_dir = Path.home() / '.PHITS-Tools' / 'MC_materials/' if not user_data_dir.exists(): import pkgutil import shutil # First check to see if MC_materials distributed directory exists (this script should be sitting in it...) if phits_tools_module_path is None: phits_tools_module_path = pkgutil.get_loader("PHITS_tools").get_filename() mc_materials_dist_path = Path(phits_tools_module_path).parent / 'MC_materials/' if not mc_materials_dist_path.exists(): mc_materials_dist_path = Path(phits_tools_module_path).parent / '..' / 'MC_materials/' if mc_materials_dist_path.exists(): # Need to create initial local user data directory #user_data_dir.mkdir(parents=True, exist_ok=True) # and then copy contents of the distributed MC_materials directory into it shutil.copytree(mc_materials_dist_path, user_data_dir, ignore=shutil.ignore_patterns('__*__')) print('Copied contents of', mc_materials_dist_path, 'to new local directory', user_data_dir) # With only JSON files distributed, go ahead and create the text files for the local database print('Creating local .txt versions of default MC materials databases...') # PNNL base library #mat_list = pnnl_lib_csv_to_dict_list(path_to_materials_compendium_csv=mc_materials_dist_path/'materials_compendium.csv') #materials_dict_list_to_json(mat_list, json_filepath=mc_materials_dist_path/'PNNL_materials_compendium.json') mat_list = materials_json_to_dict_list(json_filepath=user_data_dir/'PNNL_materials_compendium.json') header_text = 'This file was assembled from the Compendium of Material Composition Data for' + '\n' header_text += 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n' header_text += 'Pacific Northwest National Laboratory.' + '\n' header_text += 'This file seeks to just compile the core information of the compendium in an' + '\n' header_text += 'easily accessible plain-text format. The full document can be found at: ' + '\n' header_text += r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n' if not (user_data_dir/'PNNL_materials_compendium.txt').exists(): write_descriptive_file(mat_list, lib_filepath=user_data_dir/'PNNL_materials_compendium.txt', header_text=header_text) if not (user_data_dir / 'PNNL_materials_compendium_by_atom_fraction_for_neutrons.txt').exists(): write_mc_formated_files(mat_list, lib_filepath=user_data_dir/'PNNL_materials_compendium.txt', header_text=header_text) if not (user_data_dir / 'PNNL_materials_compendium_general.txt').exists(): write_general_mc_file(mat_list, lib_filepath=user_data_dir/'PNNL_materials_compendium_general.txt', header_text=header_text) # User-customized library mat_list = materials_json_to_dict_list(json_filepath=user_data_dir/'Compiled_MC_materials.json') if not (user_data_dir/'Compiled_MC_materials.txt').exists(): write_descriptive_file(mat_list, lib_filepath=user_data_dir/'Compiled_MC_materials.txt') if not (user_data_dir / 'Compiled_MC_materials_by_atom_fraction_for_neutrons.txt').exists(): write_mc_formated_files(mat_list, lib_filepath=user_data_dir/'Compiled_MC_materials.txt') if not (user_data_dir / 'Compiled_MC_materials_general.txt').exists(): write_general_mc_file(mat_list, lib_filepath=user_data_dir/'Compiled_MC_materials_general.txt') return user_data_dir - Confirmation of existence of a local
def pnnl_lib_csv_to_dict_list(path_to_materials_compendium_csv=(Path.cwd()/'PHITS-Tools/materials_compendium.csv'))-
Description
This function converts a CSV file of the PNNL Compendium of Material Composition Data for Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, to a Python dictionary object. The PNNL library's CSV file was obtained from PYNE: https://github.com/pyne/pyne/blob/develop/pyne/dbgen/materials_compendium.csv
Inputs
path_to_materials_compendium_csv= string or Path object denoting the path to the "materials_compendium.csv" file
Outputs
mat_list= a list of dictionary objects of the extracted materials information
Expand source code
def pnnl_lib_csv_to_dict_list(path_to_materials_compendium_csv=(Path.cwd()/'materials_compendium.csv')): r''' Description: This function converts a CSV file of the PNNL Compendium of Material Composition Data for Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, to a Python dictionary object. The PNNL library's CSV file was obtained from PYNE: https://github.com/pyne/pyne/blob/develop/pyne/dbgen/materials_compendium.csv Inputs: - `path_to_materials_compendium_csv` = string or Path object denoting the path to the "materials_compendium.csv" file Outputs: - `mat_list` = a list of dictionary objects of the extracted materials information ''' def is_line_not_empty(line): if any([i != '' for i in line]): return True return False f = open(path_to_materials_compendium_csv, 'r', newline='', encoding="utf-8") csv_reader = csv.reader(f, delimiter=',', quotechar='"') lines = list(filter(is_line_not_empty, csv_reader)) f.close() # isolate just the lines we care about mat_dict_list = [] matno = 1 in_core_lines = False in_element_section = False in_mcnp_neutrons = False in_mcnp_photons = False for line in lines: nostr = (str(matno) + '.') if nostr in line[0][:len(nostr)]: mat = {} mat.update({'PNNL_number': int(line[0].replace('.', ''))}) mat.update({'name': line[1]}) mat.update({'source': 'PNNL-15870 Rev. 1'}) mat.update({'source_short': 'PNNL'}) matno += 1 in_core_lines = True elif line[0] == 'Formula =': mat.update({'formula': line[1]}) mat.update({'molecular weight': line[6]}) elif line[0] == 'Density (g/cm3) =': mat.update({'density': line[2]}) mat.update({'total atom density': line[6]}) elif line[0] == 'Element': in_element_section = True mat.update({'summary': {'element': [], 'neutron ZA': [], 'photon ZA': [], 'weight fraction': [], 'atom fraction': [], 'atom density': []}}) continue elif in_element_section: if line[0] == 'Total': in_element_section = False elif line[0] == 'MCNP Form': in_mcnp_section = True elif line[0] == 'Neutrons' and in_mcnp_section: in_mcnp_neutrons = True mat.update({'neutrons': {'weight fraction': {'ZA': [], 'value': []}, 'atom fraction': {'ZA': [], 'value': []}, 'atom density': {'ZA': [], 'value': []}}}) if in_element_section: mat['summary']['element'].append(line[0]) mat['summary']['neutron ZA'].append(line[1]) mat['summary']['photon ZA'].append(line[2]) mat['summary']['weight fraction'].append(line[3]) mat['summary']['atom fraction'].append(line[4]) mat['summary']['atom density'].append(line[5]) if in_mcnp_neutrons: if line[0] == 'Photons': in_mcnp_neutrons = False in_mcnp_photons = True mat.update({'photons': {'weight fraction': {'ZA': [], 'value': []}, 'atom fraction': {'ZA': [], 'value': []}, 'atom density': {'ZA': [], 'value': []}}}) else: mat['neutrons']['weight fraction']['ZA'].append(line[1]) mat['neutrons']['weight fraction']['value'].append(line[2]) mat['neutrons']['atom fraction']['ZA'].append(line[3]) mat['neutrons']['atom fraction']['value'].append(line[4]) mat['neutrons']['atom density']['ZA'].append(line[5]) mat['neutrons']['atom density']['value'].append(line[6]) if in_mcnp_photons: if line[0] == 'CEPXS Form:': in_mcnp_photons = False in_mcnp_section = False # wrap up entry mat_dict_list.append(mat) else: mat['photons']['weight fraction']['ZA'].append(line[1]) mat['photons']['weight fraction']['value'].append(line[2]) mat['photons']['atom fraction']['ZA'].append(line[3]) mat['photons']['atom fraction']['value'].append(line[4]) mat['photons']['atom density']['ZA'].append(line[5]) mat['photons']['atom density']['value'].append(line[6]) return mat_dict_list def materials_dict_list_to_json(mat_list, json_filepath=(Path.cwd()/'PHITS-Tools/materials_compendium.json'))-
Description
This function converts the materials list of dictionaries into a JSON file.
Inputs
mat_dict_list= a list of dictionary objects of the extracted materials informationjson_filepath= string or Path object denoting the path to the JSON materials file to be written
Outputs
None; the materials data will be saved tojson_filepath.
Expand source code
def materials_dict_list_to_json(mat_list,json_filepath=(Path.cwd()/'materials_compendium.json')): r''' Description: This function converts the materials list of dictionaries into a JSON file. Inputs: - `mat_dict_list` = a list of dictionary objects of the extracted materials information - `json_filepath` = string or Path object denoting the path to the JSON materials file to be written Outputs: - `None`; the materials data will be saved to `json_filepath`. ''' with open(json_filepath, 'w') as f: json.dump(mat_list, f) print('Materials library file written:', json_filepath) return None def materials_json_to_dict_list(json_filepath=(Path.cwd()/'PHITS-Tools/materials_compendium.json'))-
Description
This function converts the materials list of dictionaries into a JSON file.
Inputs
json_filepath= string or Path object denoting the path to the JSON materials file
Outputs
mat_list= a list of dictionary objects of the extracted materials information
Expand source code
def materials_json_to_dict_list(json_filepath=(Path.cwd()/'materials_compendium.json')): r''' Description: This function converts the materials list of dictionaries into a JSON file. Inputs: - `json_filepath` = string or Path object denoting the path to the JSON materials file Outputs: - `mat_list` = a list of dictionary objects of the extracted materials information ''' with open(json_filepath, "r") as f: mat_list = json.load(f) return mat_list def write_descripive_material_entry(mat, mati)-
Description
Generate a descriptive text block for a material dictionary object.
Inputs
mat= a dictionary object formatted like all entries in the librarymati= integer specifying material number within the database
Outputs
entry_text= a string of formatted text with all information about the material
Expand source code
def write_descripive_material_entry(mat,mati): r''' Description: Generate a descriptive text block for a material dictionary object. Inputs: - `mat` = a dictionary object formatted like all entries in the library - `mati` = integer specifying material number within the database Outputs: - `entry_text` = a string of formatted text with all information about the material ''' banner_width = 80 if 'PNNL_number' in mat: # entries are strings already summary_table_format_string = ' {:9} {:12} {:12} {:13} {:13} {:13} \n' mcnp_table_format_string = ' {:7} {:13} {:7} {:13} {:7} {:13} \n' else: # entries are ints/floats summary_table_format_string = ' {:9} {:<12d} {:<12d} {:<13.6f} {:<13.6f} {:<13.6f} \n' mcnp_table_format_string = ' {:<7d} {:<13.6f} {:<7d} {:<13.6f} {:<7d} {:<13.6f} \n' entry_text = '\n' + '*' * banner_width + '\n' entry_text += ' {:<3d} : {} \n'.format(mati, mat['name']) entry_text += ' Source = {} \n'.format(mat['source']) entry_text += ' Formula = {} \n'.format(mat['formula']) entry_text += ' Molecular weight (g/mole) = {} \n'.format(mat['molecular weight']) entry_text += ' Density (g/cm3) = {} \n'.format(mat['density']) if isinstance(mat['total atom density'], str): entry_text += ' Total atom density (atoms/b-cm) = {} \n'.format(mat['total atom density']) else: entry_text += ' Total atom density (atoms/b-cm) = {:<13.4E} \n'.format(mat['total atom density']) entry_text += '-' * banner_width + '\n' entry_text += ' Elemental composition \n' entry_text += ' {:9} {:12} {:12} {:13} {:13} {:13} \n'.format("Element", "Neutron ZA", "Photon ZA", "Weight frac.", "Atom frac.", "Atom density") for j in range(len(mat['summary']['element'])): entry_text += summary_table_format_string.format(mat['summary']['element'][j], mat['summary']['neutron ZA'][j], mat['summary']['photon ZA'][j], mat['summary']['weight fraction'][j], mat['summary']['atom fraction'][j], mat['summary']['atom density'][j]) pars = ['neutrons', 'photons'] for pi in range(len(pars)): par = pars[pi] entry_text += '-' * banner_width + '\n' entry_text += ' PHITS/MCNP formatted ({}) \n'.format(par) entry_text += ' {:^17} {:^17} {:^17} \n'.format("Weight Fractions", "Atom Fractions", "Atom Densities") for j in range(len(mat[par]['weight fraction']['ZA'])): entry_text += mcnp_table_format_string.format(mat[par]['weight fraction']['ZA'][j], mat[par]['weight fraction']['value'][j], mat[par]['atom fraction']['ZA'][j], mat[par]['atom fraction']['value'][j], mat[par]['atom density']['ZA'][j], mat[par]['atom density']['value'][j]) entry_text += '*' * banner_width + '\n' return entry_text def write_mc_material_entry(mat, mati, particle_format='neutrons', concentration_format='weight fraction', comment_char='$')-
Description
Generate a MCNP/PHITS-formatted text block for a material dictionary object.
Inputs
mat= a dictionary object formatted like all entries in the librarymati= integer specifying material number within the databaseparticle_format= (D='neutrons') string denoting how material compositions are formatted; select'neutrons'for full isotopic composition or'photons'for just elemental compositions (natural abundances)concentration_format= (D='weight fraction') string denoting how material concentrations are formatted; select either'weight fraction'or'atom fraction'.comment_char= (D='$') string denoting the comment character to use (Note:'$'works for both PHITS and MCNP.)
Outputs
entry_text= a string of MCNP/PHITS-formatted text with material composition information
Expand source code
def write_mc_material_entry(mat,mati,particle_format='neutrons',concentration_format='weight fraction',comment_char='$'): r''' Description: Generate a MCNP/PHITS-formatted text block for a material dictionary object. Inputs: - `mat` = a dictionary object formatted like all entries in the library - `mati` = integer specifying material number within the database - `particle_format` = (D=`'neutrons'`) string denoting how material compositions are formatted; select `'neutrons'` for full isotopic composition or `'photons'` for just elemental compositions (natural abundances) - `concentration_format` = (D=`'weight fraction'`) string denoting how material concentrations are formatted; select either `'weight fraction'` or `'atom fraction'`. - `comment_char` = (D=`'$'`) string denoting the comment character to use (Note: `'$'` works for both PHITS and MCNP.) Outputs: - `entry_text` = a string of MCNP/PHITS-formatted text with material composition information ''' from PHITS_tools import Element_Z_to_Sym concentration_formats = ['atom fraction', 'weight fraction'] particle_formats = ['neutrons', 'photons'] par = particle_format conctype = concentration_format if conctype not in concentration_formats: raise ValueError("Invalid concentration format. Expected `conctype` to be one of: %s" % concentration_formats) if par not in particle_formats: raise ValueError("Invalid particle format. Expected `par` to be one of: %s" % particle_formats) cc = comment_char #mati = i + 1 # counting number for material banner_width = 60 entry_text = '\n' + cc + '*' * banner_width + '\n' entry_text += cc + ' {:<3d} : {} \n'.format(mati, mat['name']) if mat['source'] and mat['source'] != '-': entry_text += cc + ' Source = {} \n'.format(mat['source']) if mat['formula'] and mat['formula'] != '-': entry_text += cc + ' Formula = {} \n'.format(mat['formula']) if mat['molecular weight'] and mat['molecular weight'] != '-': entry_text += cc + ' Molecular weight (g/mole) = {} \n'.format(mat['molecular weight']) if mat['density'] and mat['density'] != '-': entry_text += cc + ' Density (g/cm3) = {} \n'.format(mat['density']) if mat['total atom density'] and mat['total atom density'] != '-': if isinstance(mat['total atom density'], str): entry_text += cc + ' Total atom density (atoms/b-cm) = {} \n'.format(mat['total atom density']) else: entry_text += cc + ' Total atom density (atoms/b-cm) = {:<13.4E} \n'.format(mat['total atom density']) entry_text += cc + ' Composition by {} \n'.format(conctype) for j in range(len(mat[par][conctype]['ZA'])): if isinstance(mat[par][conctype]['value'][j], str): entry_format = '{:4} {:>7} {:13} ' + cc + ' {}' + '\n' else: entry_format = '{:4} {:>7d} {:<13.6f} ' + cc + ' {}' + '\n' if j == 0: mstr = 'M{:<3}'.format(mati) else: mstr = ' ' * 4 ZZZAAA = mat[par][conctype]['ZA'][j] if ZZZAAA == '-': ZZZAAA = mat['photons'][conctype]['ZA'][j] Z = int(str(ZZZAAA)[:-3]) A = str(ZZZAAA)[-3:] sym = Element_Z_to_Sym(Z) if A != '000': isotope = sym + '-' + A.lstrip('0') else: isotope = sym entry_text += entry_format.format(mstr, ZZZAAA, mat[par][conctype]['value'][j], isotope) entry_text += cc + '*' * banner_width + '\n' return entry_text def write_descriptive_file(mat_list, lib_filepath=(Path.cwd()/'PHITS-Tools/MC_materials.txt'), header_text='', write_index_file=True)-
Description
Generates a text file of descriptive text blocks for a list of material dictionary objects.
Inputs
mat_list= a list of dictionary objects of the extracted materials informationlib_filepath= string or Path object denoting the path to the materials library text file to be savedheader_text= a string of text appearing at the very top of the output text file If left blank (''), a default header string as defined at the start of this function will be used.write_index_file= (D=True) Boolean specifying if an index file, just listing the materials contained in the outputted file along with their ID number and data source (tab delimited), should also be written. IfTrue, this file will have the same filepath aslib_filepathbut with'_index'appended to its basename.
Outputs
None; the materials text data will be saved tolib_filepath.
Expand source code
def write_descriptive_file(mat_list,lib_filepath=Path(Path.cwd(),'MC_materials.txt'),header_text='',write_index_file=True): r''' Description: Generates a text file of descriptive text blocks for a list of material dictionary objects. Inputs: - `mat_list` = a list of dictionary objects of the extracted materials information - `lib_filepath` = string or Path object denoting the path to the materials library text file to be saved - `header_text` = a string of text appearing at the very top of the output text file If left blank (`''`), a default header string as defined at the start of this function will be used. - `write_index_file` = (D=`True`) Boolean specifying if an index file, just listing the materials contained in the outputted file along with their ID number and data source (tab delimited), should also be written. If `True`, this file will have the same filepath as `lib_filepath` but with `'_index'` appended to its basename. Outputs: - `None`; the materials text data will be saved to `lib_filepath`. ''' lib_text = '' if header_text=='': header_text = ' ' + 'This file was assembled from a variety of sources over time;' + '\n' header_text += ' ' + 'it just seeks to compile information for materials to be used in Monte Carlo ' + '\n' header_text += ' ' + 'particle transport calculations in an easily accessible plain-text format. ' + '\n' header_text += ' ' + 'The first 372 entries are from the Compendium of Material Composition Data for' + '\n' header_text += ' ' + 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n' header_text += ' ' + 'Pacific Northwest National Laboratory. That document can be found at:' + '\n' header_text += ' ' + r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n' header_text += ' ' + 'The sources for other entries are specified.' + '\n' lib_text += header_text for i in range(len(mat_list)): mat = mat_list[i] mati = i + 1 entry_text = write_descripive_material_entry(mat, mati) lib_text += entry_text # save file f = open(lib_filepath, 'w+') f.write(lib_text) f.close() print('Materials library file written:', lib_filepath) # Make an index if write_index_file: index_text = 'ID\tName\tSource\n' for i in range(len(mat_list)): mat = mat_list[i] index_text += '{}\t{}\t{}\n'.format(str(i + 1), mat['name'], mat['source']) lib_filepath = Path(lib_filepath) fpath = Path(lib_filepath.parent, lib_filepath.stem + '_index' + lib_filepath.suffix) f = open(fpath, 'w+') f.write(index_text) f.close() return None def write_mc_formated_files(mat_list, lib_filepath=(Path.cwd()/'PHITS-Tools/MC_materials.txt'), comment_char='$', header_text='')-
Description
Generates four text files of MCNP/PHITS-formatted materials section text blocks for a list of material dictionary objects. The four files cover every combination of concentration formats
['atom fraction', 'weight fraction']and particle formats['neutrons', 'photons'].Inputs
mat_list= a list of dictionary objects of the extracted materials informationlib_filepath= string or Path object denoting the basic path to the materials library text files to be savedcomment_char= (D='$') string denoting the comment character to use (Note:'$'works for both PHITS and MCNP.)header_text= a string of text appearing at the very top of the output text files. If left blank (''), a default header string as defined at the start of this function will be used.
Outputs
None; the materials text data will be saved to four files atlib_filepathwith_by_[atom/weight]_fraction_for_[neutrons/photons]appended to the end of the filename.
Expand source code
def write_mc_formated_files(mat_list, lib_filepath=(Path.cwd()/'MC_materials.txt'), comment_char='$', header_text=''): r''' Description: Generates four text files of MCNP/PHITS-formatted materials section text blocks for a list of material dictionary objects. The four files cover every combination of concentration formats `['atom fraction', 'weight fraction']` and particle formats `['neutrons', 'photons']`. Inputs: - `mat_list` = a list of dictionary objects of the extracted materials information - `lib_filepath` = string or Path object denoting the basic path to the materials library text files to be saved - `comment_char` = (D=`'$'`) string denoting the comment character to use (Note: `'$'` works for both PHITS and MCNP.) - `header_text` = a string of text appearing at the very top of the output text files. If left blank (`''`), a default header string as defined at the start of this function will be used. Outputs: - `None`; the materials text data will be saved to four files at `lib_filepath` with `_by_[atom/weight]_fraction_for_[neutrons/photons]` appended to the end of the filename. ''' cc = comment_char if header_text=='': header_text = cc + ' ' + 'This file was assembled from a variety of sources over time;' + '\n' header_text += cc + ' ' + 'it just seeks to compile information for materials to be used in Monte Carlo ' + '\n' header_text += cc + ' ' + 'particle transport calculations in an easily accessible plain-text format. ' + '\n' header_text += cc + ' ' + 'The first 372 entries are from the Compendium of Material Composition Data for' + '\n' header_text += cc + ' ' + 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n' header_text += cc + ' ' + 'Pacific Northwest National Laboratory. That document can be found at:' + '\n' header_text += cc + ' ' + r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n' header_text += cc + ' ' + 'The sources for other entries are specified.' + '\n' lib_filepath = Path(lib_filepath) concentration_formats = ['atom fraction', 'weight fraction'] particle_formats = ['neutrons', 'photons'] for cfi in range(len(concentration_formats)): for pfi in range(len(particle_formats)): conctype = concentration_formats[cfi] par = particle_formats[pfi] lib_text = header_text for i in range(len(mat_list)): mat = mat_list[i] mati = i + 1 entry_text = write_mc_material_entry(mat,mati,particle_format=par,concentration_format=conctype,comment_char=cc) lib_text += entry_text # save file fpath = Path(lib_filepath.parent, lib_filepath.stem + '_by_' + conctype.replace(' ', '_') + '_for_' + par + lib_filepath.suffix) f = open(fpath, 'w+') f.write(lib_text) f.close() print('Materials library file written:', fpath) return None def write_general_mc_file(mat_list, lib_filepath=(Path.cwd()/'PHITS-Tools/MC_materials_general.txt'), comment_char='$', header_text='')-
Description
Generates a text file of MCNP/PHITS-formatted materials section text blocks for a list of material dictionary objects. This single file is a mix of concentration and particle formats as automatically selected for most general situations. If a material entry contains a chemical formula, its concentration will be specified by atom fraction; otherwise, weight fraction will be used. If any of the "neutron keywords" (
['depleted', 'enriched', ' heu', ' leu', 'uranium', 'plutonium', 'uranyl']) appear in a material's name (case insensitive), the "neutrons" particle-formatted data is used (isotopic compositions specified); otherwise, the "photons" particle format (natural abundances for elements) is used.Inputs
mat_list= a list of dictionary objects of the extracted materials informationlib_filepath= string or Path object denoting the basic path to the materials library text file to be savedcomment_char= (D='$') string denoting the comment character to use (Note:'$'works for both PHITS and MCNP.)header_text= a string of text appearing at the very top of the output text files. If left blank (''), a default header string as defined at the start of this function will be used.
Outputs
None; the materials text data will be saved tolib_filepath.
Expand source code
def write_general_mc_file(mat_list,lib_filepath=Path(Path.cwd(),'MC_materials_general.txt'),comment_char='$',header_text=''): r''' Description: Generates a text file of MCNP/PHITS-formatted materials section text blocks for a list of material dictionary objects. This single file is a mix of concentration and particle formats as automatically selected for most general situations. If a material entry contains a chemical formula, its concentration will be specified by atom fraction; otherwise, weight fraction will be used. If any of the "neutron keywords" (`['depleted', 'enriched', ' heu', ' leu', 'uranium', 'plutonium', 'uranyl']`) appear in a material's name (case insensitive), the "neutrons" particle-formatted data is used (isotopic compositions specified); otherwise, the "photons" particle format (natural abundances for elements) is used. Inputs: - `mat_list` = a list of dictionary objects of the extracted materials information - `lib_filepath` = string or Path object denoting the basic path to the materials library text file to be saved - `comment_char` = (D=`'$'`) string denoting the comment character to use (Note: `'$'` works for both PHITS and MCNP.) - `header_text` = a string of text appearing at the very top of the output text files. If left blank (`''`), a default header string as defined at the start of this function will be used. Outputs: - `None`; the materials text data will be saved to `lib_filepath`. ''' from PHITS_tools import fetch_MC_material cc = comment_char if header_text=='': header_text = cc + ' ' + 'This file was assembled from a variety of sources over time;' + '\n' header_text += cc + ' ' + 'it just seeks to compile information for materials to be used in Monte Carlo ' + '\n' header_text += cc + ' ' + 'particle transport calculations in an easily accessible plain-text format. ' + '\n' header_text += cc + ' ' + 'The first 372 entries are from the Compendium of Material Composition Data for' + '\n' header_text += cc + ' ' + 'Radiation Transport Modeling (Rev. 1), PNNL-15870 Rev. 1, published by the' + '\n' header_text += cc + ' ' + 'Pacific Northwest National Laboratory. That document can be found at:' + '\n' header_text += cc + ' ' + r'https://www.pnnl.gov/main/publications/external/technical_reports/PNNL-15870Rev1.pdf' + '\n' header_text += cc + ' ' + 'The sources for other entries are specified.' + '\n' lib_text = header_text for i in range(len(mat_list)): lib_text += fetch_MC_material(i + 1, matdict=mat_list[i]) # save file f = open(lib_filepath, 'w+') f.write(lib_text) f.close() print('Materials library file written:', lib_filepath) return None