# Module Hunters_tools

This Python module contains a collection of my functions which I often utilize in other scripts and/or are just generally useful and could possibly be useful in the future.

The functions contained in this module and brief descriptions of their functions are included below.

### General Purpose Functions

• find() : return index of the first instance of a value in a list
• scan_folder_for_file() : determine if a file is present a folder or any of its subfolders
• slugify() : converts a string to a characterset usable in filenames
• hex_to_rgb() : converts a hexidecimal color string to a RGB tuple
• rgb_to_hex() : converts a RGB tuple to a hexidecimal color string
• round_up_to_next_multiple() : rounds a number up to its next multiple of some value
• round_down_to_next_multiple() : rounds a number down to its next multiple of some value
• humansize() : converts a number in bytes to a more readable unit (kB, MB, GB, etc.)
• table_array_generator() : generates an array of table values and headers
• Excel_table_generator() : generates an Excel-formatted (tab-delimited) table string
• Latex_table_generator() : generates a LaTeX-formatted table string

### Science/Engineering Functions

• time_str_to_sec_multiplier() : determine multiplier to convert a time unit to seconds
• seconds_to_dhms() : convert a time in seconds to a string of human-relatable time units
• seconds_to_ydhms() : convert a time in seconds to a string of human-relatable time units (also with years)
• seconds_to_human_time() : convert a time in seconds to the shortest string of human-relatable time units
• time_units_converter() : convert a decimal time in one unit to a decimal time in different units
• SI_prefix_converter() : returns a multiplier to convert from one SI prefix to another
• Element_Z_to_Sym() : returns elemental symbol provided the atomic number Z
• Element_Sym_to_Z() : returns an atomic number Z provided the elemental symbol
• Element_ZorSym_to_name() : returns a string of the name of an element provided its atomic number Z or symbol
• Element_ZorSym_to_mass() : returns the average atomic mass of an element provided its atomic number Z or symbol
• nuclide_to_Latex_form() : form a LaTeX-formatted string of a nuclide provided its information
• nuclide_plain_str_to_latex_str() : convert a plaintext string for a nuclide to a LaTeX formatted raw string
• nuclide_plain_str_to_ZZZAAAM() : convert a plaintext string for a nuclide to an integer ZZZAAAM value
• ZZZAAAM_to_nuclide_plain_str() : convert an integer ZZZAAAM value for a nuclide to a plaintext string
• relative_error_to_N() : convert a relative uncertainty to an "N" value (analogous to number of counts)
• N_to_relative_error() : convert an "N" value (analogous to number of counts) to a relative uncertainty
• fractional_error() : calculate the fractional error of a test value relative to a reference value
• fractional_difference() : calculate the fractional difference between two values (of equal merit)
• absolute_difference() : calculate the difference between a reference and test value
• quotient() : safely divide two values, returning 0 if either the numerator or denominator are 0
• Lorentz_gamma() : determine the Lorentz variable gamma from a velocity in cm/ns
• Lorentz_B2_from_Tn() : determine the Lorentz variable beta^2 from a neutron kinetic energy in MeV
• dist() : Cartesian distance between two N-dimensional points
• curvature() : find the curvature (N-2 values) of N 2D Cartesian points
• circumradius() : find the circumradius of 3 2D Cartesian points
• circumcenter() : find the circumcenter of 3 2D Cartesian points
• circ_solid_angle() : calculate solid angle of a source as seen by a detector, provided coordinates for all
• energy_resolution_of_peak() : calculate energy resolution (& related values) from a spectrum and range of channels
• eval_distribution() : evaluate y values of named distribution with provided x and fit parameters
• fit_distribution() : determine best fit parameters for a distribution based on initial guesses
• r_squared() : calculate R-squared value between two arrays of y values, experimental and fitted
• chi_squared() : calculate chi-squared value between two arrays of y values, experimental and fitted
• tally() : tally/histogram values (and their indices) falling within a desired binning structure
• rebinner() : rebin a set of y-data to a new x-binning structure (edges not necessarily preserved)
• calc_GCR_intensity() : calculate GCR intensity for a provided solar modulation, ion, and energy
• assemble_GCR_flux() : assemble GCR spectra for desired elements/ions
• ICRP116_effective_dose_coeff() : returns effective dose of a mono-energetic particle of some species and some geometry

### Monte Carlo transport code Functions (PHITS/MCNP)

• fetch_MC_material() : returns a string of a formatted material for MCNP or PHITS
• parse_ttrack_file() : parser for the [T-Track] output file from PHITS
• parse_tdeposit_file() : parser for the [T-Deposit] output file from PHITS
• parse_dyld_files() : parser for the *.dyld files from PHITS meant for DCHAIN
• generate_line_bar_coordinates() : convert a set of bin-wise data to line coordinates to plot normally looking like bars
• colors_list_6() : return 1 of 6 color values from ColorBrewer
• colors_list_12() : return 1 of 12 color values from ColorBrewer
• colors_list_10() : return 1 of 10 color values from the new matplotlib default from v3 1 1
• colors_list_20() : return 1 of 20 color values from a blog post by Sasha Trubetskoy
• colors_list_64() : return 1 of 64 color values from a StackOverflow comment by user Tatarize
• get_colormap() : retrieve a matplotlib colormap using just its string name
• truncate_colormap() : truncate a colormap to new upper/lower bounds to a subset of the original colormap
• makeErrorBoxes() : draw uncertainties as a box surrounding a point (can be used with/instead of crosshair-style error bars)
• fancy_plot() : very comprehensive plotter for 2D datasets, an accumulation of all of my past plotting commands/settings
• fancy_3D_plot() : very comprehensive plotter for 3D datasets on 3D axes, an accumulation of all of my past plotting commands/settings
Expand source code
'''

This Python module contains a collection of my functions which I often utilize in
other scripts and/or are just generally useful and could possibly be useful in the future.

The functions contained in this module and brief descriptions of their functions are included below.

### General Purpose Functions

- find                            : return index of the first instance of a value in a list
- scan_folder_for_file            : determine if a file is present a folder or any of its subfolders
- slugify                         : converts a string to a characterset usable in filenames
- hex_to_rgb                      : converts a hexidecimal color string to a RGB tuple
- rgb_to_hex                      : converts a RGB tuple to a hexidecimal color string
- round_up_to_next_multiple       : rounds a number up to its next multiple of some value
- round_down_to_next_multiple     : rounds a number down to its next multiple of some value
- humansize                       : converts a number in bytes to a more readable unit (kB, MB, GB, etc.)
- table_array_generator           : generates an array of table values and headers
- Excel_table_generator           : generates an Excel-formatted (tab-delimited) table string
- Latex_table_generator           : generates a LaTeX-formatted table string

### Science/Engineering Functions

- time_str_to_sec_multiplier      : determine multiplier to convert a time unit to seconds
- seconds_to_dhms                 : convert a time in seconds to a string of human-relatable time units
- seconds_to_ydhms                : convert a time in seconds to a string of human-relatable time units (also with years)
- seconds_to_human_time           : convert a time in seconds to the shortest string of human-relatable time units
- time_units_converter            : convert a decimal time in one unit to a decimal time in different units
- SI_prefix_converter             : returns a multiplier to convert from one SI prefix to another
- Element_Z_to_Sym                : returns elemental symbol provided the atomic number Z
- Element_Sym_to_Z                : returns an atomic number Z provided the elemental symbol
- Element_ZorSym_to_name          : returns a string of the name of an element provided its atomic number Z or symbol
- Element_ZorSym_to_mass          : returns the average atomic mass of an element provided its atomic number Z or symbol
- nuclide_to_Latex_form           : form a LaTeX-formatted string of a nuclide provided its information
- nuclide_plain_str_to_latex_str  : convert a plaintext string for a nuclide to a LaTeX formatted raw string
- nuclide_plain_str_to_ZZZAAAM    : convert a plaintext string for a nuclide to an integer ZZZAAAM value
- ZZZAAAM_to_nuclide_plain_str    : convert an integer ZZZAAAM value for a nuclide to a plaintext string
- relative_error_to_N             : convert a relative uncertainty to an "N" value (analogous to number of counts)
- N_to_relative_error             : convert an "N" value (analogous to number of counts) to a relative uncertainty
- fractional_error                : calculate the fractional error of a test value relative to a reference value
- fractional_difference           : calculate the fractional difference between two values (of equal merit)
- absolute_difference             : calculate the difference between a reference and test value
- quotient                        : safely divide two values, returning 0 if either the numerator or denominator are 0
- Lorentz_gamma                   : determine the Lorentz variable gamma from a velocity in cm/ns
- Lorentz_B2_from_Tn              : determine the Lorentz variable beta^2 from a neutron kinetic energy in MeV
- dist                            : Cartesian distance between two N-dimensional points
- curvature                       : find the curvature (N-2 values) of N 2D Cartesian points
- circumradius                    : find the circumradius of 3 2D Cartesian points
- circumcenter                    : find the circumcenter of 3 2D Cartesian points
- circ_solid_angle                : calculate solid angle of a source as seen by a detector, provided coordinates for all
- energy_resolution_of_peak       : calculate energy resolution (& related values) from a spectrum and range of channels
- eval_distribution               : evaluate y values of named distribution with provided x and fit parameters
- fit_distribution                : determine best fit parameters for a distribution based on initial guesses
- r_squared                       : calculate R-squared value between two arrays of y values, experimental and fitted
- chi_squared                     : calculate chi-squared value between two arrays of y values, experimental and fitted
- tally                           : tally/histogram values (and their indices) falling within a desired binning structure
- rebinner                        : rebin a set of y-data to a new x-binning structure (edges not necessarily preserved)
- calc_GCR_intensity              : calculate GCR intensity for a provided solar modulation, ion, and energy
- assemble_GCR_flux               : assemble GCR spectra for desired elements/ions
- ICRP116_effective_dose_coeff    : returns effective dose of a mono-energetic particle of some species and some geometry

### Monte Carlo transport code Functions (PHITS/MCNP)

- fetch_MC_material               : returns a string of a formatted material for MCNP or PHITS
- parse_ttrack_file               : parser for the [T-Track] output file from PHITS
- parse_tdeposit_file             : parser for the [T-Deposit] output file from PHITS
- parse_dyld_files                : parser for the *.dyld files from PHITS meant for DCHAIN

### Plotting-related Functions

- generate_line_bar_coordinates   : convert a set of bin-wise data to line coordinates to plot normally looking like bars
- colors_list_6                   : return 1 of 6 color values from ColorBrewer
- colors_list_12                  : return 1 of 12 color values from ColorBrewer
- colors_list_10                  : return 1 of 10 color values from the new matplotlib default from v3 1 1
- colors_list_20                  : return 1 of 20 color values from a blog post by Sasha Trubetskoy
- colors_list_64                  : return 1 of 64 color values from a StackOverflow comment by user Tatarize
- get_colormap                    : retrieve a matplotlib colormap using just its string name
- truncate_colormap               : truncate a colormap to new upper/lower bounds to a subset of the original colormap
- makeErrorBoxes                  : draw uncertainties as a box surrounding a point (can be used with/instead of crosshair-style error bars)
- fancy_plot                      : very comprehensive plotter for 2D datasets, an accumulation of all of my past plotting commands/settings
- fancy_3D_plot                   : very comprehensive plotter for 3D datasets on 3D axes, an accumulation of all of my past plotting commands/settings

'''
'''
Each function beings with a comment block containing the following sections:

Description:

Dependencies:

Inputs:

Outputs:

("Dependencies:" is omitted when there are none.)
'''

import unicodedata as ud
import re
import os
import sys
import pickle
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.ticker as mticker
from matplotlib import cm
from scipy.interpolate import CubicSpline, lagrange, interp1d

from mpl_toolkits.mplot3d.axis3d import Axis
import matplotlib.projections as proj
from matplotlib.colors import colorConverter, LinearSegmentedColormap
import matplotlib.ticker as ticker
from mpl_toolkits.axes_grid1 import make_axes_locatable

from munch import *
from scipy.optimize import curve_fit
from scipy.stats import chisquare

'''
**************************************************************************************************
----------------------------------- GENERAL PURPOSE FUNCTIONS ------------------------------------
**************************************************************************************************
'''

def find(target, myList):
'''
Description:
Search for and return the index of the first occurance of a value in a list.

Inputs:
- target = value to be searched for
- myList = list of values

Output:
- index of first instance of target in myList
'''
for i in range(len(myList)):
if myList[i] == target:
return i

def scan_folder_for_file(path,filename):
'''
Description:
Provided a path to a folder and a filename, determine if the file is present in the folder or any of its subfolders

Dependencies:
import os

Inputs:
- path = string, path to folder (containing files, folders, and/or nested subfolders)
- filename = string, filename to search for in folders

Outputs:
- logical variable True or False stating whether the filename exists anywhere in the specified path and it's subdirectories
'''
file_found = False
#for entry in os.scandir(final_exp_folder_path):
for root, dirs, files in os.walk(path):
for name in files:
if name == filename:
file_found = True
return file_found
return file_found

def slugify(value):
'''
Description:
Normalizes string, converts to lowercase, removes non-alpha characters,and converts spaces to hyphens.
This is useful for quickly converting TeX strings, plot titles, etc. into legal filenames.

Dependencies:
- import unicodedata as ud
- import re

Input:
- value = string to be "slugified"

Output:
- value converted to a string only consisting of characters legal in filenames
'''
old_value = value
value = str(ud.normalize('NFKD', value).encode('ascii', 'ignore'))
value = str(re.sub('[^\w\s-]', '', value).strip().lower())
value = str(re.sub('[-\s]+', '-', value))
if value[0]=='b' and old_value[0]!='b': value = value[1:] # TeX strings sometimes case resulting string to being with 'b'
return value

def hex_to_rgb(hexcol,opacity=1.0,out_of_one=False):
'''
Description:
Return (red, green, blue, opacity) for the color given as #rrggbb and optional opacity

Inputs:
- hexcol = string of hexidecimal color formatted as #rrggbb
- opacity = float between 0 and 1 specifying color opacity (D=1.0)
- out_of_one = bool which toggles whether output will be out of 255 (default, =False) or 1 (=True)
useful if you'd rather have white be (255,255,255) (default) or (1,1,1)

Outputs:
- tuple containing RGB values and opacity,  (red, green, blue, opacity)
'''
divisor = 1
if out_of_one: divisor = 255
value = hexcol.lstrip('#')
lv = len(value)
rgb = (int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
rgb = tuple(rgb)
rgbo = [rgb[0]/divisor,rgb[1]/divisor,rgb[2]/divisor,opacity]
return tuple(rgbo)

def rgb_to_hex(rgb):
'''
Description:
Return hexidecimal color string #rrggbb for the color given as a tuple (red, green, blue, opacity)

Inputs:
- rgb = tuple containing RGB values and (optionally) opacity,  (red, green, blue, opacity); note that
the opacity value is not preserved when converted to hex

Outputs:
- string of hexidecimal color formatted as #rrggbb

Notes:
Each RGB tuple value needs to be either between 0 and 1 or between 0 and 255.  If no values greater
than 1 are provided in the rgb tuple, they will be automatically renormalized to 255.
Additionally, any values outside of the bounds will be reassigned to the nearest bound value.
'''
if any(rgb[i]>255 or rgb[i]<0 for i in range(3)):
print('Warning: Check that rgb values of color tuple are within bounds [0,255].')
if any(rgb[i]>1 for i in range(3)):
nrm_fac = 1
else:
nrm_fac = 255
hex_col_str = '#%02x%02x%02x' % (max(0, min(int(nrm_fac*rgb[0]), 255)),max(0, min(int(nrm_fac*rgb[1]), 255)),max(0, min(int(nrm_fac*rgb[2]), 255)))
return hex_col_str

def round_up_to_next_multiple(value,mult=1):
'''
Description:
Round a number up to its nearest multiple of some value

Dependencies:
import numpy as np

Input:
- value = number to be rounded
- mult = multiple to be rounded to (D=1, next higher integer)

Output:
- value rounded up to its nearest multiple of mult
'''
round_val = np.ceil(value/mult)*mult
if isinstance(mult,int) or (abs(round_val)%1<0.01): round_val = int(round_val)
return round_val

def round_down_to_next_multiple(value,mult=1):
'''
Description:
Round a number down to its nearest multiple of some value

Dependencies:
import numpy as np

Input:
- value = number to be rounded
- mult = multiple to be rounded to (D=1, next lower integer)

Output:
- value rounded down to its nearest multiple of mult
'''
round_val = np.floor(value/mult)*mult
if isinstance(mult,int) or (abs(round_val)%1<0.01): round_val = int(round_val)
return round_val

def humansize(nbytes):
'''
Description:
Provided an integer describing a number of bytes, return a string in more human-readable units

Dependencies:
import numpy as np

Input:
- nbytes = integer number of bytes (B)

Output:
- string containing the number converted to a human-readable number in *B
'''
suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
i = 0
while nbytes >= 1024 and i < len(suffixes)-1:
nbytes /= 1024.
i += 1
f = ('%.2f' % nbytes).rstrip('0').rstrip('.')
return '%s %s' % (f, suffixes[i])

def table_array_generator(core_table,row_headers=None,row_header_spans=None,column_headers=None,column_header_spans=None,float_formatting='{:g}'):
'''
Description:
Generates a properly populated array which can be easily converted to a table.

Dependencies:
import numpy as np

Notes:

At its core, a table at minimum consists of an array of 1x1 cells.  In addition, it may be padded to
its left and top with row and column headers, respectively, which may span multiple rows/columns.
The number of row-header columns or column-header rows can be arbitrary.

Inputs:
(required)

- core_table = an RxC array (number of rows x number of columns) or equivalent list of table values constituting
the core portion of the table composed of 1x1 cell elements

Inputs:
(optional)

- row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable:
- row_header_spans = A list (or list of lists) of the number of rows each row header spans.  If these are to
be uniform for all entries, a single value (or list of single values) may be provided instead.
- column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable:
- column_header_spans = A list (or list of lists) of the number of column each column header spans.  If these are to
be uniform for all entries, a single value (or list of single values) may be provided instead.
- float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}')

Outputs:
- table_array = an (R+num_col_heads)x(C+num_row_heads) array of strings forming the table
'''

core_num_rows, core_num_cols = np.shape(core_table)
core_table = np.array(core_table)

# identify structure of row and column headers, if included
num_row_header_columns = 0
if row_headers:
if not isinstance(row_headers,list):
print('row_headers is not a list (or list of lists), quitting...')
return None
if not row_header_spans:
print('row_header_spans must also be provided, quitting...')
return None
if isinstance(row_headers[0],list):
num_row_header_columns = len(row_headers)
else:
num_row_header_columns = 1

num_column_header_rows = 0
if column_headers:
if not isinstance(column_headers,list):
print('column_headers is not a list (or list of lists), quitting...')
return None
if not column_header_spans:
print('column_header_spans must also be provided, quitting...')
return None
if isinstance(column_headers[0],list):
num_column_header_rows = len(column_headers)
else:
num_column_header_rows = 1

nrows = core_num_rows + num_column_header_rows
ncols = core_num_cols + num_row_header_columns

# initialize table array
table_array = np.empty((nrows,ncols),dtype=object)
for ri in range(nrows):
for ci in range(ncols):
table_array[ri,ci] = ' '

# Now populate its values
# first, put core table into the bottom right of table
table_array[-core_num_rows:,-core_num_cols:] = core_table

# then row headers...
if row_headers:
for rhi in range(num_row_header_columns-1,-1,-1):
ci = rhi
if num_row_header_columns==1:
this_header_set = row_headers
this_header_spans = row_header_spans
else:
this_header_set = row_headers[rhi]
this_header_spans = row_header_spans[rhi]

if not isinstance(this_header_spans,list):
tmp = this_header_spans
this_header_spans = [tmp for i in this_header_set]

if np.sum(this_header_spans) > nrows:
print('Total span of row headers ({}) exceeds total number of rows calculated ({}); please reassess index {} of row headers lists'.format(str(int(np.sum(this_header_spans))),str(nrows),str(rhi)))
return None

ri = nrows
for hei in range(len(this_header_set)-1,-1,-1):
ri = ri - this_header_spans[hei]
if isinstance(this_header_set[hei],str):
val = this_header_set[hei]
elif isinstance(this_header_set[hei],float):
val=float_formatting.format(this_header_set[hei])
else:
val = str(this_header_set[hei])
table_array[ri,ci] = val

# then column headers...
if column_headers:
for chi in range(num_column_header_rows-1,-1,-1):
ri = chi
if num_column_header_rows==1:
this_header_set = column_headers
this_header_spans = column_header_spans
else:
this_header_set = column_headers[chi]
this_header_spans = column_header_spans[chi]

if not isinstance(this_header_spans,list):
tmp = this_header_spans
this_header_spans = [tmp for i in this_header_set]

if np.sum(this_header_spans) > ncols:
print('Total span of column headers ({}) exceeds total number of columns calculated ({}); please reassess index {} of column headers lists'.format(str(int(np.sum(this_header_spans))),str(ncols),str(chi)))
return None

ci = ncols
for hei in range(len(this_header_set)-1,-1,-1):
ci = ci - this_header_spans[hei]
if isinstance(this_header_set[hei],str):
val = this_header_set[hei]
elif isinstance(this_header_set[hei],float):
val=float_formatting.format(this_header_set[hei])
else:
val = str(this_header_set[hei])
table_array[ri,ci] = val

return table_array

def Excel_table_generator(core_table,title=None,row_headers=None,row_header_spans=None,column_headers=None,column_header_spans=None,float_formatting='{:g}'):
'''
Description:
This function generates a string containing a Excel-formatted (tab delimited) table from an input array supplemented with
other formatting and header information.

Dependencies:
- import numpy as np

Notes:
At its core, a table at minimum consists of an array of 1x1 cells.  In addition, it may be padded to
its left and top with row and column headers, respectively, which may span multiple rows/columns.
The number of row-header columns or column-header rows can be arbitrary.

Inputs:
(required)

- core_table = an RxC array (number of rows x number of columns) or equivalent list of table values
constituting the core portion of the table composed of 1x1 cell elements

Inputs:
(optional)

- title = (optional) string to be placed above the table (D=None)
- row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable:
- row_header_spans = A list (or list of lists) of the number of rows each row header spans.  If these are to
be uniform for all entries, a single value (or list of single values) may be provided instead.
- column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable:
- column_header_spans = A list (or list of lists) of the number of column each column header spans.  If these are to
be uniform for all entries, a single value (or list of single values) may be provided instead.
- float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}')

Outputs:
- tab_str = text string containing the formatted table
'''

core_table = np.array(core_table)
if row_headers or column_headers: # need to generate comprehensive table array using headers and provided core array
table_array = table_array_generator(core_table,row_headers=row_headers,row_header_spans=row_header_spans,column_headers=column_headers,column_header_spans=column_header_spans,float_formatting=float_formatting)

num_rows, num_cols = np.shape(table_array)

tab_str = ''
for ri in range(num_rows):
for ci in range(num_cols):
if isinstance(table_array[ri,ci],str):
val = table_array[ri,ci]
elif isinstance(table_array[ri,ci],float):
val=float_formatting.format(table_array[ri,ci])
else:
val = str(table_array[ri,ci])
tab_str += val + '\t'
tab_str = tab_str[:-1] +  '\n'
if title:
tab_str = title + '\n' + tab_str
return tab_str

def Latex_table_generator(core_table,title=None,row_headers=None,row_header_spans=None,column_headers=None,column_header_spans=None,
float_formatting='{:g}',table_positioning='[h]',label=None,use_table_ruling=True,coulmn_formatting=None,
hline_row_indices=None,cline_row_cstart_cend_indices_triplets = None,return_only_core_tabular_environment=False,
nest_in_ruledtabular=False,colormap=None,color_transform_fcn=None,color_scale='linear',color_min_val=None,color_max_val=None):
'''
Description:
This function generates a string containing a LaTeX-formatted table from an input array supplemented with
other formatting and header information.

Dependencies:
import numpy as np

Notes:
At its core, a table at minimum consists of an array of 1x1 cells.  In addition, it may be padded to
its left and top with row and column headers, respectively, which may span multiple rows/columns.
The number of row-header columns or column-header rows can be arbitrary.

Inputs:
(required)

- core_table = an RxC array (number of rows x number of columns) or equivalent list of table values
constituting the core portion of the table composed of 1x1 cell elements

Inputs:
(optional)

- title = (optional) string to be placed above the table (D=None)
- row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable:
- row_header_spans = A list (or list of lists) of the number of rows each row header spans.  If these are to
be uniform for all entries, a single value (or list of single values) may be provided instead.
- column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable:
- column_header_spans = A list (or list of lists) of the number of column each column header spans.  If these are to
be uniform for all entries, a single value (or list of single values) may be provided instead.
- float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}')
- table_positioning = string of table positioning argument for LaTeX (D='[h]')
- label = string of table label (D = slugify(title))
- use_table_ruling = boolean controlling automatic use of toprule, midrule, and bottomrule (D=True)
- coulmn_formatting = string controlling column borders and justification (D='{ccc...ccc}' (all columns centered))
- hline_row_indices = list (or single) of integer row indices beneath which a hline, spanning all columns,
should be drawn (D=None) (-1 places line above very first row)
- cline_row_cstart_cend_indices_triplets = list of length-3 lists (or an individual length-3 list) composed of
[row index, column start, column end] for the \cline{start-stop} command
- return_only_core_tabular_environment = boolean specifying whether the whole table (False) or just the inner
tabular environment (True) will be returned (D=False)
- nest_in_ruledtabular = boolean specifying if the tabular environment will (True) or will not (False) be
nested inside of the ruledtabular environment used by REVTeX (D=False)
- colormap = callable colormap object or string of matplotlib colormap name to be used in setting the background
color of each cell in the table.  By default, colormap = None and the table is produced normally.
Otherwise, the specified colormap will be used to color the background of each cell based on its value.
For the colors used, the values of core_table are rescaled from [min(core_table),max(core_table)]
to [0,1] and then used to sample the provided colormap.  As a special case, if colormap='default'
then colormap=truncate_colormap('bwr',0.35,0.65) is used; ref:truncate_colormap
Note that in the LaTeX document \\usepackage[table]{xcolor} must be included in the preamble for table colors to function.
- color_transform_fcn = user-supplied function called to alter each value of core_table used for color assignment, prior to
rescaling/normalization.  This only affects color assignment, not the actual value printed to the final table. (D=None)
- color_scale = string specifying whether the mapping of colors to the table values should use 'linear' (default) or 'log' scaling
- color_min_val = float/int specifying new lower bound of core_table to be used in colormap normalization; all values
less than or equal to col_min_val will be set to 0 in the array used to sample the colormap.
- color_max_val = float/int specifying new upper bound of core_table to be used in colormap normalization; all values
greater than or equal to col_max_val will be set to 1 in the array used to sample the colormap.

Outputs:
- tab_str = text string containing the formatted table
'''

core_table = np.array(core_table)
core_nrows, core_ncols = np.shape(core_table)
# ensure everything is sorted nicely into the table array
table_array = table_array_generator(core_table,row_headers=row_headers,row_header_spans=row_header_spans,column_headers=column_headers,column_header_spans=column_header_spans,float_formatting=float_formatting)
num_rows, num_cols = np.shape(table_array)
num_column_header_rows, num_row_header_columns = num_rows-core_nrows, num_cols-core_ncols

if colormap:
if isinstance(colormap,str):
if colormap=='default' or colormap=='Default' or colormap=='DEFAULT':
cmap = truncate_colormap('bwr', 0.35, 0.65)
else:
cmap = get_colormap(cmap)
else:
cmap = colormap

color_val_table = np.copy(core_table)

if color_transform_fcn!=None: # user supplied function to transform values
for ri in range(core_nrows):
for ci in range(core_ncols):
color_val_table[ri,ci] = color_transform_fcn(color_val_table[ri,ci])

if color_min_val==None:
col_min_val = np.min(color_val_table)
else:
col_min_val = color_min_val
if color_max_val==None:
col_max_val = np.max(color_val_table)
else:
col_max_val = color_max_val

color_val_table[color_val_table<col_min_val] = col_min_val
color_val_table[color_val_table>col_max_val] = col_max_val

if color_scale=='log':
if np.any(color_val_table<0):
print('Warning: col_min_val<0 & core_table contains negative values and thus cannot be color scaled logarithmically; reverting to linear color scale.')
else:
if np.any(color_val_table==0):
print('Warning: core_table contains zero values; they will be ignored while assigning colors.')
color_val_table[color_val_table==0] = np.NaN # set to NaN and use this as a flag to ignore later
color_val_table = np.log(color_val_table)
col_min_val = np.log(col_min_val)
col_max_val = np.log(col_max_val)

color_val_table = (color_val_table - col_min_val)/(col_max_val - col_min_val)

# double check values are within bounds
color_val_table[color_val_table<0] = 0
color_val_table[color_val_table>1] = 1

if hline_row_indices:
if not isinstance(hline_row_indices,list): hline_row_indices = [hline_row_indices]

if cline_row_cstart_cend_indices_triplets:
if not isinstance(cline_row_cstart_cend_indices_triplets,list):
print('cline_row_cstart_cend_indices_triplets elements must be length-3 lists of the row index, starting column, and ending column')
return None
if not isinstance(cline_row_cstart_cend_indices_triplets[0],list):
cline_row_cstart_cend_indices_triplets = [cline_row_cstart_cend_indices_triplets]

tab_str = ''

if not return_only_core_tabular_environment:
tab_str += r'\begin{table}' + table_positioning + '\n'
#tab_str += r'%\vspace*{-0.4cm}' + '\n'
if title:
tab_str += '\t' + r'\caption{'+title+'}' + '\n'
else:
tab_str += '\t' + r'%\caption{Title}' + '\n'
if label:
tab_str += '\t' + r'\label{tab:'+label+'}' + '\n'
elif title:
tab_str += '\t' + r'\label{tab:'+slugify(title)+'}' + '\n'
else:
tab_str += '\t' + r'%\label{tab:Label}' + '\n'
tab_str += '\t' + r'\centering' + '\n'

if nest_in_ruledtabular:
tab_str += '\t' + r'\begin{ruledtabular}' + '\n'

if coulmn_formatting:
tab_str += '\t' + r'\begin{tabular}' + coulmn_formatting + '\n'
else:
cstr = 'c'*num_cols
tab_str += '\t' + r'\begin{tabular}{'+cstr+'}' + '\n'

if use_table_ruling: tab_str += '\t\t' + r'\toprule' + '\n'

if hline_row_indices:
if -1 in hline_row_indices: tab_str += '\t\t' + r'\hline' + '\n'

chi_list = [0 for i in range(num_row_header_columns)]
for ri in range(num_rows):
line = ''

# handle header row separately
if ri < num_column_header_rows:
if num_column_header_rows==1:
this_header_set = column_headers
this_header_spans = column_header_spans
else:
this_header_set = column_headers[ri]
this_header_spans = column_header_spans[ri]

if not isinstance(this_header_spans,list):
tmp = this_header_spans
this_header_spans = [tmp for i in this_header_set]

for hi in range(len(this_header_set)-1,-1,-1):
if this_header_spans[hi] == 1:
line = this_header_set[hi] + ' & ' + line
else:
line = r'\multicolumn{'+'{}'.format(this_header_spans[hi])+'}{c}{' + this_header_set[hi] + '} & ' + line

if np.sum(this_header_spans) < num_cols:
for i in range(num_cols-int(np.sum(this_header_spans))):
line = ' & ' + line

tab_str += '\t\t' + line[:-2] + ' \\\\' + '\n'

if hline_row_indices:
if ri in hline_row_indices:
tab_str += '\t\t' + r'\hline' + '\n'

if cline_row_cstart_cend_indices_triplets:
for i in range(len(cline_row_cstart_cend_indices_triplets)):
if ri == cline_row_cstart_cend_indices_triplets[i][0]:
tab_str += '\t\t' + r'\cline{' + '{}-{}'.format(str(int(cline_row_cstart_cend_indices_triplets[i][1])),str(int(cline_row_cstart_cend_indices_triplets[i][2]))) + '}' + '\n'

continue

if use_table_ruling and ri==num_column_header_rows and num_column_header_rows>0:
tab_str += '\t\t' + r'\midrule' + '\n'

for ci in range(num_cols):
val = table_array[ri,ci]
if not isinstance(val,str): val = float_formatting.format(val)

# add multicolumns where appropriate
if ci < num_row_header_columns:
if num_row_header_columns==1:
this_header_set = row_headers
this_header_spans = row_header_spans
else:
this_header_set = row_headers[ci]
this_header_spans = row_header_spans[ci]

if not isinstance(this_header_spans,list):
tmp = this_header_spans
this_header_spans = [tmp for i in this_header_set]

if val != ' ' and this_header_spans[ci]>1:
line += r'\multirow{'+'{}'.format(this_header_spans[chi_list[ci]])+'}{*}{' + this_header_set[chi_list[ci]] + '} & '
chi_list[ci] += 1
else:
line += val + ' & '
continue

if colormap:
if not np.isnan(color_val_table[ri-num_column_header_rows,ci-num_row_header_columns]): # skip zero values assigned NaN when log scaled
col_rgb = cmap(color_val_table[ri-num_column_header_rows,ci-num_row_header_columns])
col_hex = rgb_to_hex(col_rgb)[1:] # remove leading '#' character
line += r'\cellcolor[HTML]{'+col_hex+'} '

line += val + ' & '

tab_str += '\t\t' + line[:-2] + ' \\\\' + '\n'

if hline_row_indices:
if ri in hline_row_indices:
tab_str += '\t\t' + r'\hline' + '\n'

if cline_row_cstart_cend_indices_triplets:
for i in range(len(cline_row_cstart_cend_indices_triplets)):
if ri == cline_row_cstart_cend_indices_triplets[i][0]:
tab_str += '\t\t' + r'\cline{' + '{}-{}'.format(str(int(cline_row_cstart_cend_indices_triplets[i][1])),str(int(cline_row_cstart_cend_indices_triplets[i][2]))) + '}' + '\n'

if use_table_ruling: tab_str += '\t\t' + r'\bottomrule' + '\n'

tab_str += '\t' + r'\end{tabular}' + '\n'
if nest_in_ruledtabular:
tab_str += '\t' + r'\end{ruledtabular}' + '\n'
if not return_only_core_tabular_environment:
tab_str += r'\end{table}' + '\n'
if return_only_core_tabular_environment:
tab_str = tab_str[1:].replace('\n\t','\n') # remove extra tab character from start of each line

return tab_str

'''
**************************************************************************************************
-------------------------------- SCIENCE/ENGINEERING FUNCTIONS -----------------------------------
**************************************************************************************************
'''

def time_str_to_sec_multiplier(time_str):
'''
Description:
Provide a time unit and this function provides what those time units need to be multiplied by to obtain seconds.

Inputs:
- time_str = string containing time units character(s) [s,m,h,d,y,ms,us,ns,ps,fs]

Outputs:
- m = multiplier to convert a time of the supplied units to seconds

'''
try:
if time_str == 's':
m = 1
elif time_str == 'm':
m = 60
elif time_str == 'h':
m = 60*60
elif time_str == 'd':
m = 60*60*24
elif time_str == 'y':
m = 60*60*24*365.25
elif time_str == 'ms':
m = 1e-3
elif time_str == 'us':
m = 1e-6
elif time_str == 'ns':
m = 1e-9
elif time_str == 'ps':
m = 1e-12
elif time_str == 'fs':
m = 1e-15
return m
except:
print('"{}" is not a valid time unit; please use one of the following: [s,m,h,d,y,ms,us,ns,ps,fs]'.format(time_str))
return None

def seconds_to_dhms(t_sec):
'''
Description:
Provide a time in seconds and obtain a string with the time in days, hours, minutes, and seconds

Inputs:
- t_sec = a time in seconds (float or int)

Outputs:
- time_str = string containing the time prettily formatted in d/h/m/s format

'''
m, s = divmod(t_sec, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)

if d != 0:
time_str = "{:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(d,h,m,s)
elif h != 0:
time_str = "{:0.0f}h {:0.0f}m {:0.2f}s".format(h,m,s)
elif m != 0:
time_str = "{:0.0f}m {:0.2f}s".format(m,s)
elif s != 0:
time_str = "{:0.2f}s".format(s)
else:
time_str = ""

return time_str

def seconds_to_ydhms(t_sec):
'''
Description:
Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds

Inputs:
- t_sec = a time in seconds (float or int)

Outputs:
- time_str = string containing the time prettily formatted in y/d/h/m/s format

'''
m, s = divmod(t_sec, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
y, d = divmod(d, 365)

if y>=4 : # if leap year occurred
n_leap_years = int(y/4)
d = d-n_leap_years

if y != 0:
time_str = "{:0.0f}y {:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(y,d,h,m,s)
elif d != 0:
time_str = "{:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(d,h,m,s)
elif h != 0:
time_str = "{:0.0f}h {:0.0f}m {:0.2f}s".format(h,m,s)
elif m != 0:
time_str = "{:0.0f}m {:0.2f}s".format(m,s)
elif s != 0:
time_str = "{:0.2f}s".format(s)
else:
time_str = ""

return time_str

def seconds_to_human_time(t_sec,time_style='mixed',abbreviation_style='shortest'):
'''
Description:
Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds,
presented in the shortest and most human-friendly/readable format

Inputs:
(required)

- t_sec = a time in seconds (float or int)

Inputs:
(optional)

- time_style = string denoting whether times are split into mixed units ('mixed', default, e.g. 1d 12h),
one value in the largest units ('single_largest', e.g. 1.5d), or one value in the largest
integer units ('single_integer', e.g. 36h)
- abbreviation_style = string denoting style of abbreviation (D='shortest'); select from: ['shortest','normal','none'],
for example: h/hr(s)/hour(s), m/min/minute(s)

Outputs:
- time_str = string containing the time prettily formatted in the selected format

'''

if abbreviation_style=='shortest':
tu_strs = ['s','m','h','d','y']
elif abbreviation_style=='normal':
tu_strs = [' sec',' min',' hr',' day',' yr']
elif abbreviation_style=='none':
tu_strs = [' second',' minute',' hour',' day',' year']
else:
print('Check value of "abbreviation_style"; assuming "shortest" was intended.')
abbreviation_style = 'shortest'
tu_strs = ['s','m','h','d','y']

m, s = divmod(t_sec, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
y, d = divmod(d, 365)

if y>=4 : # if leap year occurred
n_leap_years = int(y/4)
d = d-n_leap_years

time_str = ''

if time_style=='single_integer':
divs = [60,60,24,365.25]
t = t_sec
for i in range(len(divs)):
t_new = t/divs[i]
if not t_new.is_integer():
time_str = "{:8.4g}{:}".format(t,tu_strs[i])
if t>1.0 and ((abbreviation_style=='normal' and i!=0 and i!=1) or abbreviation_style=='none'):
time_str += 's'
break
else:
t = t_new

elif time_style=='single_largest':
divs = [365.25,24,60,60]
t_yr = t_sec/(60*60*24*365.25)
t = t_yr
for i in range(len(divs)+1):
if round(t,6)>=1.0:
time_str = "{:8.4g}{:}".format(t,tu_strs[4-i])
if t>1.0 and ((abbreviation_style=='normal' and i!=4 and i!=3) or abbreviation_style=='none'):
time_str += 's'
break
else:
t = t*divs[i]

else:
vals = [y,d,h,m,s]
for vi in range(len(vals)):
if vals[vi] != 0:
if vi==4:
time_str += " {:0.2g}{:}".format(vals[vi],tu_strs[4-vi]) # 4 = number of total time units - 1
else:
time_str += " {:0.0g}{:}".format(vals[vi],tu_strs[4-vi]) # 4 = number of total time units - 1
# make units plural if warranted
if vals[vi]>1.0 and ((abbreviation_style=='normal' and vi!=4 and vi!=3) or abbreviation_style=='none'):
time_str += 's'

if time_str=='': time_str = '0{:}'.format(tu_strs[0])
time_str = time_str.strip()

return time_str

def time_units_converter(t1_val,t1_units='s',t2_units='s'):
'''
Description:
Provide a decimal time, its current units, and the desired units to obtain a new decimal time in the desired units

Inputs:
- t1_val = a decimal time value (float or int)
- t1_units = (optional) a time units string for the input time value (D='s'); select from: ['s','m','h','d','y','ms','us','ns','ps','fs'] (str)
- t2_units = (optional) a time units string for the output time value (D='s'); select from: ['s','m','h','d','y','ms','us','ns','ps','fs'] (str)

Outputs:
- t2_val = a decimal time value in the units specified by t2_units (float)

'''
t1_sec = t1_val*time_str_to_sec_multiplier(t1_units)
t2_val = t1_sec/time_str_to_sec_multiplier(t2_units)

return t2_val

def SI_prefix_converter(SI1,SI2=''):
'''
Description:
Provides the multiplication constant needed to convert between SI prefixes

Dependencies:
find (function within the "Hunter's tools" package)

Inputs:
- SI1 = string of SI prefix of the current value
- SI2 = string of the desired SI prefix of the value (D='', no SI prefix)

These values should be selected from this list:
['y', 'z', 'a', 'f', 'p', 'n', 'u', 'm', 'c', 'd', '', 'da', 'h', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']

Outputs:
- m = multiplier to convert a value of SI prefix SI1 to one of SI prefix SI2

'''
SI_prefixes = ['y','z','a','f','p','n','u','m','c','d','','da','h','k','M','G','T','P','E','Z','Y']
SI_powers   = [-24,-21,-18,-15,-12, -9, -6, -3, -2, -1, 0,   1,  2,  3,  6,  9, 12, 15, 18, 21, 24]
if SI1 not in SI_prefixes or SI2 not in SI_prefixes:
if SI1 not in SI_prefixes and SI2 not in SI_prefixes:
pstr = 'Both SI1={} and SI2={} are not valid entries.'.format(SI1,SI2)
elif SI1 not in SI_prefixes:
pstr = 'SI1={} is not a valid entry.'.format(SI1)
elif SI1 not in SI_prefixes:
pstr = 'SI2={} is not a valid entry.'.format(SI2)
pstr += '\nPlease select from the valid SI prefixes:'
print(pstr,SI_prefixes)
return None
v1=SI_powers(find(SI1))
v2=SI_powers(find(SI2))
m = 10**(v2-v1)
return m

def Element_Z_to_Sym(Z):
'''
Description:
Returns elemental symbol for a provided atomic number Z

Inputs:
- Z = atomic number

Outputs:
- sym = string of elemental symbol for element of atomic number Z
'''
elms = ["n ",\
"H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
"Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
"Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
"Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
"Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
"Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
"Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
"Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
"Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
"Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
"Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
"Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
i = int(Z)
if i < 0 or i > len(elms):
print('Z={} is not valid, please select a number from 0 to 118 (inclusive).'.format(str(Z)))
return None
return elms[i].strip()

def Element_Sym_to_Z(sym):
'''
Description:
Returns atomic number Z for a provided elemental symbol

Dependencies:
find (function within the "Hunter's tools" package)

Inputs:
- sym = string of elemental symbol for element of atomic number Z

Outputs:
- Z = atomic number
'''
elms = ["n ",\
"H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
"Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
"Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
"Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
"Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
"Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
"Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
"Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
"Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
"Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
"Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
"Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]

if len(sym.strip())>2:
print('Please provide a valid elemental symbol (1 or 2 characters), {} is too long'.format(sym))
return -1

# handle exception for neutron first
if sym.strip()=='XX':
return 0

# make sure string is formatted to match entries in elms list
sym2 = sym.strip()
if len(sym2)==1: sym2 += ' '
sym2 = sym2[0].upper() + sym2[1].lower()

Z = find(sym2,elms)

if Z==None:
print('Z could not be found for element "{}"; please make sure entry is correct.'.format(sym))
return -1

return Z

def Element_ZorSym_to_name(Z):
'''
Description:
Returns an element's name provided its atomic number Z or elemental symbol

Inputs:
- Z = string of elemental symbol or atomic number Z

Outputs:
- name = element name
'''
element_names = ['neutron','Hydrogen','Helium','Lithium','Beryllium','Boron','Carbon','Nitrogen','Oxygen','Fluorine',
'Neon','Sodium','Magnesium','Aluminium','Silicon','Phosphorus','Sulfur','Chlorine','Argon',
'Potassium','Calcium','Scandium','Titanium','Vanadium','Chromium','Manganese','Iron','Cobalt',
'Nickel','Copper','Zinc','Gallium','Germanium','Arsenic','Selenium','Bromine','Krypton',
'Rubidium','Strontium','Yttrium','Zirconium','Niobium','Molybdenum','Technetium','Ruthenium',
'Rhodium','Palladium','Silver','Cadmium','Indium','Tin','Antimony','Tellurium','Iodine','Xenon',
'Caesium','Barium','Lanthanum','Cerium','Praseodymium','Neodymium','Promethium','Samarium',
'Europium','Gadolinium','Terbium','Dysprosium','Holmium','Erbium','Thulium','Ytterbium',
'Lutetium','Hafnium','Tantalum','Tungsten','Rhenium','Osmium','Iridium','Platinum','Gold',
'Mercury','Thallium','Lead','Bismuth','Polonium','Astatine','Radon','Francium','Radium',
'Actinium','Thorium','Protactinium','Uranium','Neptunium','Plutonium','Americium','Curium',
'Berkelium','Californium','Einsteinium','Fermium','Mendelevium','Nobelium','Lawrencium',
'Rutherfordium','Dubnium','Seaborgium','Bohrium','Hassium','Meitnerium','Darmstadtium',
'Roentgenium','Copernicium','Nihonium','Flerovium','Moscovium','Livermorium','Tennessine','Oganesson']

try:
zi = int(Z)
except:
zi = Element_Sym_to_Z(Z)

return element_names[zi]

def Element_ZorSym_to_mass(Z):
'''
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]

def nuclide_to_Latex_form(Z,A,m=''):
'''
Description:
Form a LaTeX-formatted string of a nuclide provided its information

Dependencies:
Element_Z_to_Sym (function within the "Hunter's tools" package)
(only required if inputed Z is not already an elemental symbol)

Inputs:
- Z = atomic number of nuclide (int, float, or string) or elemental symbol (string)
- A = atomic mass of nuclide (int, float, or string) or string to go in place of A (ex. 'nat')
- m = metastable state (D='', ground state); this will be appended to the end of A
if not a string already, it will be converted into one and appended to 'm' (ex. 1 -> 'm1')

Outputs:
- LaTeX-formatted raw string of a nuclide, excellent for plot titles, labels, and auto-generated LaTeX documents
'''
if isinstance(A,(int,float)): A = str(int(A))
if not isinstance(Z,str): symbol = Element_Z_to_Sym(int(Z))
if isinstance(m,float): m = int(m)
if isinstance(m,int): m = 'm' + str(m)
latex_str = r"$^{{{}{}}}$".format(A,m) + "{}".format(symbol)
return latex_str

def nuclide_plain_str_to_latex_str(nuc_str,include_Z=False):
'''
Description:
Converts a plaintext string of a nuclide to a LaTeX-formatted raw string
Note: if you already have the Z, A, and isomeric state information determined, the "nuclide_to_Latex_form" function can be used instead

Dependencies:
- Element_Z_to_Sym (function within the "Hunter's tools" package) (only required if include_Z = True)

Input:
(required)

- nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
+ Isomeric/metastable state characters must always immediately follow the atomic mass characters.
Isomeric state labels MUST either:
- (1) be a single lower-case character OR
- (2) begin with any non-numeric character and end with a number
+ Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
+ Elemental symbols MUST begin with an upper-case character

Input:
(optional)

- include_Z = True/False determining whether the nuclide's atomic number Z will be printed as a subscript beneath the atomic mass

Output:
- LaTeX-formatted raw string of nuclide
'''
tex_str = r''

# remove unwanted characters from provided string
delete_characters_list = [' ', '-', '_']
for dc in delete_characters_list:
nuc_str = nuc_str.replace(dc,'')

# determine which characters are letters versus numbers
isalpha_list = []
isdigit_list = []
for c in nuc_str:
isalpha_list.append(c.isalpha())
isdigit_list.append(c.isdigit())

symbol = ''
mass = ''
isost = ''

# string MUST begin with either mass number or elemental symbol
if isdigit_list[0] or nuc_str[0:3]=='nat': # mass first
mass_first = True
else:
mass_first = False

if mass_first:
if nuc_str[0:3]=='nat':
mass = 'nat'
ci = 3
else:
ci = 0
while isdigit_list[ci]:
mass += nuc_str[ci]
ci += 1
mass = str(int(mass)) # eliminate any extra leading zeros
# encountered a non-numeric character, end of mass
# now, determine if metastable state is listed or if element is listed next
# first, check to see if any other numerals are in string
lni = 0 # last numeral index
for i in range(ci,len(nuc_str)):
if isdigit_list[i]:
lni = i
if lni != 0:
# grab all characters between ci and last numeral as metastable state
isost = nuc_str[ci:lni+1]
ci = lni + 1
else: # no more numerals in string, now check for single lower-case letter
if isalpha_list[ci] and nuc_str[ci].islower():
isost = nuc_str[ci]
ci += 1

# Now extract elemental symbol
for i in range(ci,len(nuc_str)):
if isalpha_list[i]:
symbol += nuc_str[i]

else: # if elemental symbol is listed first
if 'nat' in nuc_str:
mass = 'nat'
nuc_str = nuc_str.replace('nat','')

ci = 0
# Extract all characters before first number as the elemental symbol
while nuc_str[ci].isalpha():
symbol += nuc_str[ci]
ci += 1

# now, extract mass
if mass != 'nat':
while nuc_str[ci].isdigit():
mass += nuc_str[ci]
ci += 1
if ci == len(nuc_str):
break

# lastly, extract isomeric state, if present
if ci != len(nuc_str):
isost = nuc_str[ci:]

# treating the cases of lowercase-specified particles (n, d, t, etc.)
if symbol == '' and isost != '':
symbol = isost
isost = ''

# Now assemble LaTeX string for nuclides
if include_Z:
if symbol == 'n':
Z = 0
elif symbol == 'p' or symbol == 'd' or symbol == 't':
Z = 1
else:
Z = Element_Sym_to_Z(symbol)
Z = str(int(Z))
tex_str = r"$^{{{}{}}}_{{{}}}$".format(mass,isost,Z) + "{}".format(symbol)
else:
tex_str = r"$^{{{}{}}}$".format(mass,isost) + "{}".format(symbol)

return tex_str

def nuclide_plain_str_to_ZZZAAAM(nuc_str):
'''
Description:
Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000\*Z + 10\*A + M

Dependencies:
Element_Z_to_Sym (function within the "Hunter's tools" package)

Input:
- nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
+ Isomeric/metastable state characters must always immediately follow the atomic mass characters.
Isomeric state labels MUST either:
- (1) be a single lower-case character OR
- (2) begin with any non-numeric character and end with a number
+ Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
+ Elemental symbols MUST begin with an upper-case character

Output:
- ZZZAAAM integer
'''

# remove unwanted characters from provided string
delete_characters_list = [' ', '-', '_']
for dc in delete_characters_list:
nuc_str = nuc_str.replace(dc,'')

# determine which characters are letters versus numbers
isalpha_list = []
isdigit_list = []
for c in nuc_str:
isalpha_list.append(c.isalpha())
isdigit_list.append(c.isdigit())

symbol = ''
mass = ''
isost = ''

if 'nat' in nuc_str:
print('Must specify a specific nuclide, not natural abundances')
return None

# string MUST begin with either mass number or elemental symbol
if isdigit_list[0]: # mass first
mass_first = True
else:
mass_first = False

if mass_first:
ci = 0
while isdigit_list[ci]:
mass += nuc_str[ci]
ci += 1
mass = str(int(mass)) # eliminate any extra leading zeros
# encountered a non-numeric character, end of mass
# now, determine if metastable state is listed or if element is listed next
# first, check to see if any other numerals are in string
lni = 0 # last numeral index
for i in range(ci,len(nuc_str)):
if isdigit_list[i]:
lni = i
if lni != 0:
# grab all characters between ci and last numeral as metastable state
isost = nuc_str[ci:lni+1]
ci = lni + 1
else: # no more numerals in string, now check for single lower-case letter
if isalpha_list[ci] and nuc_str[ci].islower():
isost = nuc_str[ci]
ci += 1

# Now extract elemental symbol
for i in range(ci,len(nuc_str)):
if isalpha_list[i]:
symbol += nuc_str[i]

else: # if elemental symbol is listed first
ci = 0
# Extract all characters before first number as the elemental symbol
while nuc_str[ci].isalpha():
symbol += nuc_str[ci]
ci += 1

# now, extract mass
while nuc_str[ci].isdigit():
mass += nuc_str[ci]
ci += 1
if ci == len(nuc_str):
break

# lastly, extract isomeric state, if present
if ci != len(nuc_str):
isost = nuc_str[ci:]

# treating the cases of lowercase-specified particles (n, d, t, etc.)
if symbol == '' and isost != '':
symbol = isost
isost = ''

if symbol == 'n':
Z = 0
elif symbol == 'p' or symbol == 'd' or symbol == 't':
Z = 1
else:
Z = Element_Sym_to_Z(symbol)

A = int(mass)

if isost.strip()=='' or isost=='g':
M = 0
elif isost=='m' or isost=='m1':
M = 1
elif isost=='n' or isost=='m2':
M = 2
elif isost=='o' or isost=='m3':
M = 3
elif isost=='p' or isost=='m4':
M = 4
elif isost=='q' or isost=='m5':
M = 5
else:
print("Unknown isomeric state {}, assumed ground state".format(isost))
M = 0

ZZZAAAM = 10000*Z + 10*A + M

return ZZZAAAM
nuclide_plain_str_ZZZAAAM = nuclide_plain_str_to_ZZZAAAM # backwards compatibility with old version of function name

def ZZZAAAM_to_nuclide_plain_str(ZZZAAAM,include_Z=False,ZZZAAA=False,delimiter='-'):
'''
Description:
Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000\*Z + 10\*A + M

Dependencies:
Element_Z_to_Sym (function within the "Hunter's tools" package)

Input:
- ZZZAAAM = integer equal to 10000*Z + 10*A + M, where M designates the metastable state (0=ground)
- include_Z = Boolean denoting whether the Z number should be included in the output string (D=False)
- ZZZAAA = Boolean denoting whether the input should be interpreted as a ZZZAAA value (1000Z+A) instead (D=False)
- delimiter = string which will be used to separate elements of the output string (D=-)

Output:
- nuc_str = string describing the input nuclide formatted as [Z]-[Symbol]-[A][m]
'''
ZZZAAAM = int(ZZZAAAM)
if ZZZAAA:
ZZZAAAM = ZZZAAAM*10
m = ZZZAAAM % 10
A = (ZZZAAAM % 10000) // 10
Z = ZZZAAAM // 10000
symbol = Element_Z_to_Sym(Z)

m_str = ''
if m>0:
m_str = 'm' + str(m)

nuc_str = ''
if include_Z:
nuc_str += str(Z) + delimiter
nuc_str += symbol + delimiter + str(A) + m_str

return nuc_str

def relative_error_to_N(relerr):
'''
Description:
Convert a relative uncertainty to a "N" value
Inputs:
- relerr = a relative uncertainty
Outputs:
- N = "number of counts"-like number
'''
if relerr == 0:
N = 0
else:
N = (1/(relerr**2))
return N

def N_to_relative_error(N):
'''
Description:
Convert an "N" value to a relative uncertainty
Inputs:
- N = "number of counts"-like number
Outputs:
- relerr = a relative uncertainty
'''
if N == 0: # or N < 0:
relerr = 0
else:
relerr = (1/(N**0.5))
return relerr

def fractional_error(test_val,ref_val):
'''
Description:
Calculate fractional error of an experimental test value relative to a reference value
Inputs:
- test_val = new/tested/experimental value (float ot int)
- ref_val = old/established/reference value (float ot int)
Outputs:
- ferr = fractional error of the new value from the old one
'''
if ref_val != 0:
ferr = (test_val-ref_val)/ref_val
elif test_val == 0: # both are zero
ferr = 0
else: # only one is zero, 100% error
ferr = 1
#ferr = 0 # changed to zero because I'd rather reject points where no comparison can be made
return ferr

def fractional_difference(v1,v2):
'''
Description:
Calculate fractional difference between two values (neither of more merit than the other)
Inputs:
- v1 = first value (float ot int)
- v2 = second value (float ot int)
Outputs:
- fdiff = fractional difference between the two values
'''
if v1 != 0 and v2 != 0:
fdiff = abs(v1-v2)/(0.5*(v1+v2))
elif (v1+v2) != 0: # only one is nonzero, 100% difference
fdiff = 1
else: # both are zero
fdiff = 0
return fdiff

def absolute_difference(test_val,ref_val):
'''
Description:
Calculate absolute difference of a reference value and an experimental test value
Inputs:
- test_val = new/tested/experimental value (float ot int)
- ref_val = old/established/reference value (float ot int)
Outputs:
- absdiff = difference of reference and test values
'''
absdiff = ref_val-test_val
return absdiff

def quotient(n,d):
'''
Description:
Calculate the quotient of two value, returning 0 if either the numerator or demoninator equal zero
Inputs:
- n = numerator (float ot int)
- d = denominator (float ot int)
Outputs:
- q = quotient of n and d (q=n/d)
'''
if n != 0 and d != 0:
q = n/d
else:
q = 0
return q

def Lorentz_gamma(v):
'''
Description:
Calculate the Lorentz variable gamma provided a velocity in cm/ns
Inputs:
- v = velocity in cm/ns (float ot int)
Outputs:
- gamma = Lorentz variable gamma
'''
m_n = 939.5654133 # neutron mass in MeV/c^2
c_vac = 29979245800 # cm/s
refractive_index_air = 1.0003
c = c_vac/refractive_index_air
c_cm_per_ns = c/(10**9)
beta = v/c_cm_per_ns
gamma = np.sqrt(1/(1 - beta**2))
return gamma

def Lorentz_B2_from_Tn(Tn):
'''
Description:
Calculate the Lorentz variable beta^2 provided a neutron kinetic energy in MeV
Inputs:
- Tn = neutron kinetic energy in MeV (float ot int)
Outputs:
- beta_squared = Lorentz variable beta^2
'''
m_n = 939.5654133 # neutron mass in MeV/c^2
c_vac = 29979245800 # cm/s
refractive_index_air = 1.0003
c = c_vac/refractive_index_air
c_cm_per_ns = c/(10**9)
gamma = 1 + (Tn/m_n)
beta_squared = 1 - (1/(gamma**2))
return beta_squared

def dist(a,b):
'''
Description:
Calculate the distance between two N-dimensional Cartesian points a and b
Dependencies:
import numpy as np
Inputs:
- a = length N list containing coordinates of the first point, a  (ex. [ax,ay] or [ax,ay,az])
- b = length N list containing coordinates of the second point, b (same format as a)
Outputs:
- d = Cartesian distance between points a and b

'''
if len(a) != len(b):
print('a ({}) and b ({}) are not of the same dimension'.format(str(len(a)),str(len(b))))
return 0
d = 0
for i in range(len(a)):
d += (a[i]-b[i])**2
d = np.sqrt(d)
return d

def curvature(x_data,y_data):
'''
Description:
Calculates curvature for all interior points on a curve whose coordinates are provided

Dependencies:
circumradius (function within the "Hunter's tools" package)

Inputs:
- x_data = list of N x-coordinates
- y_data = list of N y-coordinates
Outputs:
- curvature = list of N-2 curvature values
'''
curvature = []
for i in range(1,len(x_data)-1):
R = circumradius(x_data[i-1:i+2],y_data[i-1:i+2])
if ( R == 0 ):
print('Failed: points are either collinear or not distinct')
return 0
curvature.append(1/R)
return curvature

def circumradius(xvals,yvals):
'''
Description:
Calculates circumradius between three 2-dimensional points

Inputs:
- xvals = list of 3 x-coordinates
- yvals = list of 3 y-coordinates
Outputs:
- R = circumradius of the three points
'''
x1, x2, x3, y1, y2, y3 = xvals[0], xvals[1], xvals[2], yvals[0], yvals[1], yvals[2]
den = 2*((x2-x1)*(y3-y2)-(y2-y1)*(x3-x2))
num = ( (((x2-x1)**2) + ((y2-y1)**2)) * (((x3-x2)**2)+((y3-y2)**2)) * (((x1-x3)**2)+((y1-y3)**2)) )**(0.5)
if ( den == 0 ):
print('Failed: points are either collinear or not distinct')
return 0
R = abs(num/den)
return R

def circumcenter(xvals,yvals):
'''
Description:
Calculates circumcenter between three 2-dimensional points

Inputs:
- xvals = list of 3 x-coordinates
- yvals = list of 3 y-coordinates
Outputs:
- x = x-coordinate of circumcenter
- y = y-coordinate of circumcenter
'''
x1, x2, x3, y1, y2, y3 = xvals[0], xvals[1], xvals[2], yvals[0], yvals[1], yvals[2]
A = 0.5*((x2-x1)*(y3-y2)-(y2-y1)*(x3-x2))
if ( A == 0 ):
print('Failed: points are either collinear or not distinct')
return 0
xnum = ((y3 - y1)*(y2 - y1)*(y3 - y2)) - ((x2**2 - x1**2)*(y3 - y2)) + ((x3**2 - x2**2)*(y2 - y1))
x = xnum/(-4*A)
y =  (-1*(x2 - x1)/(y2 - y1))*(x-0.5*(x1 + x2)) + 0.5*(y1 + y2)
return x, y

def circ_solid_angle(pp,a,b,r):
'''
Description:
Calculates the solid angle subtended on (circular-faced) detector from point pp [xp,yp] to two points on a detector a [xa, ya] and b [xb, yb]
Dependencies:
import numpy as np
Inputs:
- pp = point of emission (radiation source), N-dimensional, list of floats/ints
- a = point on edge of circular detector face, N-dimensional, list of floats/ints
- b = point on edge of circular detector face opposite a, N-dimensional, list of floats/ints
- r = radius of the detector (in the same units as the coordinates)
Outputs:
- omega = solid angle of source at pp as seen by a detector whose circular face has a diameter with ends at a and b

'''
d_pa = dist(pp,a)
d_pb = dist(pp,b)
theta = 0.5*np.arccos((d_pa**2 + d_pb**2 - (2*r)**2)/(2*d_pa*d_pb))
omega = 2*np.pi*(1-np.cos(theta))
return omega

def energy_resolution_of_peak(y,x,):
'''
Description:
Provided a spectrum of counts (or equivalent) and it's bin centers or edges, and optionally the range of bins to evaluate, determine the
energy resolution FWHM/H0 (also sigma/H0) of peak with average value H0.

Inputs:
- y = list/array of y values (length N)
- x = list/array of x bin centers (length N) or bin edges (length N+1) corresponding to y
- peak_guess_location = single value (float/int) denoting an initial guess of the peak's x location
- peak_search_range = length-2 list of x values (not indices) between which the peak will be searched for

Notes:
At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to
amplitude/height, x position/centering, and width.

Outputs:
- y_fit = list/array of evaluated y values using the optimally found fit parameters
- dist_info = dictionary object containing the distribution short_name and full_name; optimized fit parameters a, mu, and sigma;
calculated FWHM, r2/r_squared R^2, and chi2/chi_squared values; Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str
'''

return None

def eval_distribution(x,dist_name='gauss',a=1,mu=0,sigma=0.3):
'''
Description:
Evaluates a provided array of x values (or single value) for a desired distribution provided its defining parameters.

Dependencies:
from munch import *

Inputs:
- x = list/array of x values or single x value
- dist_name = string denoting the distribution used (D='Gaussian'), options include:
['gauss','normal','Logistic','sech'], Read more here on the [Gaussian/normal](https://en.wikipedia.org/wiki/Normal_distribution),
[Logistic](https://en.wikipedia.org/wiki/Logistic_distribution), and
[Hyperbolic secant](https://en.wikipedia.org/wiki/Hyperbolic_secant_distribution) distributions.
- a = distribution amplitude/height parameter
- mu = distribution mean/center parameter
- sigma = distribution width parameter

Notes:
At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to
amplitude/height, x position/centering, and width.

Outputs:
- y_eval = list/array of evaluated y values or single y value
- dist_info = dictionary object containing the distribution short_name and full_name; assigned fit parameters a, mu, and sigma;
Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str
'''
dist_names_list = ['gauss','normal','Logistic','sech']
dist_name = dist_name.lower()
if dist_name not in dist_names_list:
print('Selected distribution name, ',dist_name,' is not in the list of allowed distribution names: ',dist_names_list,'\n exiting function... Please pick from this list and try again.')
return None
if type(x) is list: x = np.array(x)

if dist_name=='gauss' or dist_name=='normal':
y_eval = a*np.exp(-(x-mu)**2/(2*sigma**2))
fcn_py_str = 'f = a*np.exp(-(x-mu)**2/(2*sigma**2))'
fcn_tex_str = r'f(x) = A$\cdot$exp$(\frac{-(x-\mu)^2}{2\sigma^2})$'
if dist_name=='gauss':
dist_full_name = 'Gaussian'
else:
dist_full_name = 'Normal'
elif dist_name=='logistic':
y_eval = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))**2
fcn_py_str = 'f = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))**2'
fcn_tex_str = r'f(x) = $\frac{A}{4\sigma}$sech$^2(\frac{x-\mu}{2\sigma})$'
dist_full_name = 'Logistic'
else: #if dist_name=='sech'
y_eval = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))
fcn_py_str = 'f = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))'
fcn_tex_str = r'f(x) = $\frac{A}{4\sigma}$sech$(\frac{x-\mu}{2\sigma})$'
dist_full_name = 'Hyperbolic secant'

dist_info = {'short_name':dist_name,
'full_name':dist_full_name,
'a':a,
'mu':mu,
'sigma':sigma,
'fcn_py_str':fcn_py_str,
'fcn_tex_str':fcn_tex_str}

try:
dist_info = Munch(dist_info)
except:
print("Munch failed.  Returned object is a conventional dictionary rather than a munch object.")

return y_eval,  dist_info

def fit_distribution(x,y,dist_name='gauss',a0=None,mu0=None,sigma0=None):
'''
Description:
Determine best fit parameters and quality of fit provided test x and y values, the name of the ditribution to be fit, and initial guesses of its parameters.
If initial guesses are omitted, they will try to be automatically assessed (your mileage may vary).

Dependencies:
from munch import *
from scipy.optimize import curve_fit

Inputs:
- x = list/array of x values to be fit
- y = list/array of y values to be fit
- dist_name = string denoting the distribution used (D='Gaussian'), options include:
['gauss','normal','Logistic','sech'], Read more here on the [Gaussian/normal](https://en.wikipedia.org/wiki/Normal_distribution),
[Logistic](https://en.wikipedia.org/wiki/Logistic_distribution), and
[Hyperbolic secant](https://en.wikipedia.org/wiki/Hyperbolic_secant_distribution) distributions.
- a0 = initial guess of the distribution amplitude/height parameter
- mu0 = initial guess of the distribution mean/center parameter
- sigma0 = initial guess of the distribution width parameter

Notes:
At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to
amplitude/height, x position/centering, and width.

Outputs:
- y_fit = list/array of evaluated y values using the optimally found fit parameters
- dist_info = dictionary object containing the distribution short_name and full_name; optimized fit parameters a, mu, and sigma;
calculated FWHM, r2/r_squared R^2, and chi2/chi_squared values; Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str
'''
dist_names_list = ['gauss','normal','Logistic','sech']
dist_name = dist_name.lower()
if dist_name not in dist_names_list:
print('Selected distribution name, ',dist_name,' is not in the list of allowed distribution names: ',dist_names_list,'\n exiting function... Please pick from this list and try again.')
return None
if type(x) is list: x = np.array(x)
if type(y) is list: y = np.array(y)

def gaus(x,a,mu,sigma):
f = a*np.exp(-(x-mu)**2/(2*sigma**2))
return f
def logistic_dist(x,a,mu,sigma):
f = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))**2
return f
def hyperbolic_secant_dist(x,a,mu,sigma):
f = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))
return f

if dist_name=='gauss' or dist_name=='normal':
fit_fcn = gaus
elif dist_name=='logistic':
fit_fcn = logistic_dist
else: #if dist_name=='sech'
fit_fcn = hyperbolic_secant_dist

n = len(x)
ymax=max(y)
mean = sum(x*y/ymax)/n
sigma = sum((y/ymax)*(x-mean)**2)/n

if a0==None: a0 = ymax
if mu0==None: mu0 = mean
if sigma0==None: sigma0 = sigma
popt,pcov = curve_fit(fit_fcn,x,y,p0=[a0,mu0,sigma0])
a, mu, sigma = popt[0], popt[1], popt[2]
FWHM_fit = 2*np.sqrt(2*np.log(2))*sigma

y_fit, dist_info = eval_distribution(x, dist_name=dist_name, a=a, mu=mu, sigma=sigma)
r2 = r_squared(y,y_fit)

#chi2,red_chi2 = chi_squared(y,y_fit,num_fit_params=3)
num_fit_params=3
chi2,p_chi2,ndf_chi2 = chi_squared(y,y_fit,num_fit_params=num_fit_params)
# How to write chi2 results: https://www.socscistatistics.com/tutorials/chisquare/default.aspx
if p_chi2<0.001:
chi_p_str = 'p<0.001'
else:
chi_p_str = 'p = {:4g}'
chi2_tex_str = r'$\chi^2$'+'({:},N={:})={:.3g}, {}'.format(num_fit_params,n,chi2,chi_p_str)

dist_info.update({
'FWHM':FWHM_fit,
'r2':r2,
'r_squared':r2,
'chi2':chi2,
'chi_squared':chi2,
'p_chi2':p_chi2,
'ndf_chi2':ndf_chi2,
'chi2_tex_str':chi2_tex_str
#'red_chi2':red_chi2,
#'red_chi_squared':red_chi2,
})

return y_fit, dist_info

def r_squared(y,y_fit):
'''
Description:
Calculate R^2 (R-squared) value between two sets of data, an experimental "y" and fitted "y_fit"

Inputs:
- y = list/array of y values (experimental)
- y_fit = list/array of fitted y values to be compared against y

Outputs:
- r_squared = calculated R-squared value
'''
# Calculate R^2
# residual sum of squares
ss_res = np.sum((y-y_fit)**2)
# total sum of squares
ss_tot = np.sum((y-np.mean(y))**2)
# r-squared
r2 = 1-(ss_res/ss_tot)
return r2

def chi_squared(y,y_fit,num_fit_params=0):
'''
Description:
Calculate chi^2 (chi-squared) value between two sets of data, an experimental "y" and fitted "y_fit"

Dependencies:
from scipy.stats import chisquare

Inputs:
- y = list/array of y values (experimental)
- y_fit = list/array of fitted y values to be compared against y
- num_deg_freedom = number of degrees of freedom (DoF) in fit function (number of optimized parameters) (D=0)

Outputs:
- chi_squared = calculated chi-squared value
- reduced_chi_squared = calculated reduced chi-squared value (chi^2 / DoF)
'''
# This normalization shouldn't be necessary, but a past build of scipy broke the chisquare function from working without it
y_fit = y_fit*sum(y)/sum(y_fit)
chi2,p = chisquare(y,f_exp=y_fit,ddof=num_fit_params)
ndf = len(y) - num_fit_params
# Hand calc
#O_k = y     # observed, from measurement
#E_k = y_fit # expected, from some distribution e.g. Gaussian
#chi2 = np.sum( ((O_k-E_k)**2)/E_k )
#num_deg_freedom = len(O_k) - num_fit_params
#red_chi2 = chi2/num_deg_freedom
return chi2, p, ndf

def tally(data, bin_edges=[], min_bin_left_edge=None, max_bin_right_edge=None, nbins=None, bin_width=None, divide_by_bin_width=False, normalization=None, scaling_factor=1, place_overflow_at_ends=True, return_uncertainties=False, return_event_indices_histogram=False):
'''
Description:
Tally number of incidences of values falling within a desired binning structure

Inputs:
- data = list of values to be tallied/histogrammed
- bin_edges = list of N+1 bin edge values for a tally of N bins
- min_bin_left_edge = left/minimum edge value of the first bin
- max_bin_right_edge = right/maximum edge value of the last bin
- nbins = number of equally-sized bins to be created from min_bin_left_edge to max_bin_right_edge
- bin_width = constant width of bins to be created from min_bin_left_edge to max_bin_right_edge
- divide_by_bin_width = Boolean denoting whether final bin values are divided by their bin widths (D=False)
- normalization = determine how the resulting histogram is normalized (D=None), options are:
[None, 'unity-sum', 'unity-max-val'].  If None, no additional normalization is done.
If unity-sum, the data is normalized such that its sum will be 1.  If unity-max-val, the
data is normalized such that the maximum value is 1.  The operation occurs after any bin
width normalization from divide_by_bin_width but before any scaling from scaling_factor.
- scaling_factor = value which all final bins are multiplied/scaled by (D=1)
- place_overflow_at_ends = handling of values outside of binning range (D=True); if True extreme
values are tallied in the first/last bin, if False extreme values are discarded
- return_uncertainties = Boolean denoting if should return an extra N-length list whose elements
are the statistical uncertainties (square root w/ normalizations) of the tally bins (D=False)
- return_event_indices_histogram = Boolean denoting if should return an extra N-length list whose elements
are each a list of the event indices corresponding to each bin (D=False)

Notes:
Regarding the binning structure, this function only needs to be provided bin_edges directly (takes priority)
or the information needed to calculate bin_edges, that is: min_bin_left_edge and max_bin_right_edge and
either nbins or bin_width.  (Priority is given to nbins if both are provided.)

Outputs:
- tallied_hist = N-length list of tallied data
- bin_edges = list of N+1 bin edge values for a tally of N bins
- tallied_hist_err = (optional) N-length list of statistical uncertainties of tallied data
- tallied_event_indicies = (optional) N-length list of, for each bin, a list of the event indices populating it
'''

normalization_valid_entries = [None, 'unity-sum', 'unity-max-val']
if normalization not in normalization_valid_entries:
print("Entered normalization option of ",normalization," is not a valid option; please select from the following: [None, 'unity-sum', 'unity-max-val']".format())

if len(bin_edges)!=0:
bin_edges = np.array(bin_edges)
else:
if nbins != None:
bin_edges = np.linspace(min_bin_left_edge,max_bin_right_edge,num=nbins+1)
else:
bin_edges = np.arange(min_bin_left_edge,max_bin_right_edge+bin_width,step=bin_width)

nbins = len(bin_edges) - 1

if return_event_indices_histogram:
tallied_event_indicies = []
tallied_hist = np.zeros(nbins)
for i in range(nbins):
tallied_event_indicies.append([])
# events must be histogrammed manually
for i, val in enumerate(data):
if val < bin_edges[0]:
if place_overflow_at_ends:
tallied_hist[0] += 1
tallied_event_indicies[0].append(i)
continue
if val > bin_edges[-1]:
if place_overflow_at_ends:
tallied_hist[-1] += 1
tallied_event_indicies[-1].append(i)
continue
for j, be in enumerate(bin_edges):
if be > val: # found right edge of bin containing val
tallied_hist[j-1] += 1
tallied_event_indicies[j-1].append(i)
break

else:
tallied_hist, bins = np.histogram(data,bins=bin_edges)

if return_uncertainties:
tallied_hist_err = np.sqrt(tallied_hist)
if divide_by_bin_width: tallied_hist_err = tallied_hist_err/(bin_edges[1:]-bin_edges[:-1])
if normalization=='unity-sum': tallied_hist_err = tallied_hist_err/np.sum(tallied_hist)
if normalization=='unity-max-val': tallied_hist_err = tallied_hist_err/np.max(tallied_hist)
if scaling_factor != 1: tallied_hist_err = tallied_hist_err*scaling_factor

if divide_by_bin_width: tallied_hist = tallied_hist/(bin_edges[1:]-bin_edges[:-1])
if normalization=='unity-sum': tallied_hist = tallied_hist/np.sum(tallied_hist)
if normalization=='unity-max-val': tallied_hist = tallied_hist/np.max(tallied_hist)
if scaling_factor != 1: tallied_hist = tallied_hist*scaling_factor

if return_event_indices_histogram:
if return_uncertainties:
return tallied_hist,bin_edges,tallied_hist_err,tallied_event_indicies
else:
return tallied_hist,bin_edges,tallied_event_indicies
else:
if return_uncertainties:
return tallied_hist,bin_edges,tallied_hist_err
else:
return tallied_hist,bin_edges

def rebinner(output_xbins,input_xbins,input_ybins):
"""
Description:
The purpose of this function is to rebin a set of y values corresponding to a set of x bins to a new set of x bins.
The function seeks to be as generalized as possible, meaning bin sizes do not need to be consistent.

Dependencies:
import numpy as np

Inputs:
- output_xbins = output array containing bounds of x bins of length N; first entry is leftmost bin boundary
- input_xbins  = input array containing bounds of x bins of length M; first entry is leftmost bin boundary
- input_ybins  = input array containing y values of length M-1

Outputs:
- output_ybins = output array containing y values of length N-1
"""

N = len(output_xbins)
M = len(input_xbins)
output_ybins = np.zeros(N-1)

for i in range(0,N-1):
# For each output bin
lxo = output_xbins[i]   # lower x value of output bin
uxo = output_xbins[i+1] # upper x value of output bin
dxo = uxo - lxo         # width of current x output bin

# Scan input x bins to see if any fit in this output bin
for j in range(0,M-1):
lxi = input_xbins[j]    # lower x value of input bin
uxi = input_xbins[j+1]  # upper x value of input bin
dxi = uxi - lxi         # width of current x input bin

if uxi<lxo or lxi>uxo:
# no bins are aligned
continue
elif lxi >= lxo and lxi < uxo:
# start of an input bin occurs in this output bin
if lxi >= lxo and uxi <= uxo:
# input bin completely encompassed by output bin
output_ybins[i] = output_ybins[i] + input_ybins[j]
else:
# input bin spans over at least one output bin
# count fraction in current output x bin
f_in_dxo = (uxo-lxi)/dxi
output_ybins[i] = output_ybins[i] + f_in_dxo*input_ybins[j]
elif lxi < lxo and uxi > uxo:
# output bin is completely encompassed by input bin
f_in_dxo = (uxo-lxo)/dxi
output_ybins[i] = output_ybins[i] + f_in_dxo*input_ybins[j]
elif lxi < lxo and uxi > lxo and uxi <= uxo:
# tail of input bin is located in this output bin
f_in_dxo = (uxi-lxo)/dxi
output_ybins[i] = output_ybins[i] + f_in_dxo*input_ybins[j]

return output_ybins

def calc_GCR_intensity(Z,W,E):
'''
Description:
Calculate GCR flux for a given ion at a given energy using the Matthia model
https://www.sciencedirect.com/science/article/pii/S0273117712005947?via%3Dihub

Dependencies:
import numpy as np

Inputs:
- Z = GCR ion Z
- W = solar modulation parameter
- E = GCR ion energy (in MeV/n)

Outputs:
- IOSI = ion flux in (s\*sr\*cm^2\*MeV/n)^-1
'''

if Z<1 or Z>28 or W<0 or W>200:
return -99
if E<10:
return 0

AI = [1.0 ,4.0,  6.9,  9.0, 10.8, 12.0, 14.0, 16.0, 19.0, 20.2, 23.0, 24.3, 27.0, 28.1, 31.0, 32.1, 35.4, 39.9, 39.1, 40.1, 44.9, 47.9, 50.9, 52.0, 54.9, 55.8, 58.9, 58.7]
CI = [1.85e4, 3.69e3, 19.5, 17.7, 49.2, 103.0, 36.7, 87.4, 3.19, 16.4, 4.4300, 19.300, 4.17, 13.4, 1.15, 3.060, 1.30, 2.33, 1.87, 2.17, 0.74, 2.63, 1.23, 2.12, 1.14, 9.32, 0.10, 0.48]
gammaI = [2.74, 2.77, 2.82, 3.05, 2.96, 2.76, 2.89, 2.70, 2.82, 2.76, 2.84, 2.70, 2.77, 2.66, 2.89, 2.71, 3.00, 2.93, 3.05, 2.77, 2.97, 2.99, 2.94, 2.89, 2.74, 2.63, 2.63, 2.63]
alphaI = [2.85, 3.12, 3.41, 4.30, 3.93, 3.18, 3.77, 3.11, 4.05, 3.11, 3.14, 3.65, 3.46, 3.00, 4.04, 3.30, 4.40, 4.33, 4.49, 2.93, 3.78, 3.79, 3.50, 3.28, 3.29, 3.01, 4.25, 3.52]

P = [0.02,4.7]

i = int(Z-1)

E0S = 938.0 # rest mass in MeV/n
if Z>1: E0S = 939.0 # rest mass in MeV/n
E0SS = E0S/1000 # rest mass in GeV/n
ES = E/1000 # energy in GeV/n
RigS = (AI[i]/Z*np.sqrt(ES*(ES+2*E0SS))) #rigidity in GV
betaS2 = (np.sqrt(ES*(ES+2.*E0SS))/(ES+E0SS)) #convert kinetic energy per nucleon to beta=v/c
R0S = (0.37+0.0003*(W**1.45))
DELTAI = (P[1]+P[0]*W)
PHII = CI[i]*(betaS2**alphaI[i])/(RigS**gammaI[i])
PHII = PHII*((RigS/(RigS+R0S))**DELTAI)
IOSI = 0.0001*PHII*AI[i]/Z*0.001/betaS2

return IOSI

def assemble_GCR_flux(W,Z_list,nEbins=1000):
'''
Description:
Composes a NumPy array containing GCR flux from 10 MeV/n to 1 TeV/n for each GCR ion specified

Dependencies:
- import numpy as np
- calc_GCR_intensity (function within the "Hunter's tools" package)

Inputs:
- W = solar modulation parameter
- Z_list = list of element atomic numbers to form GCR spectra for
- nEbins = number of evenly-logspaced energy bins (D=1000)

Outputs:
- GCR_flux(len(Z_list),4,nEbins) = array containing flux values in (s\*sr\*cm^2\*MeV/n)^-1 ; [emin/emid/emax/flux]
'''
GCR_flux = np.zeros((len(Z_list),4,nEbins))

Emin = 10 # MeV
Emax = 1e6 # MeV

logEmin = np.log10(Emin)
logEmax = np.log10(Emax)
logdE = (logEmax-logEmin)/nEbins
logE = logEmin

for k in range(nEbins):
GCR_flux[:,0,k] = 10**(logE)
GCR_flux[:,1,k] = 10**(logE+0.5*logdE)
GCR_flux[:,2,k] = 10**(logE+logdE)
logE += logdE

for j in range(len(Z_list)):
Z = Z_list[j]
for k in range(nEbins):
GCR_flux[j,3,k] = calc_GCR_intensity(Z,W,GCR_flux[j,1,k])

return GCR_flux

def ICRP116_effective_dose_coeff(E=1.0,particle='photon',geometry='AP',interp_scale='log',interp_type='cubic',extrapolation_on=False):
'''
Description:
For a given particle at a given energy in a given geometry, returns its
effective dose conversion coefficient from ICRP 116

Dependencies:
- import numpy as np
- from scipy.interpolate import CubicSpline, lagrange, interp1d
- find (function within the "Hunter's tools" package)

Inputs:
- E = energy of the particle in MeV (D=1)
- particle = select particle (D='photon', options include: ['photon', 'electron', 'positron' ,'neutron' ,'proton', 'negmuon', 'posmuon', 'negpion', 'pospion', 'He3ion'])
- geometry = geometric arrangement (D='AP', options include: ['AP', 'PA', 'LLAT', 'RLAT', 'ROT', 'ISO', 'H*(10)'] ('LLAT','RLAT','ROT' only available for photon, proton, and neutron))
- Meanings:
AP, antero-posterior; PA, postero-anterior; LLAT, left lateral; RLAT, right lateral; ROT, rotational; ISO, isotropic.
- Note: 'H*(10)' ambient dose equivalent is available for photons only
- interp_scale = interpolation scale (D='log' to interpolate on a log scale, options include: ['log','lin'], ICRP 74/116 suggest log-log cubic interpolation)
- interp_type  = interpolation method (D='cubic' to interpolate with a cubic spline, options include: ['cubic','linear'], ICRP 74/116 suggest log-log cubic interpolation)
technically, any options available for scipy.interpolate.interp1d() can be used: ['linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic', 'previous']
- extrapolation_on = boolean designating whether values outside of the tabulated energies will be extrapolated (D=False)

|                      |                                                                       |
| -------------------- | --------------------------------------------------------------------- |
| if False & E < E_min | f(E) = 0                                                              |
| if False & E > E_max | f(E) = f(E_max)                                                       |
| if True  & E < E_min | f(E) is linearly interpolated between (0,0) and (E_min,f(E_min))      |
| if True  & E > E_max | f(E) is extrapolated using the specified interpolation scale and type |
Outputs:
- f = effective dose conversion coefficient in pSv*cm^2
'''

pars_list = ['photon','electron','positron','neutron','proton','negmuon','posmuon','negpion','pospion','He3ion']
geo_list_all = ['AP','PA','LLAT','RLAT','ROT','ISO','H*(10)']
geo_list_short = ['AP','PA','ISO']

if particle not in pars_list or geometry not in geo_list_all:
pstr = 'Please select a valid particle and geometry.\n'
pstr += "Particle selected = {}, options include: ['photon','electron','positron','neutron','proton','negmuon','posmuon','negpion','pospion','He3ion']".format(particle)
pstr += "Geometry selected = {}, options include: ['AP','PA','LLAT','RLAT','ROT','ISO'] ('LLAT','RLAT','ROT' only available for photon, proton, and neutron)"
print(pstr)
return None

if (particle not in ['photon','neutron','proton'] and geometry in ['LLAT','RLAT','ROT']) or (particle!='photon' and geometry=='H*(10)'):
if (particle!='photon' and geometry=='H*(10)'):
pstr = "geometry = {} is only available for photons\n".format(geometry)
else:
pstr = "geometry = {} is only available for photon, neutron, and proton\n".format(geometry)
pstr += "For selected particle = {}, please choose geometry from ['AP','PA','ISO']".format(particle)
print(pstr)
return None

E_photon = [0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.511, 0.6, 0.662, 0.8, 1, 1.117, 1.33, 1.5, 2, 3, 4, 5, 6, 6.129, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000]
f_photon = [
[0.0685, 0.156, 0.225, 0.313, 0.351, 0.37, 0.39, 0.413, 0.444, 0.519, 0.748, 1, 1.51, 2, 2.47, 2.52, 2.91, 3.17, 3.73, 4.49, 4.9, 5.59, 6.12, 7.48, 9.75, 11.7, 13.4, 15, 15.1, 17.8, 20.5, 26.1, 30.8, 37.9, 43.1, 47.1, 50.1, 54.5, 57.8, 63.3, 67.3, 72.3, 75.5, 77.5, 78.9, 80.5, 81.7, 83.8, 85.2, 86.9, 88.1, 88.9, 89.5, 90.2, 90.7],
[0.0184, 0.0155, 0.026, 0.094, 0.161, 0.208, 0.242, 0.271, 0.301, 0.361, 0.541, 0.741, 1.16, 1.57, 1.98, 2.03, 2.38, 2.62, 3.13, 3.83, 4.22, 4.89, 5.39, 6.75, 9.12, 11.2, 13.1, 15, 15.2, 18.6, 22, 30.3, 38.2, 51.4, 62, 70.4, 76.9, 86.6, 93.2, 104, 111, 119, 124, 128, 131, 135, 138, 142, 145, 148, 150, 152, 153, 155, 155],
[0.0189, 0.0416, 0.0655, 0.11, 0.14, 0.16, 0.177, 0.194, 0.214, 0.259, 0.395, 0.552, 0.888, 1.24, 1.58, 1.62, 1.93, 2.14, 2.59, 3.23, 3.58, 4.2, 4.68, 5.96, 8.21, 10.2, 12, 13.7, 13.9, 17, 20.1, 27.4, 34.4, 47.4, 59.2, 69.5, 78.3, 92.4, 103, 121, 133, 148, 158, 165, 170, 178, 183, 193, 198, 206, 212, 216, 219, 224, 228],
[0.0182, 0.039, 0.0573, 0.0891, 0.114, 0.133, 0.15, 0.167, 0.185, 0.225, 0.348, 0.492, 0.802, 1.13, 1.45, 1.49, 1.78, 1.98, 2.41, 3.03, 3.37, 3.98, 4.45, 5.7, 7.9, 9.86, 11.7, 13.4, 13.6, 16.6, 19.7, 27.1, 34.4, 48.1, 60.9, 72.2, 82, 97.9, 110, 130, 143, 161, 172, 180, 186, 195, 201, 212, 220, 229, 235, 240, 244, 251, 255],
[0.0337, 0.0664, 0.0986, 0.158, 0.199, 0.226, 0.248, 0.273, 0.297, 0.355, 0.528, 0.721, 1.12, 1.52, 1.92, 1.96, 2.3, 2.54, 3.04, 3.72, 4.1, 4.75, 5.24, 6.55, 8.84, 10.8, 12.7, 14.4, 14.6, 17.6, 20.6, 27.7, 34.4, 46.1, 56, 64.4, 71.2, 82, 89.7, 102, 111, 121, 128, 133, 136, 142, 145, 152, 156, 161, 165, 168, 170, 172, 175],
[0.0288, 0.056, 0.0812, 0.127, 0.158, 0.18, 0.199, 0.218, 0.239, 0.287, 0.429, 0.589, 0.932, 1.28, 1.63, 1.67, 1.97, 2.17, 2.62, 3.25, 3.6, 4.2, 4.66, 5.9, 8.08, 10, 11.8, 13.5, 13.7, 16.6, 19.6, 26.8, 33.8, 46.1, 56.9, 66.2, 74.1, 87.2, 97.5, 116, 130, 147, 159, 168, 174, 185, 193, 208, 218, 232, 243, 251, 258, 268, 276],
[0.061, 0.83, 1.05, 0.81, 0.64, 0.55, 0.51, 0.52, 0.53, 0.61, 0.89, 1.20, 1.80, 2.38, 2.93, 2.99, 3.44, 3.73, 4.38, 5.20, 5.60, 6.32, 6.90, 8.60, 11.10, 13.40, 15.50, 17.60, 17.86, 21.60, 25.60, 8.53, 8.29, 8.23, 8.26, 8.64, 8.71, 8.86, 9.00, 9.60, 10.20, 10.73, 11.27, 11.80, 11.78, 11.74, 11.70, 11.60, 11.50, 12.10, 12.70, 13.30, 13.08, 12.64, 12.20]
]

E_electron = [0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000]
f_electron = [
[0.0269, 0.0404, 0.0539, 0.081, 0.108, 0.135, 0.163, 0.218, 0.275, 0.418, 0.569, 0.889, 1.24, 1.63, 2.05, 4.04, 7.1, 15, 22.4, 36.1, 48.2, 59.3, 70.6, 97.9, 125, 188, 236, 302, 329, 337, 341, 346, 349, 355, 359, 365, 369, 372, 375, 379, 382, 387, 391, 397, 401, 405, 407, 411, 414],
[0.0268, 0.0402, 0.0535, 0.0801, 0.107, 0.133, 0.16, 0.213, 0.267, 0.399, 0.53, 0.787, 1.04, 1.28, 1.5, 1.68, 1.68, 1.62, 1.62, 1.95, 2.62, 3.63, 5.04, 9.46, 18.3, 53.1, 104, 220, 297, 331, 344, 358, 366, 379, 388, 399, 408, 414, 419, 428, 434, 446, 455, 468, 477, 484, 490, 499, 507],
[0.0188, 0.0283, 0.0377, 0.0567, 0.0758, 0.0948, 0.114, 0.152, 0.191, 0.291, 0.393, 0.606, 0.832, 1.08, 1.35, 1.97, 2.76, 4.96, 7.24, 11.9, 16.4, 21, 25.5, 35.5, 46.7, 76.9, 106, 164, 212, 249, 275, 309, 331, 363, 383, 410, 430, 445, 457, 478, 495, 525, 549, 583, 608, 628, 646, 675, 699]
]

E_positron = [0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000]
f_positron = [
[3.28, 3.29, 3.3, 3.33, 3.36, 3.39, 3.42, 3.47, 3.53, 3.67, 3.84, 4.16, 4.52, 4.9, 5.36, 7.41, 10.5, 18.3, 25.7, 39.1, 51, 61.7, 72.9, 99, 126, 184, 229, 294, 320, 327, 333, 339, 342, 349, 354, 362, 366, 369, 372, 376, 379, 385, 389, 395, 399, 402, 404, 408, 411],
[1.62, 1.64, 1.65, 1.68, 1.71, 1.73, 1.76, 1.82, 1.87, 2.01, 2.14, 2.4, 2.65, 2.9, 3.12, 3.32, 3.37, 3.44, 3.59, 4.19, 5.11, 6.31, 8.03, 14, 23.6, 59, 111, 221, 291, 321, 334, 349, 357, 371, 381, 393, 402, 409, 415, 424, 430, 443, 451, 465, 473, 480, 486, 495, 503],
[1.39, 1.4, 1.41, 1.43, 1.45, 1.47, 1.49, 1.53, 1.57, 1.67, 1.77, 1.98, 2.21, 2.45, 2.72, 3.38, 4.2, 6.42, 8.7, 13.3, 18, 22.4, 26.9, 36.7, 47.6, 75.5, 104, 162, 209, 243, 268, 302, 323, 356, 377, 405, 425, 440, 453, 474, 491, 522, 545, 580, 605, 627, 645, 674, 699]
]

E_neutron = [1.00E-09, 1.00E-08, 2.50E-08, 1.00E-07, 2.00E-07, 5.00E-07, 1.00E-06, 2.00E-06, 5.00E-06, 1.00E-05, 2.00E-05, 5.00E-05, 1.00E-04, 2.00E-04, 5.00E-04, 0.001, 0.002, 0.005, 0.01, 0.02, 0.03, 0.05, 0.07, 0.1, 0.15, 0.2, 0.3, 0.5, 0.7, 0.9, 1, 1.2, 1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 30, 50, 75, 100, 130, 150, 180, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 5000, 10000]
f_neutron = [
[3.09, 3.55, 4, 5.2, 5.87, 6.59, 7.03, 7.39, 7.71, 7.82, 7.84, 7.82, 7.79, 7.73, 7.54, 7.54, 7.61, 7.97, 9.11, 12.2, 15.7, 23, 30.6, 41.9, 60.6, 78.8, 114, 177, 232, 279, 301, 330, 365, 407, 458, 483, 494, 498, 499, 499, 500, 500, 499, 495, 493, 490, 484, 477, 474, 453, 433, 420, 402, 382, 373, 363, 359, 363, 389, 422, 457, 486, 508, 524, 537, 612, 716, 933],
[1.85, 2.11, 2.44, 3.25, 3.72, 4.33, 4.73, 5.02, 5.3, 5.44, 5.51, 5.55, 5.57, 5.59, 5.6, 5.6, 5.62, 5.95, 6.81, 8.93, 11.2, 15.7, 20, 25.9, 34.9, 43.1, 58.1, 85.9, 112, 136, 148, 167, 195, 235, 292, 330, 354, 371, 383, 392, 398, 404, 412, 417, 419, 420, 422, 423, 423, 422, 428, 439, 444, 446, 446, 447, 448, 464, 496, 533, 569, 599, 623, 640, 654, 740, 924, 1.17E+03],
[1.04, 1.15, 1.32, 1.7, 1.94, 2.21, 2.4, 2.52, 2.64, 2.65, 2.68, 2.66, 2.65, 2.66, 2.62, 2.61, 2.6, 2.74, 3.13, 4.21, 5.4, 7.91, 10.5, 14.4, 20.8, 27.2, 39.7, 63.7, 85.5, 105, 115, 130, 150, 179, 221, 249, 269, 284, 295, 303, 310, 316, 325, 333, 336, 338, 343, 347, 348, 360, 380, 399, 409, 416, 420, 425, 427, 441, 472, 510, 547, 579, 603, 621, 635, 730, 963, 1.23E+03],
[0.893, 0.978, 1.12, 1.42, 1.63, 1.86, 2.02, 2.11, 2.21, 2.24, 2.26, 2.24, 2.23, 2.24, 2.21, 2.21, 2.2, 2.33, 2.67, 3.6, 4.62, 6.78, 8.95, 12.3, 17.9, 23.4, 34.2, 54.4, 72.6, 89.3, 97.4, 110, 128, 153, 192, 220, 240, 255, 267, 276, 284, 290, 301, 310, 313, 317, 323, 328, 330, 345, 370, 392, 404, 413, 418, 425, 429, 451, 483, 523, 563, 597, 620, 638, 651, 747, 979, 1.26E+03],
[1.7, 2.03, 2.31, 2.98, 3.36, 3.86, 4.17, 4.4, 4.59, 4.68, 4.72, 4.73, 4.72, 4.67, 4.6, 4.58, 4.61, 4.86, 5.57, 7.41, 9.46, 13.7, 18, 24.3, 34.7, 44.7, 63.8, 99.1, 131, 160, 174, 193, 219, 254, 301, 331, 351, 365, 374, 381, 386, 390, 395, 398, 398, 399, 399, 398, 398, 395, 395, 402, 406, 411, 414, 418, 422, 443, 472, 503, 532, 558, 580, 598, 614, 718, 906, 1.14E+03],
[1.29, 1.56, 1.76, 2.26, 2.54, 2.92, 3.15, 3.32, 3.47, 3.52, 3.54, 3.55, 3.54, 3.52, 3.47, 3.46, 3.48, 3.66, 4.19, 5.61, 7.18, 10.4, 13.7, 18.6, 26.6, 34.4, 49.4, 77.1, 102, 126, 137, 153, 174, 203, 244, 271, 290, 303, 313, 321, 327, 332, 339, 344, 346, 347, 350, 352, 353, 358, 371, 387, 397, 407, 412, 421, 426, 455, 488, 521, 553, 580, 604, 624, 642, 767, 1.01E+03, 1.32E+03]
]

E_proton = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000]
f_proton = [
[5.46, 8.2, 10.9, 16.4, 21.9, 27.3, 32.8, 43.7, 54.9, 189, 428, 750, 1.02E+03, 1.18E+03, 1.48E+03, 2.16E+03, 2.51E+03, 2.38E+03, 1.77E+03, 1.38E+03, 1.23E+03, 1.15E+03, 1.16E+03, 1.11E+03, 1.09E+03, 1.15E+03, 1.12E+03, 1.23E+03, 1.27E+03, 1.23E+03, 1.37E+03, 1.45E+03, 1.41E+03],
[5.47, 8.21, 10.9, 16.4, 21.9, 27.3, 32.8, 43.7, 54.6, 56.1, 43.6, 36.1, 45.5, 71.5, 156, 560, 1.19E+03, 2.82E+03, 1.93E+03, 1.45E+03, 1.30E+03, 1.24E+03, 1.23E+03, 1.23E+03, 1.23E+03, 1.25E+03, 1.28E+03, 1.34E+03, 1.40E+03, 1.45E+03, 1.53E+03, 1.65E+03, 1.74E+03],
[2.81, 4.21, 5.61, 8.43, 11.2, 14, 16.8, 22.4, 28.1, 50.7, 82.8, 180, 290, 379, 500, 799, 994, 1.64E+03, 2.15E+03, 1.44E+03, 1.27E+03, 1.21E+03, 1.20E+03, 1.19E+03, 1.18E+03, 1.21E+03, 1.25E+03, 1.32E+03, 1.31E+03, 1.39E+03, 1.44E+03, 1.56E+03, 1.63E+03],
[2.81, 4.2, 5.62, 8.41, 11.2, 14, 16.8, 22.4, 28.1, 48.9, 78.8, 172, 278, 372, 447, 602, 818, 1.46E+03, 2.18E+03, 1.45E+03, 1.28E+03, 1.21E+03, 1.20E+03, 1.20E+03, 1.20E+03, 1.23E+03, 1.25E+03, 1.32E+03, 1.33E+03, 1.41E+03, 1.45E+03, 1.59E+03, 1.67E+03],
[4.5, 6.75, 8.98, 13.4, 17.8, 22.1, 26.3, 34.5, 50.1, 93.7, 165, 296, 422, 532, 687, 1.09E+03, 1.44E+03, 2.16E+03, 1.96E+03, 1.44E+03, 1.28E+03, 1.22E+03, 1.22E+03, 1.20E+03, 1.19E+03, 1.23E+03, 1.23E+03, 1.30E+03, 1.29E+03, 1.35E+03, 1.41E+03, 1.49E+03, 1.56E+03],
[3.52, 5.28, 7.02, 10.5, 13.9, 17.3, 20.5, 26.8, 45.8, 80.1, 136, 249, 358, 451, 551, 837, 1.13E+03, 1.79E+03, 1.84E+03, 1.42E+03, 1.25E+03, 1.18E+03, 1.17E+03, 1.17E+03, 1.15E+03, 1.21E+03, 1.22E+03, 1.31E+03, 1.40E+03, 1.43E+03, 1.57E+03, 1.71E+03, 1.78E+03]
]

E_negmuon = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000]
f_negmuon = [
[180, 180, 184, 188, 193, 205, 242, 293, 332, 414, 465, 657, 735, 755, 628, 431, 382, 340, 326, 319, 320, 321, 325, 327, 333, 331, 333, 336, 337, 337, 337, 337, 338],
[75.2, 76.8, 78.3, 81.4, 84.8, 87.7, 86.7, 86.8, 88.6, 100, 122, 251, 457, 703, 775, 485, 402, 345, 329, 321, 321, 324, 326, 332, 337, 338, 341, 344, 345, 346, 346, 347, 347],
[78.7, 79.5, 80.9, 83.7, 87.1, 91.5, 98.1, 113, 127, 161, 191, 275, 363, 446, 496, 498, 432, 354, 332, 321, 321, 323, 326, 331, 337, 338, 341, 344, 346, 347, 347, 348, 348]
]

E_posmuon = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000]
f_posmuon = [
[194, 196, 198, 202, 207, 216, 251, 300, 340, 425, 481, 674, 751, 768, 635, 431, 381, 339, 326, 318, 319, 320, 322, 325, 327, 331, 333, 336, 337, 337, 337, 337, 339],
[82.6, 84.1, 85.7, 88.9, 92.1, 94.3, 92.5, 92.8, 94.8, 108, 133, 265, 473, 721, 787, 483, 399, 345, 328, 320, 321, 323, 325, 330, 333, 339, 341, 344, 345, 346, 346, 347, 347],
[85.2, 86.2, 87.5, 90.3, 93.6, 97.7, 103, 117, 132, 167, 199, 284, 373, 456, 506, 502, 432, 354, 332, 320, 320, 322, 324, 329, 333, 338, 341, 344, 346, 347, 347, 348, 348]
]

E_negpion = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 15000, 20000, 30000, 40000, 50000, 60000, 80000, 100000, 150000, 200000]
f_negpion = [
[406, 422, 433, 458, 491, 528, 673, 965, 1.09E+03, 1.25E+03, 1.28E+03, 1.77E+03, 1.92E+03, 1.93E+03, 1.68E+03, 1.14E+03, 995, 927, 902, 848, 844, 869, 901, 947, 977, 1.03E+03, 1.05E+03, 1.03E+03, 1.03E+03, 1.06E+03, 1.09E+03, 1.14E+03, 1.17E+03, 1.21E+03, 1.24E+03, 1.30E+03, 1.35E+03, 1.39E+03, 1.42E+03, 1.48E+03, 1.54E+03, 1.67E+03, 1.78E+03],
[194, 201, 210, 225, 233, 237, 208, 181, 178, 197, 244, 547, 1.02E+03, 1.70E+03, 1.99E+03, 1.31E+03, 991, 889, 871, 843, 850, 880, 917, 976, 1.02E+03, 1.08E+03, 1.12E+03, 1.11E+03, 1.13E+03, 1.18E+03, 1.22E+03, 1.29E+03, 1.34E+03, 1.41E+03, 1.47E+03, 1.56E+03, 1.63E+03, 1.70E+03, 1.75E+03, 1.86E+03, 1.95E+03, 2.15E+03, 2.33E+03],
[176, 189, 198, 215, 232, 251, 271, 317, 361, 439, 508, 676, 868, 1.02E+03, 1.15E+03, 1.15E+03, 1.03E+03, 857, 815, 794, 807, 838, 875, 935, 979, 1.05E+03, 1.09E+03, 1.11E+03, 1.15E+03, 1.20E+03, 1.26E+03, 1.36E+03, 1.43E+03, 1.55E+03, 1.64E+03, 1.79E+03, 1.91E+03, 2.02E+03, 2.11E+03, 2.29E+03, 2.46E+03, 2.80E+03, 3.04E+03]
]

E_pospion = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 15000, 20000, 30000, 40000, 50000, 60000, 80000, 100000, 150000, 200000]
f_pospion = [
[314, 324, 340, 379, 429, 489, 540, 717, 819, 1000, 1.10E+03, 1.52E+03, 1.75E+03, 1.83E+03, 1.66E+03, 1.22E+03, 1.13E+03, 1.22E+03, 1.25E+03, 1.07E+03, 969, 943, 952, 999, 1.04E+03, 1.10E+03, 1.10E+03, 1.06E+03, 1.06E+03, 1.07E+03, 1.10E+03, 1.14E+03, 1.17E+03, 1.22E+03, 1.25E+03, 1.30E+03, 1.34E+03, 1.38E+03, 1.42E+03, 1.48E+03, 1.54E+03, 1.67E+03, 1.78E+03],
[121, 125, 133, 151, 170, 183, 185, 177, 179, 201, 247, 494, 906, 1.48E+03, 1.82E+03, 1.38E+03, 1.12E+03, 1.15E+03, 1.23E+03, 1.10E+03, 998, 970, 980, 1.04E+03, 1.09E+03, 1.16E+03, 1.19E+03, 1.16E+03, 1.16E+03, 1.20E+03, 1.24E+03, 1.31E+03, 1.35E+03, 1.42E+03, 1.48E+03, 1.57E+03, 1.64E+03, 1.70E+03, 1.75E+03, 1.84E+03, 1.94E+03, 2.14E+03, 2.33E+03],
[151, 160, 168, 183, 198, 216, 233, 265, 296, 367, 439, 602, 787, 953, 1.09E+03, 1.16E+03, 1.10E+03, 1.05E+03, 1.08E+03, 1.02E+03, 953, 930, 938, 993, 1.05E+03, 1.13E+03, 1.16E+03, 1.16E+03, 1.18E+03, 1.23E+03, 1.28E+03, 1.37E+03, 1.43E+03, 1.55E+03, 1.64E+03, 1.79E+03, 1.90E+03, 2.01E+03, 2.10E+03, 2.27E+03, 2.42E+03, 2.76E+03, 3.07E+03]
]

E_He3ion = [1, 2, 3, 5, 10, 14, 20, 30, 50, 75, 100, 150, 200, 300, 500, 700, 1000, 2000, 3000, 5000, 10000, 20000, 50000, 100000]
f_He3ion = [
[219, 438, 656, 1.09E+03, 2.19E+03, 4.61E+03, 1.72E+04, 3.01E+04, 4.75E+04, 8.05E+04, 1.01E+05, 9.25E+04, 6.74E+04, 5.14E+04, 4.27E+04, 4.11E+04, 4.00E+04, 4.02E+04, 4.08E+04, 4.12E+04, 4.56E+04, 5.12E+04, 6.12E+04, 7.14E+04],
[219, 438, 657, 1.09E+03, 2.19E+03, 2.56E+03, 1.74E+03, 1.44E+03, 2.88E+03, 1.75E+04, 4.84E+04, 1.10E+05, 7.29E+04, 5.33E+04, 4.49E+04, 4.60E+04, 4.47E+04, 4.80E+04, 5.01E+04, 5.17E+04, 6.26E+04, 6.10E+04, 8.14E+04, 1.01E+05],
[141, 281, 419, 689, 1.82E+03, 2.81E+03, 5.46E+03, 9.86E+03, 1.78E+04, 3.00E+04, 4.55E+04, 6.95E+04, 7.01E+04, 5.25E+04, 4.27E+04, 4.19E+04, 4.09E+04, 4.31E+04, 4.50E+04, 4.76E+04, 5.73E+04, 7.10E+04, 9.67E+04, 1.24E+05]
]

E_all = [E_photon, E_electron, E_positron, E_neutron, E_proton, E_negmuon, E_posmuon, E_negpion, E_pospion, E_He3ion]
f_all = [f_photon, f_electron, f_positron, f_neutron, f_proton, f_negmuon, f_posmuon, f_negpion, f_pospion, f_He3ion]

pi = find(particle, pars_list)
if particle in ['photon','neutron','proton']:
gi = find(geometry, geo_list_all)
else:
gi = find(geometry, geo_list_short)

E_list = E_all[pi]
f_list = f_all[pi][gi]

# Interpolate f given E
if E in E_list:
f = f_list[find(E,E_list)]
else:
if not extrapolation_on and (E < E_list[0] or E > E_list[-1]):  # E is outside of bounds and extrapolation is off
if E < E_list[0]:
f = 0   # assume negligibly low energy particle
if E > E_list[-1]:
f = f_list[-1]  # just set equal to max energy particle's coefficient
else:
if E < E_list[0]:
E_list = [0] + E_list
f_list = [0] + f_list
interp_scale = 'linear'

if interp_scale=='log':
cs = interp1d(np.log10(np.array(E_list)),np.log10(np.array(f_list)), kind=interp_type,fill_value='extrapolate')
f = 10**cs(np.log10(E))
else:
cs = interp1d(np.array(E_list),np.array(f_list), kind=interp_type,fill_value='extrapolate')
f = cs(E)

# for sake of sanity, return zero for values quite below minimum coefficients
if f < 1e-4:
f = 0.0

#if interp_type=='cubic':
#    if interp_scale=='log':
#        cs = interp1d(np.log10(np.array(E_list)),np.log10(np.array(f_list)), kind='cubic',fill_value='extrapolate')
#        f = 10**cs(np.log10(E))
#    else:
#        cs = interp1d(np.array(E_list),np.array(f_list), kind='cubic',fill_value='extrapolate')
#        f = cs(E)
#else:
#    if interp_scale=='log':
#        f = 10**np.interp(np.log10(E),np.log10(np.array(E_list)),np.log10(np.array(f_list)))
#    else:
#        f = np.interp(E,np.array(E_list),np.array(f_list))

#if interp_type=='cubic':
#    if interp_scale=='log':
#        cs = lagrange(np.log10(np.array(E_list)),np.log10(np.array(f_list)))
#        f = 10**cs(np.log10(E))
#    else:
#        cs = lagrange(np.array(E_list),np.array(f_list))
#        f = cs(E)
#if interp_type=='cubic':
#    if interp_scale=='log':
#        cs = CubicSpline(np.log10(np.array(E_list)),np.log10(np.array(f_list)))
#        f = 10**cs(np.log10(E))
#    else:
#        cs = CubicSpline(np.array(E_list),np.array(f_list))
#        f = cs(E)

return f

'''
**************************************************************************************************
------------------------------- MC-TRANSPORT-CODE-RELATED FUNCTIONS ------------------------------
**************************************************************************************************
'''

def fetch_MC_material(matid=None,matname=None,matsource=None,concentration_type=None,particle=None):
'''
Description:
Returns a materials definition string formatted for use in MCNP or PHITS

Dependencies:
- import os
- import pickle
- PYTHONPATH environmental variable must be set and one entry must contain the directory
which contains the vital "MC_tools/materials/Hunters_MC_materials.pkl" file.

Inputs:
(required to enter matid OR matname, with matid taking priority if conflicting)

- matid = ID number in the "Hunters_MC_materials" file
- matname = exact name of material in "Hunters_MC_materials" file
- matsource = exact source of material in "Hunters_MC_materials" file, only used when multiple
materials have identical names
- concentration_type = selection between 'weight fraction' (default if no formula) and 'atom fraction' (default if formula present) to be returned
- particle = selection of whether natural ('photons', default) or isotopic ('neutrons') elements are used
Note that if "enriched" or "depleted" appears in the material's name, particle='neutrons' is set automatically.

Outputs:
- mat_str = string containing the material's information, ready to be inserted directly into a MCNP/PHITS input deck
'''

if not matid and not matname:
print('Either "matid" or "matname" MUST be defined')
return None

# First, locate and open materials library
try:
user_paths = os.environ['PYTHONPATH'].split(os.pathsep)
lib_file = None
for i in user_paths:
if 'personal Python scripts' in i:
lib_file = i + r"\MC_tools\materials\Hunters_MC_materials"
if not lib_file:
print('Could not find "personal Python scripts" folder in PYTHONPATH; this folder contains the vital "MC_tools/materials/Hunters_MC_materials.pkl" file.')
except KeyError:
print('The PYTHONPATH environmental variable must be defined and contain the path to the directory holding "MC_tools/materials/Hunters_MC_materials.pkl"')
return None

# Load materials library
def load_obj(name ):
with open(name + '.pkl', 'rb') as f:
return pickle.load(f)
all_mats_list = load_obj(lib_file)

if matid: # use mat ID number
mi = int(matid)-1
matname = all_mats_list[mi]['name']
else: # use material name and possibly source too
# determine material
mi = None
# first check for exact matches
matching_mi = []
for i in range(len(all_mats_list)):
if all_mats_list[i]['name'].lower()==matname.lower():
matching_mi.append(i)
if len(matching_mi)==1:
mi = matching_mi[0]
elif len(matching_mi)>1:
print('Found multiple materials with this identical matname value:')
for mmi in matching_mi:
print('\tmatid={}  matname="{}"  source="{}"'.format(str(mmi+1),all_mats_list[mmi]['name'],all_mats_list[mmi]['source']))
if all_mats_list[mmi]['source'] and all_mats_list[mmi]['source']==matsource:
mi = mmi
print('\t\t^ matches inputed "matsource" and will be used')
if mi==None:
print('Please enter a "matsource" value identical to one of these two (or the matid).')
return None
else: # Exact material name not found
# search for similar entries
similar_mi = []
for i in range(len(all_mats_list)):
if matname.lower() in all_mats_list[i]['name'].lower():
similar_mi.append(i)
if len(similar_mi)==0:
print('No materials with that exact name or names containing "matname" were found.')
return None
elif len(similar_mi)==1:
mi = similar_mi[0]
print('Found one similar material (matid={}  matname="{}"  source="{}"); using it.'.format(str(mi+1),all_mats_list[mi]['name'],all_mats_list[mi]['source']))
else:
print('Found no material with exact "matname" but {} with similar names:'.format(len(similar_mi)))
for smi in similar_mi:
print('\tmatid={}  matname="{}"  source="{}"'.format(str(smi+1),all_mats_list[smi]['name'],all_mats_list[smi]['source']))
print('The first of these will be used.  If another material was desired, please enter its "matid" or exact "matname".')
mi = similar_mi[0]

# Now that material ID has been found, generate text entry
mat = all_mats_list[mi]
banner_width = 60
cc = '$' # comment character entry_text = '\n'+cc+'*'*banner_width + '\n' entry_text += cc+' {:<3d} : {} \n'.format(mi+1,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']) if concentration_type==None: # user did not select this, determine which is more appropriate automatically if mat['formula'] and mat['formula']!='-': concentration_type = 'atom fraction' else: concentration_type = 'weight fraction' entry_text += cc+' Composition by {} \n'.format(concentration_type) # Determine if neutron or photon entry will be used neutron_keyword_list = ['depleted','enriched',' heu',' leu','uranium','plutonium','uranyl'] if particle==None: # user did not select this, determine which is more appropriate automatically neutron_kw_found_in_name = False for nki in neutron_keyword_list: if nki in matname.lower(): neutron_kw_found_in_name = True if neutron_kw_found_in_name: particle = 'neutrons' else: particle = 'photons' for j in range(len(mat[particle][concentration_type]['ZA'])): if isinstance(mat[particle][concentration_type]['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(mi+1) else: mstr = ' '*4 ZZZAAA = mat[particle][concentration_type]['ZA'][j] if ZZZAAA == '-': ZZZAAA = mat['photons'][concentration_type]['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[particle][concentration_type]['value'][j],isotope) entry_text += cc+'*'*banner_width + '\n' return entry_text def parse_ttrack_file(path_to_dtrk_file,return_metadata=False): ''' Description: Parses the output file of a T-Track tally generated by PHITS. Note that this specific function assumes that the T-Track tally was one automatically generated by and corresponding to a T-Dchain tally but in principle works with any T-Track tally. This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format. It is currently only designed for tallies tracking a single particle (or "all"). Inputs: - path_to_dtrk_file = path to the T-Track tally output file to be parsed - return_metadata = Boolean indicating whether additional information is outputted with the flux (D=False) Outputs: - flux = a RxEx4 array containing regionwise fluxes [x-lower/x-upper/flux/abs_error] - dtrk_metadata (only returned if return_metadata=True) = list of length two - dtrk_metadata[0] = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format) - dtrk_metadata[1] = string denoting mesh type as either 'reg', 'xyz', or 'tet' ''' # Extract text from file f = open(path_to_dtrk_file) file_text = f.read() lines = file_text.split('\n') f.close() # Determine geometry type (mesh = reg, xyz, or tet) for line in lines: if 'mesh =' in line: meshtype = line.replace('mesh =','').strip().split()[0] break # Determine if original or reduced format (axis = eng or axis = dchain or axis=x/y/z) for line in lines: if 'axis =' in line: axistype = line.replace('axis =','').strip().split()[0] break if meshtype=='xyz': nx, ny, nz = 0, 0, 0 for line in lines: if 'nx =' in line: nx = int(line.replace('nx =','').strip().split()[0]) break for line in lines: if 'ny =' in line: ny = int(line.replace('ny =','').strip().split()[0]) break for line in lines: if 'nz =' in line: nz = int(line.replace('nz =','').strip().split()[0]) break nreg = nx*ny*nz dtrk_metadata = [axistype,meshtype] # Determine number of regions if axistype=='eng': nreg = file_text.count('# no. =') elif axistype in ['x','y','z']: #nreg = nx*ny*nz if axistype=='x': nbins = nx if axistype=='y': nbins = ny if axistype=='z': nbins = nz elif axistype=='dchain': for li, line in reversed(list(enumerate(lines))): #print(line) if '0 0 0.0000E+00 0.0000' in line: nreg = int(lines[li-1].split()[0]) break if axistype=='dchain': nEbins = 1968 else: for line in lines: if 'ne =' in line: nEbins = int(line.replace('ne =','').strip().split()[0]) break if meshtype=='xyz': flux = np.zeros((nx,ny,nz,nEbins,4)) else: flux = np.zeros((nreg,nEbins,4)) if axistype=='eng': in_flux_lines = False ei = 0 ri = -1 for line in lines: if '# no. =' in line: ri += 1 if '# e-lower e-upper ' in line: in_flux_lines = True continue if in_flux_lines: flux[ri,ei,:] = [float(x) for x in line.split()] flux[ri,ei,3] = flux[ri,ei,3]*flux[ri,ei,2] # convert relative error to absolute error ei += 1 if ei == nEbins: in_flux_lines = False ei = 0 elif axistype in ['x','y','z']: xi, yi, zi, ei = 0, 0, 0, 0 ie_edges, ix_edges, iy_edges, iz_edges = [],[],[],[] in_flux_lines = False for line in lines: if '# no. =' in line: parts = line.split() if 'ie' in parts: ei = int(parts[2+parts.index('ie')])-1 if 'ix' in parts: xi = int(parts[2+parts.index('ix')])-1 if 'iy' in parts: yi = int(parts[2+parts.index('iy')])-1 if 'iz' in parts: zi = int(parts[2+parts.index('iz')])-1 if '# e =' in line: parts = line.split() ie_edges.append([float(parts[4]),float(parts[6])]) if '# x =' in line: parts = line.split() ix_edges.append([float(parts[4]),float(parts[6])]) if '# y =' in line: parts = line.split() iy_edges.append([float(parts[4]),float(parts[6])]) if '# z =' in line: parts = line.split() iz_edges.append([float(parts[4]),float(parts[6])]) if '# {}-lower {}-upper '.format(axistype,axistype) in line: in_flux_lines = True continue if in_flux_lines: flux[xi,yi,zi,ei,:] = [float(x) for x in line.split()] flux[xi,yi,zi,ei,3] = flux[xi,yi,zi,ei,3]*flux[xi,yi,zi,ei,2] # convert relative error to absolute error if axistype=='x': xi += 1 if xi==nbins: in_flux_lines = False xi=0 if axistype=='y': yi += 1 if yi==nbins: in_flux_lines = False yi=0 if axistype=='z': zi += 1 if zi==nbins: in_flux_lines = False zi=0 elif axistype=='dchain': ebins = [20.0, 1.964033E+01,1.947734E+01,1.931570E+01,1.915541E+01,1.899644E+01,1.883880E+01,1.868246E+01,1.852742E+01,1.837367E+01,1.822119E+01,1.806998E+01,1.792002E+01,1.777131E+01,1.762383E+01,1.747757E+01,1.733253E+01,1.718869E+01,1.704605E+01,1.690459E+01,1.676430E+01,1.662518E+01, 1.648721E+01,1.635039E+01,1.621470E+01,1.608014E+01,1.594670E+01,1.581436E+01,1.568312E+01,1.555297E+01,1.542390E+01,1.529590E+01,1.516897E+01,1.504309E+01,1.491825E+01,1.479444E+01,1.467167E+01,1.454991E+01,1.442917E+01,1.430943E+01,1.419068E+01,1.407291E+01, 1.395612E+01,1.384031E+01,1.372545E+01,1.361155E+01,1.349859E+01,1.338657E+01,1.327548E+01,1.316531E+01,1.305605E+01,1.294770E+01,1.284025E+01,1.273370E+01,1.262802E+01,1.252323E+01,1.241930E+01,1.231624E+01,1.221403E+01,1.211267E+01,1.201215E+01,1.191246E+01, 1.181360E+01,1.171557E+01,1.161834E+01,1.152193E+01,1.142631E+01,1.133148E+01,1.123745E+01,1.114419E+01,1.105171E+01,1.095999E+01,1.086904E+01,1.077884E+01,1.068939E+01,1.060068E+01,1.051271E+01,1.042547E+01,1.033895E+01,1.025315E+01,1.016806E+01,1.008368E+01, 1.000000E+01,9.917013E+00,9.834715E+00,9.753099E+00,9.672161E+00,9.591895E+00,9.512294E+00,9.433354E+00,9.355070E+00,9.277435E+00,9.200444E+00,9.124092E+00,9.048374E+00,8.973284E+00,8.898818E+00,8.824969E+00,8.751733E+00,8.679105E+00,8.607080E+00,8.535652E+00, 8.464817E+00,8.394570E+00,8.324906E+00,8.255820E+00,8.187308E+00,8.119363E+00,8.051983E+00,7.985162E+00,7.918896E+00,7.853179E+00,7.788008E+00,7.723377E+00,7.659283E+00,7.595721E+00,7.532687E+00,7.470175E+00,7.408182E+00,7.346704E+00,7.285736E+00,7.225274E+00, 7.165313E+00,7.105850E+00,7.046881E+00,6.988401E+00,6.930406E+00,6.872893E+00,6.815857E+00,6.759294E+00,6.703200E+00,6.647573E+00,6.592406E+00,6.537698E+00,6.483443E+00,6.429639E+00,6.376282E+00,6.323367E+00,6.270891E+00,6.218851E+00,6.167242E+00,6.116062E+00, 6.065307E+00,6.014972E+00,5.965056E+00,5.915554E+00,5.866462E+00,5.817778E+00,5.769498E+00,5.721619E+00,5.674137E+00,5.627049E+00,5.580351E+00,5.534042E+00,5.488116E+00,5.442572E+00,5.397406E+00,5.352614E+00,5.308195E+00,5.264143E+00,5.220458E+00,5.177135E+00, 5.134171E+00,5.091564E+00,5.049311E+00,5.007408E+00,4.965853E+00,4.924643E+00,4.883775E+00,4.843246E+00,4.803053E+00,4.763194E+00,4.723666E+00,4.684465E+00,4.645590E+00,4.607038E+00,4.568805E+00,4.530890E+00,4.493290E+00,4.456001E+00,4.419022E+00,4.382350E+00, 4.345982E+00,4.309916E+00,4.274149E+00,4.238679E+00,4.203504E+00,4.168620E+00,4.134026E+00,4.099719E+00,4.065697E+00,4.031957E+00,3.998497E+00,3.965314E+00,3.932407E+00,3.899773E+00,3.867410E+00,3.835316E+00,3.803488E+00,3.771924E+00,3.740621E+00,3.709579E+00, 3.678794E+00,3.648265E+00,3.617989E+00,3.587965E+00,3.558189E+00,3.528661E+00,3.499377E+00,3.470337E+00,3.441538E+00,3.412978E+00,3.384654E+00,3.356566E+00,3.328711E+00,3.301087E+00,3.273692E+00,3.246525E+00,3.219583E+00,3.192864E+00,3.166368E+00,3.140091E+00, 3.114032E+00,3.088190E+00,3.062562E+00,3.037147E+00,3.011942E+00,2.986947E+00,2.962159E+00,2.937577E+00,2.913199E+00,2.889023E+00,2.865048E+00,2.841272E+00,2.817693E+00,2.794310E+00,2.771121E+00,2.748124E+00,2.725318E+00,2.702701E+00,2.680272E+00,2.658030E+00, 2.635971E+00,2.614096E+00,2.592403E+00,2.570889E+00,2.549554E+00,2.528396E+00,2.507414E+00,2.486605E+00,2.465970E+00,2.445505E+00,2.425211E+00,2.405085E+00,2.385126E+00,2.365332E+00,2.345703E+00,2.326237E+00,2.306932E+00,2.287787E+00,2.268802E+00,2.249973E+00, 2.231302E+00,2.212785E+00,2.194421E+00,2.176211E+00,2.158151E+00,2.140241E+00,2.122480E+00,2.104866E+00,2.087398E+00,2.070076E+00,2.052897E+00,2.035860E+00,2.018965E+00,2.002210E+00,1.985595E+00,1.969117E+00,1.952776E+00,1.936570E+00,1.920499E+00,1.904561E+00, 1.888756E+00,1.873082E+00,1.857538E+00,1.842122E+00,1.826835E+00,1.811675E+00,1.796640E+00,1.781731E+00,1.766944E+00,1.752281E+00,1.737739E+00,1.723318E+00,1.709017E+00,1.694834E+00,1.680770E+00,1.666821E+00,1.652989E+00,1.639271E+00,1.625667E+00,1.612176E+00, 1.598797E+00,1.585530E+00,1.572372E+00,1.559323E+00,1.546383E+00,1.533550E+00,1.520823E+00,1.508202E+00,1.495686E+00,1.483274E+00,1.470965E+00,1.458758E+00,1.446652E+00,1.434646E+00,1.422741E+00,1.410934E+00,1.399225E+00,1.387613E+00,1.376098E+00,1.364678E+00, 1.353353E+00,1.342122E+00,1.330984E+00,1.319938E+00,1.308985E+00,1.298122E+00,1.287349E+00,1.276666E+00,1.266071E+00,1.255564E+00,1.245145E+00,1.234812E+00,1.224564E+00,1.214402E+00,1.204324E+00,1.194330E+00,1.184418E+00,1.174589E+00,1.164842E+00,1.155175E+00, 1.145588E+00,1.136082E+00,1.126654E+00,1.117304E+00,1.108032E+00,1.098836E+00,1.089717E+00,1.080674E+00,1.071706E+00,1.062812E+00,1.053992E+00,1.045245E+00,1.036571E+00,1.027969E+00,1.019438E+00,1.010978E+00,1.002588E+00,9.942682E-01,9.860171E-01,9.778344E-01, 9.697197E-01,9.616723E-01,9.536916E-01,9.457772E-01,9.379285E-01,9.301449E-01,9.224259E-01,9.147709E-01,9.071795E-01,8.996511E-01,8.921852E-01,8.847812E-01,8.774387E-01,8.701570E-01,8.629359E-01,8.557746E-01,8.486728E-01,8.416299E-01,8.346455E-01,8.277190E-01, 8.208500E-01,8.140380E-01,8.072825E-01,8.005831E-01,7.939393E-01,7.873507E-01,7.808167E-01,7.743369E-01,7.679109E-01,7.615382E-01,7.552184E-01,7.489511E-01,7.427358E-01,7.365720E-01,7.304594E-01,7.243976E-01,7.183860E-01,7.124243E-01,7.065121E-01,7.006490E-01, 6.948345E-01,6.890683E-01,6.833499E-01,6.776790E-01,6.720551E-01,6.664779E-01,6.609470E-01,6.554620E-01,6.500225E-01,6.446282E-01,6.392786E-01,6.339734E-01,6.287123E-01,6.234948E-01,6.183206E-01,6.131893E-01,6.081006E-01,6.030542E-01,5.980496E-01,5.930866E-01, 5.881647E-01,5.832837E-01,5.784432E-01,5.736429E-01,5.688824E-01,5.641614E-01,5.594796E-01,5.548366E-01,5.502322E-01,5.456660E-01,5.411377E-01,5.366469E-01,5.321934E-01,5.277769E-01,5.233971E-01,5.190535E-01,5.147461E-01,5.104743E-01,5.062381E-01,5.020369E-01, 4.978707E-01,4.937390E-01,4.896416E-01,4.855782E-01,4.815485E-01,4.775523E-01,4.735892E-01,4.696591E-01,4.657615E-01,4.618963E-01,4.580631E-01,4.542618E-01,4.504920E-01,4.467535E-01,4.430460E-01,4.393693E-01,4.357231E-01,4.321072E-01,4.285213E-01,4.249651E-01, 4.214384E-01,4.179410E-01,4.144727E-01,4.110331E-01,4.076220E-01,4.042393E-01,4.008846E-01,3.975578E-01,3.942586E-01,3.909868E-01,3.877421E-01,3.845243E-01,3.813333E-01,3.781687E-01,3.750304E-01,3.719181E-01,3.688317E-01,3.657708E-01,3.627354E-01,3.597252E-01, 3.567399E-01,3.537795E-01,3.508435E-01,3.479320E-01,3.450446E-01,3.421812E-01,3.393415E-01,3.365254E-01,3.337327E-01,3.309631E-01,3.282166E-01,3.254928E-01,3.227916E-01,3.201129E-01,3.174564E-01,3.148219E-01,3.122093E-01,3.096183E-01,3.070489E-01,3.045008E-01, 3.019738E-01,2.994678E-01,2.985000E-01,2.972000E-01,2.969826E-01,2.945181E-01,2.920740E-01,2.896501E-01,2.872464E-01,2.848626E-01,2.824986E-01,2.801543E-01,2.778293E-01,2.755237E-01,2.732372E-01,2.709697E-01,2.687210E-01,2.664910E-01,2.642794E-01,2.620863E-01, 2.599113E-01,2.577544E-01,2.556153E-01,2.534941E-01,2.513904E-01,2.493042E-01,2.472353E-01,2.451835E-01,2.431488E-01,2.411310E-01,2.391299E-01,2.371455E-01,2.351775E-01,2.332258E-01,2.312903E-01,2.293709E-01,2.274674E-01,2.255797E-01,2.237077E-01,2.218512E-01, 2.200102E-01,2.181844E-01,2.163737E-01,2.145781E-01,2.127974E-01,2.110314E-01,2.092801E-01,2.075434E-01,2.058210E-01,2.041130E-01,2.024191E-01,2.007393E-01,1.990734E-01,1.974214E-01,1.957830E-01,1.941583E-01,1.925470E-01,1.909491E-01,1.893645E-01,1.877930E-01, 1.862346E-01,1.846891E-01,1.831564E-01,1.816364E-01,1.801291E-01,1.786342E-01,1.771518E-01,1.756817E-01,1.742237E-01,1.727779E-01,1.713441E-01,1.699221E-01,1.685120E-01,1.671136E-01,1.657268E-01,1.643514E-01,1.629875E-01,1.616349E-01,1.602936E-01,1.589634E-01, 1.576442E-01,1.563359E-01,1.550385E-01,1.537519E-01,1.524760E-01,1.512106E-01,1.499558E-01,1.487113E-01,1.474772E-01,1.462533E-01,1.450396E-01,1.438360E-01,1.426423E-01,1.414586E-01,1.402847E-01,1.391205E-01,1.379660E-01,1.368210E-01,1.356856E-01,1.345596E-01, 1.334429E-01,1.323355E-01,1.312373E-01,1.301482E-01,1.290681E-01,1.279970E-01,1.269348E-01,1.258814E-01,1.248368E-01,1.238008E-01,1.227734E-01,1.217545E-01,1.207441E-01,1.197421E-01,1.187484E-01,1.177629E-01,1.167857E-01,1.158165E-01,1.148554E-01,1.139022E-01, 1.129570E-01,1.120196E-01,1.110900E-01,1.101681E-01,1.092538E-01,1.083471E-01,1.074480E-01,1.065563E-01,1.056720E-01,1.047951E-01,1.039254E-01,1.030630E-01,1.022077E-01,1.013595E-01,1.005184E-01,9.968419E-02,9.885694E-02,9.803655E-02,9.722297E-02,9.641615E-02, 9.561602E-02,9.482253E-02,9.403563E-02,9.325525E-02,9.248135E-02,9.171388E-02,9.095277E-02,9.019798E-02,8.944945E-02,8.870714E-02,8.797098E-02,8.724094E-02,8.651695E-02,8.579897E-02,8.508695E-02,8.438084E-02,8.368059E-02,8.298615E-02,8.250000E-02,8.229747E-02, 8.161451E-02,8.093721E-02,8.026554E-02,7.959944E-02,7.950000E-02,7.893887E-02,7.828378E-02,7.763412E-02,7.698986E-02,7.635094E-02,7.571733E-02,7.508897E-02,7.446583E-02,7.384786E-02,7.323502E-02,7.262726E-02,7.202455E-02,7.142684E-02,7.083409E-02,7.024626E-02, 6.966330E-02,6.908519E-02,6.851187E-02,6.794331E-02,6.737947E-02,6.682031E-02,6.626579E-02,6.571586E-02,6.517051E-02,6.462968E-02,6.409333E-02,6.356144E-02,6.303396E-02,6.251086E-02,6.199211E-02,6.147765E-02,6.096747E-02,6.046151E-02,5.995976E-02,5.946217E-02, 5.896871E-02,5.847935E-02,5.799405E-02,5.751277E-02,5.703549E-02,5.656217E-02,5.609278E-02,5.562728E-02,5.516564E-02,5.470784E-02,5.425384E-02,5.380360E-02,5.335710E-02,5.291430E-02,5.247518E-02,5.203971E-02,5.160785E-02,5.117957E-02,5.075484E-02,5.033364E-02, 4.991594E-02,4.950170E-02,4.909090E-02,4.868351E-02,4.827950E-02,4.787884E-02,4.748151E-02,4.708747E-02,4.669671E-02,4.630919E-02,4.592488E-02,4.554376E-02,4.516581E-02,4.479099E-02,4.441928E-02,4.405066E-02,4.368510E-02,4.332257E-02,4.296305E-02,4.260651E-02, 4.225293E-02,4.190229E-02,4.155455E-02,4.120970E-02,4.086771E-02,4.052857E-02,4.019223E-02,3.985869E-02,3.952791E-02,3.919988E-02,3.887457E-02,3.855196E-02,3.823203E-02,3.791476E-02,3.760011E-02,3.728808E-02,3.697864E-02,3.667176E-02,3.636743E-02,3.606563E-02, 3.576633E-02,3.546952E-02,3.517517E-02,3.488326E-02,3.459377E-02,3.430669E-02,3.402199E-02,3.373965E-02,3.345965E-02,3.318198E-02,3.290662E-02,3.263353E-02,3.236272E-02,3.209415E-02,3.182781E-02,3.156368E-02,3.130174E-02,3.104198E-02,3.078437E-02,3.052890E-02, 3.027555E-02,3.002430E-02,2.977514E-02,2.952804E-02,2.928300E-02,2.903999E-02,2.879899E-02,2.856000E-02,2.850000E-02,2.832299E-02,2.808794E-02,2.785485E-02,2.762369E-02,2.739445E-02,2.716711E-02,2.700000E-02,2.694166E-02,2.671808E-02,2.649635E-02,2.627647E-02, 2.605841E-02,2.584215E-02,2.562770E-02,2.541502E-02,2.520411E-02,2.499495E-02,2.478752E-02,2.458182E-02,2.437782E-02,2.417552E-02,2.397489E-02,2.377593E-02,2.357862E-02,2.338295E-02,2.318890E-02,2.299646E-02,2.280562E-02,2.261636E-02,2.242868E-02,2.224255E-02, 2.205796E-02,2.187491E-02,2.169338E-02,2.151335E-02,2.133482E-02,2.115777E-02,2.098218E-02,2.080806E-02,2.063538E-02,2.046413E-02,2.029431E-02,2.012589E-02,1.995887E-02,1.979324E-02,1.962898E-02,1.946608E-02,1.930454E-02,1.914434E-02,1.898547E-02,1.882791E-02, 1.867166E-02,1.851671E-02,1.836305E-02,1.821066E-02,1.805953E-02,1.790966E-02,1.776104E-02,1.761364E-02,1.746747E-02,1.732251E-02,1.717876E-02,1.703620E-02,1.689482E-02,1.675461E-02,1.661557E-02,1.647768E-02,1.634094E-02,1.620533E-02,1.607085E-02,1.593748E-02, 1.580522E-02,1.567406E-02,1.554398E-02,1.541499E-02,1.528706E-02,1.516020E-02,1.503439E-02,1.490963E-02,1.478590E-02,1.466319E-02,1.454151E-02,1.442083E-02,1.430116E-02,1.418247E-02,1.406478E-02,1.394806E-02,1.383231E-02,1.371752E-02,1.360368E-02,1.349079E-02, 1.337883E-02,1.326780E-02,1.315770E-02,1.304851E-02,1.294022E-02,1.283283E-02,1.272634E-02,1.262073E-02,1.251599E-02,1.241212E-02,1.230912E-02,1.220697E-02,1.210567E-02,1.200521E-02,1.190558E-02,1.180678E-02,1.170880E-02,1.161163E-02,1.151527E-02,1.141970E-02, 1.132494E-02,1.123095E-02,1.113775E-02,1.104532E-02,1.095366E-02,1.086276E-02,1.077261E-02,1.068321E-02,1.059456E-02,1.050664E-02,1.041944E-02,1.033298E-02,1.024723E-02,1.016219E-02,1.007785E-02,9.994221E-03,9.911282E-03,9.829031E-03,9.747463E-03,9.666572E-03, 9.586352E-03,9.506797E-03,9.427903E-03,9.349664E-03,9.272074E-03,9.195127E-03,9.118820E-03,9.043145E-03,8.968099E-03,8.893675E-03,8.819869E-03,8.746676E-03,8.674090E-03,8.602106E-03,8.530719E-03,8.459926E-03,8.389719E-03,8.320095E-03,8.251049E-03,8.182576E-03, 8.114671E-03,8.047330E-03,7.980548E-03,7.914319E-03,7.848641E-03,7.783507E-03,7.718914E-03,7.654857E-03,7.591332E-03,7.528334E-03,7.465858E-03,7.403901E-03,7.342458E-03,7.281525E-03,7.221098E-03,7.161172E-03,7.101744E-03,7.042809E-03,6.984362E-03,6.926401E-03, 6.868921E-03,6.811918E-03,6.755388E-03,6.699327E-03,6.643731E-03,6.588597E-03,6.533920E-03,6.479697E-03,6.425924E-03,6.372597E-03,6.319712E-03,6.267267E-03,6.215257E-03,6.163678E-03,6.112528E-03,6.061802E-03,6.011496E-03,5.961609E-03,5.912135E-03,5.863072E-03, 5.814416E-03,5.766164E-03,5.718312E-03,5.670858E-03,5.623797E-03,5.577127E-03,5.530844E-03,5.484945E-03,5.439427E-03,5.394287E-03,5.349521E-03,5.305127E-03,5.261101E-03,5.217441E-03,5.174143E-03,5.131204E-03,5.088622E-03,5.046393E-03,5.004514E-03,4.962983E-03, 4.921797E-03,4.880952E-03,4.840447E-03,4.800277E-03,4.760441E-03,4.720936E-03,4.681758E-03,4.642906E-03,4.604375E-03,4.566165E-03,4.528272E-03,4.490693E-03,4.453426E-03,4.416468E-03,4.379817E-03,4.343471E-03,4.307425E-03,4.271679E-03,4.236230E-03,4.201075E-03, 4.166211E-03,4.131637E-03,4.097350E-03,4.063347E-03,4.029627E-03,3.996186E-03,3.963023E-03,3.930135E-03,3.897520E-03,3.865175E-03,3.833099E-03,3.801290E-03,3.769744E-03,3.738460E-03,3.707435E-03,3.676668E-03,3.646157E-03,3.615898E-03,3.585891E-03,3.556133E-03, 3.526622E-03,3.497355E-03,3.468332E-03,3.439549E-03,3.411005E-03,3.382698E-03,3.354626E-03,3.326787E-03,3.299179E-03,3.271800E-03,3.244649E-03,3.217722E-03,3.191019E-03,3.164538E-03,3.138276E-03,3.112233E-03,3.086405E-03,3.060792E-03,3.035391E-03,3.010202E-03, 2.985221E-03,2.960447E-03,2.935879E-03,2.911515E-03,2.887354E-03,2.863392E-03,2.839630E-03,2.816065E-03,2.792695E-03,2.769519E-03,2.746536E-03,2.723743E-03,2.701139E-03,2.678723E-03,2.656494E-03,2.634448E-03,2.612586E-03,2.590904E-03,2.569403E-03,2.548081E-03, 2.526935E-03,2.505965E-03,2.485168E-03,2.464545E-03,2.444092E-03,2.423809E-03,2.403695E-03,2.383747E-03,2.363965E-03,2.344347E-03,2.324892E-03,2.305599E-03,2.286465E-03,2.267490E-03,2.248673E-03,2.230012E-03,2.211506E-03,2.193153E-03,2.174953E-03,2.156904E-03, 2.139004E-03,2.121253E-03,2.103650E-03,2.086192E-03,2.068879E-03,2.051710E-03,2.034684E-03,2.017798E-03,2.001053E-03,1.984447E-03,1.967979E-03,1.951647E-03,1.935451E-03,1.919389E-03,1.903461E-03,1.887665E-03,1.871999E-03,1.856464E-03,1.841058E-03,1.825780E-03, 1.810628E-03,1.795602E-03,1.780701E-03,1.765923E-03,1.751268E-03,1.736735E-03,1.722323E-03,1.708030E-03,1.693855E-03,1.679798E-03,1.665858E-03,1.652034E-03,1.638324E-03,1.624728E-03,1.611245E-03,1.597874E-03,1.584613E-03,1.571463E-03,1.558422E-03,1.545489E-03, 1.532663E-03,1.519944E-03,1.507331E-03,1.494822E-03,1.482417E-03,1.470115E-03,1.457915E-03,1.445816E-03,1.433817E-03,1.421919E-03,1.410118E-03,1.398416E-03,1.386811E-03,1.375303E-03,1.363889E-03,1.352571E-03,1.341346E-03,1.330215E-03,1.319176E-03,1.308228E-03, 1.297372E-03,1.286605E-03,1.275928E-03,1.265339E-03,1.254839E-03,1.244425E-03,1.234098E-03,1.223857E-03,1.213700E-03,1.203628E-03,1.193639E-03,1.183734E-03,1.173910E-03,1.164168E-03,1.154507E-03,1.144926E-03,1.135425E-03,1.126002E-03,1.116658E-03,1.107391E-03, 1.098201E-03,1.089088E-03,1.080050E-03,1.071087E-03,1.062198E-03,1.053383E-03,1.044641E-03,1.035972E-03,1.027375E-03,1.018849E-03,1.010394E-03,1.002009E-03,9.936937E-04,9.854473E-04,9.772694E-04,9.691593E-04,9.611165E-04,9.531405E-04,9.452307E-04,9.373865E-04, 9.296074E-04,9.218928E-04,9.142423E-04,9.066553E-04,8.991312E-04,8.916696E-04,8.842699E-04,8.769316E-04,8.696542E-04,8.624372E-04,8.552801E-04,8.481824E-04,8.411435E-04,8.341631E-04,8.272407E-04,8.203756E-04,8.135676E-04,8.068160E-04,8.001205E-04,7.934805E-04, 7.868957E-04,7.803654E-04,7.738894E-04,7.674671E-04,7.610981E-04,7.547820E-04,7.485183E-04,7.423066E-04,7.361464E-04,7.300373E-04,7.239790E-04,7.179709E-04,7.120126E-04,7.061038E-04,7.002441E-04,6.944330E-04,6.886701E-04,6.829550E-04,6.772874E-04,6.716668E-04, 6.660928E-04,6.605651E-04,6.550832E-04,6.496469E-04,6.442557E-04,6.389092E-04,6.336071E-04,6.283489E-04,6.231345E-04,6.179633E-04,6.128350E-04,6.077492E-04,6.027057E-04,5.977040E-04,5.927438E-04,5.878248E-04,5.829466E-04,5.781089E-04,5.733114E-04,5.685536E-04, 5.638354E-04,5.591563E-04,5.545160E-04,5.499142E-04,5.453506E-04,5.408249E-04,5.363368E-04,5.318859E-04,5.274719E-04,5.230946E-04,5.187536E-04,5.144486E-04,5.101793E-04,5.059455E-04,5.017468E-04,4.975830E-04,4.934537E-04,4.893587E-04,4.852976E-04,4.812703E-04, 4.772763E-04,4.733156E-04,4.693877E-04,4.654923E-04,4.616294E-04,4.577984E-04,4.539993E-04,4.502317E-04,4.464953E-04,4.427900E-04,4.391154E-04,4.354713E-04,4.318575E-04,4.282736E-04,4.247195E-04,4.211949E-04,4.176995E-04,4.142332E-04,4.107955E-04,4.073865E-04, 4.040057E-04,4.006530E-04,3.973281E-04,3.940308E-04,3.907608E-04,3.875180E-04,3.843021E-04,3.811129E-04,3.779502E-04,3.748137E-04,3.717032E-04,3.686185E-04,3.655595E-04,3.625258E-04,3.595173E-04,3.565338E-04,3.535750E-04,3.506408E-04,3.477309E-04,3.448452E-04, 3.419834E-04,3.391454E-04,3.363309E-04,3.335398E-04,3.307719E-04,3.280269E-04,3.253047E-04,3.226051E-04,3.199279E-04,3.172729E-04,3.146399E-04,3.120288E-04,3.094394E-04,3.068715E-04,3.043248E-04,3.017993E-04,2.992948E-04,2.968110E-04,2.943479E-04,2.919052E-04, 2.894827E-04,2.870804E-04,2.846980E-04,2.823354E-04,2.799924E-04,2.776688E-04,2.753645E-04,2.730793E-04,2.708131E-04,2.685657E-04,2.663370E-04,2.641267E-04,2.619348E-04,2.597611E-04,2.576054E-04,2.554676E-04,2.533476E-04,2.512451E-04,2.491601E-04,2.470924E-04, 2.450418E-04,2.430083E-04,2.409917E-04,2.389917E-04,2.370084E-04,2.350416E-04,2.330910E-04,2.311567E-04,2.292384E-04,2.273360E-04,2.254494E-04,2.235784E-04,2.217230E-04,2.198830E-04,2.180583E-04,2.162487E-04,2.144541E-04,2.126744E-04,2.109095E-04,2.091592E-04, 2.074234E-04,2.057021E-04,2.039950E-04,2.023021E-04,2.006233E-04,1.989584E-04,1.973073E-04,1.956699E-04,1.940461E-04,1.924358E-04,1.908388E-04,1.892551E-04,1.876845E-04,1.861269E-04,1.845823E-04,1.830505E-04,1.815315E-04,1.800250E-04,1.785310E-04,1.770494E-04, 1.755802E-04,1.741231E-04,1.726781E-04,1.712451E-04,1.698239E-04,1.684146E-04,1.670170E-04,1.656310E-04,1.642565E-04,1.628933E-04,1.615415E-04,1.602010E-04,1.588715E-04,1.575531E-04,1.562456E-04,1.549489E-04,1.536631E-04,1.523879E-04,1.511232E-04,1.498691E-04, 1.486254E-04,1.473920E-04,1.461688E-04,1.449558E-04,1.437529E-04,1.425599E-04,1.413768E-04,1.402036E-04,1.390401E-04,1.378862E-04,1.367420E-04,1.356072E-04,1.344818E-04,1.333658E-04,1.322590E-04,1.311615E-04,1.300730E-04,1.289935E-04,1.279231E-04,1.268615E-04, 1.258087E-04,1.247646E-04,1.237292E-04,1.227024E-04,1.216842E-04,1.206744E-04,1.196729E-04,1.186798E-04,1.176949E-04,1.167182E-04,1.157496E-04,1.147890E-04,1.138364E-04,1.128917E-04,1.119548E-04,1.110258E-04,1.101044E-04,1.091907E-04,1.082845E-04,1.073859E-04, 1.064947E-04,1.056110E-04,1.047345E-04,1.038654E-04,1.030034E-04,1.021486E-04,1.013009E-04,1.004603E-04,9.962658E-05,9.879981E-05,9.797990E-05,9.716679E-05,9.636043E-05,9.556076E-05,9.476773E-05,9.398128E-05,9.320136E-05,9.242791E-05,9.166088E-05,9.090021E-05, 9.014586E-05,8.939776E-05,8.865588E-05,8.792015E-05,8.719052E-05,8.646695E-05,8.574939E-05,8.503778E-05,8.433208E-05,8.363223E-05,8.293819E-05,8.224991E-05,8.156734E-05,8.089044E-05,8.021915E-05,7.955344E-05,7.889325E-05,7.823854E-05,7.758926E-05,7.694537E-05, 7.630682E-05,7.567357E-05,7.504558E-05,7.442280E-05,7.380518E-05,7.319270E-05,7.258529E-05,7.198293E-05,7.138556E-05,7.079316E-05,7.020566E-05,6.962305E-05,6.904527E-05,6.847228E-05,6.790405E-05,6.734053E-05,6.678169E-05,6.622749E-05,6.567789E-05,6.513285E-05, 6.459233E-05,6.405630E-05,6.352471E-05,6.299754E-05,6.247474E-05,6.195628E-05,6.144212E-05,6.093223E-05,6.042657E-05,5.992511E-05,5.942781E-05,5.893464E-05,5.844556E-05,5.796053E-05,5.747954E-05,5.700253E-05,5.652948E-05,5.606036E-05,5.559513E-05,5.513376E-05, 5.467623E-05,5.422248E-05,5.377251E-05,5.332626E-05,5.288373E-05,5.244486E-05,5.200963E-05,5.157802E-05,5.114999E-05,5.072551E-05,5.030456E-05,4.988709E-05,4.947309E-05,4.906253E-05,4.865538E-05,4.825160E-05,4.785117E-05,4.745407E-05,4.706026E-05,4.666972E-05, 4.628243E-05,4.589834E-05,4.551744E-05,4.513971E-05,4.476511E-05,4.439361E-05,4.402521E-05,4.365985E-05,4.329753E-05,4.293822E-05,4.258189E-05,4.222851E-05,4.187807E-05,4.153054E-05,4.118589E-05,4.084410E-05,4.050514E-05,4.016900E-05,3.983565E-05,3.950507E-05, 3.917723E-05,3.885211E-05,3.852969E-05,3.820994E-05,3.789285E-05,3.757838E-05,3.726653E-05,3.695727E-05,3.665057E-05,3.634642E-05,3.604479E-05,3.574566E-05,3.544902E-05,3.515484E-05,3.486310E-05,3.457378E-05,3.428686E-05,3.400233E-05,3.372015E-05,3.344032E-05, 3.316281E-05,3.288760E-05,3.261467E-05,3.234401E-05,3.207560E-05,3.180942E-05,3.154544E-05,3.128365E-05,3.102404E-05,3.076658E-05,3.051126E-05,3.025805E-05,3.000695E-05,2.975793E-05,2.951098E-05,2.926607E-05,2.902320E-05,2.878235E-05,2.854349E-05,2.830662E-05, 2.807171E-05,2.783875E-05,2.760773E-05,2.737862E-05,2.715141E-05,2.692609E-05,2.670264E-05,2.648104E-05,2.626128E-05,2.604335E-05,2.582722E-05,2.561289E-05,2.540033E-05,2.518954E-05,2.498050E-05,2.477320E-05,2.456761E-05,2.436373E-05,2.416154E-05,2.396104E-05, 2.376219E-05,2.356499E-05,2.336944E-05,2.317550E-05,2.298317E-05,2.279244E-05,2.260329E-05,2.241572E-05,2.222969E-05,2.204522E-05,2.186227E-05,2.168084E-05,2.150092E-05,2.132249E-05,2.114554E-05,2.097006E-05,2.079603E-05,2.062345E-05,2.045231E-05,2.028258E-05, 2.011426E-05,1.994734E-05,1.978180E-05,1.961764E-05,1.945484E-05,1.929339E-05,1.913328E-05,1.897449E-05,1.881703E-05,1.866087E-05,1.850601E-05,1.835244E-05,1.820013E-05,1.804910E-05,1.789931E-05,1.775077E-05,1.760346E-05,1.745738E-05,1.731250E-05,1.716883E-05, 1.702635E-05,1.688506E-05,1.674493E-05,1.660597E-05,1.646816E-05,1.633150E-05,1.619597E-05,1.606156E-05,1.592827E-05,1.579609E-05,1.566500E-05,1.553500E-05,1.540608E-05,1.527823E-05,1.515144E-05,1.502570E-05,1.490101E-05,1.477735E-05,1.465472E-05,1.453310E-05, 1.441250E-05,1.429289E-05,1.417428E-05,1.405665E-05,1.394000E-05,1.382431E-05,1.370959E-05,1.359582E-05,1.348299E-05,1.337110E-05,1.326014E-05,1.315010E-05,1.304097E-05,1.293274E-05,1.282542E-05,1.271898E-05,1.261343E-05,1.250876E-05,1.240495E-05,1.230201E-05, 1.219991E-05,1.209867E-05,1.199827E-05,1.189870E-05,1.179995E-05,1.170203E-05,1.160492E-05,1.150861E-05,1.141311E-05,1.131839E-05,1.122446E-05,1.113132E-05,1.103894E-05,1.094733E-05,1.085648E-05,1.076639E-05,1.067704E-05,1.058843E-05,1.050056E-05,1.041342E-05, 1.032701E-05,1.024130E-05,1.015631E-05,1.007203E-05,9.988446E-06,9.905554E-06,9.823351E-06,9.741830E-06,9.660985E-06,9.580812E-06,9.501303E-06,9.422455E-06,9.344261E-06,9.266715E-06,9.189814E-06,9.113550E-06,9.037919E-06,8.962916E-06,8.888536E-06,8.814772E-06, 8.741621E-06,8.669077E-06,8.597135E-06,8.525790E-06,8.455037E-06,8.384871E-06,8.315287E-06,8.246281E-06,8.177848E-06,8.109982E-06,8.042680E-06,7.975936E-06,7.909746E-06,7.844105E-06,7.779009E-06,7.714454E-06,7.650434E-06,7.586945E-06,7.523983E-06,7.461544E-06, 7.399622E-06,7.338215E-06,7.277317E-06,7.216925E-06,7.157034E-06,7.097640E-06,7.038739E-06,6.980326E-06,6.922399E-06,6.864952E-06,6.807981E-06,6.751484E-06,6.695455E-06,6.639892E-06,6.584789E-06,6.530144E-06,6.475952E-06,6.422210E-06,6.368914E-06,6.316060E-06, 6.263645E-06,6.211665E-06,6.160116E-06,6.108995E-06,6.058298E-06,6.008022E-06,5.958164E-06,5.908719E-06,5.859684E-06,5.811056E-06,5.762832E-06,5.715008E-06,5.667581E-06,5.620547E-06,5.573904E-06,5.527647E-06,5.481775E-06,5.436284E-06,5.391169E-06,5.346430E-06, 5.302061E-06,5.258061E-06,5.214426E-06,5.171153E-06,5.128239E-06,5.085681E-06,5.043477E-06,4.918953E-06,4.797503E-06,4.679053E-06,4.563526E-06,4.450853E-06,4.340961E-06,4.233782E-06,4.129250E-06,4.000000E-06,3.927860E-06,3.830880E-06,3.736300E-06,3.644050E-06, 3.554080E-06,3.466330E-06,3.380750E-06,3.300000E-06,3.217630E-06,3.137330E-06,3.059020E-06,2.983490E-06,2.909830E-06,2.837990E-06,2.767920E-06,2.720000E-06,2.659320E-06,2.600000E-06,2.550000E-06,2.485030E-06,2.421710E-06,2.382370E-06,2.360000E-06,2.300270E-06, 2.242050E-06,2.185310E-06,2.130000E-06,2.100000E-06,2.059610E-06,2.020000E-06,1.974490E-06,1.930000E-06,1.884460E-06,1.855390E-06,1.840000E-06,1.797000E-06,1.755000E-06,1.711970E-06,1.670000E-06,1.629510E-06,1.590000E-06,1.544340E-06,1.500000E-06,1.475000E-06, 1.440000E-06,1.404560E-06,1.370000E-06,1.337500E-06,1.300000E-06,1.267080E-06,1.235000E-06,1.202060E-06,1.170000E-06,1.150000E-06,1.123000E-06,1.110000E-06,1.097000E-06,1.080000E-06,1.071000E-06,1.045000E-06,1.035000E-06,1.020000E-06,9.960000E-07,9.860000E-07, 9.720000E-07,9.500000E-07,9.300000E-07,9.100000E-07,8.764250E-07,8.600000E-07,8.500000E-07,8.194500E-07,7.900000E-07,7.800000E-07,7.415500E-07,7.050000E-07,6.825600E-07,6.531500E-07,6.250000E-07,5.952800E-07,5.669600E-07,5.400000E-07,5.315800E-07,5.196200E-07, 5.000000E-07,4.850000E-07,4.670100E-07,4.496800E-07,4.330000E-07,4.139900E-07,4.000000E-07,3.910000E-07,3.699300E-07,3.500000E-07,3.346600E-07,3.200000E-07,3.145000E-07,3.000000E-07,2.800000E-07,2.635100E-07,2.480000E-07,2.335800E-07,2.200000E-07,2.091400E-07, 1.988100E-07,1.890000E-07,1.800000E-07,1.697100E-07,1.600000E-07,1.530300E-07,1.463700E-07,1.400000E-07,1.340000E-07,1.150000E-07,1.000000E-07,9.500000E-08,8.000000E-08,7.700000E-08,6.700000E-08,5.800000E-08,5.000000E-08,4.200000E-08,3.500000E-08,3.000000E-08, 2.500000E-08,2.000000E-08,1.500000E-08,1.000000E-08,6.900000E-09,5.000000E-09,3.000000E-09,1.000010E-11 ] ebins = ebins[::-1] in_flux_lines = False for line in lines: if '# num ie flux r.err' in line: in_flux_lines = True continue if '0 0 0.0000E+00 0.0000' in line: in_flux_lines = False break if in_flux_lines: vals = line.split() ri = int(vals[0])-1 ei = int(vals[1])-1 fval = np.float(vals[2]) ferr = np.float(vals[3]) flux[ri,ei,0] = ebins[ei] flux[ri,ei,1] = ebins[ei+1] flux[ri,ei,2] = fval flux[ri,ei,3] = fval*ferr if return_metadata: return flux, dtrk_metadata else: return flux def parse_tdeposit_file(path_to_tdeposit_file,return_metadata=False): ''' Description: Parses the output file of a T-Deposit tally generated by PHITS. This works for region, xyz, and tetrahedral mesh geometries. It has only been tested for output=deposit, axis=eng. It is currently only designed for tallies tracking a single particle (or "all"). Inputs: - path_to_tdeposit_file = path to the T-Deposit tally output file to be parsed - return_metadata = Boolean indicating whether additional information is outputted with the flux (D=False) Outputs: - deposit = a RxEx4 array containing regionwise T-Deposit tally output [Elower/Eupper/deposit/abs_error] - deposit_metadata (only returned if return_metadata=True) = list of length two - deposit_metadata[0] = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format) - deposit_metadata[1] = string denoting mesh type as either 'reg', 'xyz', or 'tet' ''' # Extract text from file f = open(path_to_tdeposit_file) file_text = f.read() lines = file_text.split('\n') f.close() # Determine geometry type (mesh = reg, xyz, or tet) for line in lines: if 'mesh =' in line: meshtype = line.replace('mesh =','').strip().split()[0] break # Determine if original or reduced format (axis = eng or axis = dchain) for line in lines: if 'axis =' in line: axistype = line.replace('axis =','').strip().split()[0] break # Double check for li, line in enumerate(lines): if li>500: break if '# e-lower e-upper neutron r.err ' in line: axistype='eng' break deposit_metadata = [axistype,meshtype] # Determine number of regions nreg = file_text.count('# no. =') for line in lines: if 'ne =' in line: nEbins = int(line.replace('ne =','').strip().split()[0]) break deposit = np.zeros((nreg,nEbins,4)) in_deposit_lines = False ei = 0 ri = -1 for line in lines: if '# no. =' in line: ri += 1 if '# e-lower e-upper ' in line: in_deposit_lines = True continue if in_deposit_lines: deposit[ri,ei,:] = [float(x) for x in line.split()] deposit[ri,ei,3] = deposit[ri,ei,3]*deposit[ri,ei,2] # convert relative error to absolute error ei += 1 if ei == nEbins: in_deposit_lines = False ei = 0 if return_metadata: return deposit, deposit_metadata else: return deposit def parse_dyld_files(path_to_dyld_file,iredufmt=None): ''' Description: Parses the output files of a T-Yield tally generated by PHITS with axis=dchain. This function assumes that the T-Yield tally was one automatically generated by and corresponding to a T-Dchain tally (axis=dchain). This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format. Inputs: - path_to_dyld_file = path to the T-Yield tally output file to be parsed (the *_err.dyld file of the same name is automatically searched for and read, if present) - iredufmt = (DEPRICATED; this is now determined automatically) integer 1 or 0 specifying how the xyz meshes are ordered relative to the internal region numbers. In the new format (1), region indices are incremented as x->y->z (x=innermost loop); this is reversed in the old format (0). Ultimately, this corresponds to the same iredufmt parameter in PHITS/DCHAIN, 1='new' and 0='old'. This variable is only used for xyz meshes where this ordering matters. Outputs: - yields = a RxNx2 array containing regionwise yields (and their absolute uncertainties) for all nuclides produced in T-Yield - nuclide_names_yld = a length N list of all nuclide names in order ''' def ZAM_to_Dname(ZAM): ''' Description: Converts a ZZZAAAM number to a DCHAIN-formatted nuclide name Inputs: - ZZZAAAM = nuclide identification ineger, calculated as 10000\*Z + 10\*A + m Outputs: - Dname = nuclide identification string in DCHAIN format ''' elms = ["n ",\ "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\ "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\ "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\ "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\ "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\ "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\ "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\ "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\ "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\ "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\ "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\ "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"] m = int(str(ZAM)[-1]) A = int(str(ZAM)[-4:-1]) Z = int(str(ZAM)[:-4]) sym = elms[Z] A_str = '{:>3}'.format(A) m_str_list = [' ','m','n'] m_str = m_str_list[m] Dname = sym + A_str + m_str return Dname # Extract text from file f = open(path_to_dyld_file) file_text = f.read() lines = file_text.split('\n') f.close() # determine if in reduced format iredufmt=0 for li, line in enumerate(lines): if "# num nucleusID yield r.err" in line: iredufmt=1 break if "isotope production #" in line: iredufmt=0 break # Get error data if available if iredufmt==0: try: f_err = open(path_to_dyld_file.replace('.dyld','_err.dyld')) file_text_err = f_err.read() lines_err = file_text_err.split('\n') f_err.close() err_dyld_found = True except: file_text_err = None lines_err = None err_dyld_found = False # Determine geometry type (mesh = reg, xyz, or tet) for line in lines: if 'mesh =' in line: meshtype = line.replace('mesh =','').strip().split()[0] break # If xyz mesh, need to find mesh dimensions: if meshtype=='xyz': for line in lines: if 'nx =' in line: nx = int(line.replace('nx =','').strip().split()[0]) elif 'ny =' in line: ny = int(line.replace('ny =','').strip().split()[0]) elif 'nz =' in line: nz = int(line.replace('nz =','').strip().split()[0]) break # Find starting line for li, line in enumerate(lines): if 'nuclear yield (or production)' in line: li_start = li break if iredufmt==1: # Count number of nuclides present in whole file nreg = 0 nuc_id_list = [] for li, line in enumerate(lines): if li <= li_start+3: continue # in header vals = line.strip().split() if int(vals[0])==0: break # reached end if int(vals[0])>nreg: nreg = int(vals[0]) zzzaaam = int(vals[1]) if zzzaaam not in nuc_id_list: nuc_id_list.append(zzzaaam) nnuc = len(nuc_id_list) yields = np.zeros((nreg,nnuc,2)) nuclide_names_yld = [] # Get names nuc_id_list = sorted(nuc_id_list) for id in nuc_id_list: nuclide_names_yld.append(ZAM_to_Dname(id)) # Get values for li, line in enumerate(lines): if li <= li_start+3: continue # in header vals = line.strip().split() if int(vals[0])==0: break # reached end ri = int(vals[0]) - 1 zzzaaam = int(vals[1]) ni = nuc_id_list.index(zzzaaam) yields[ri,ni,0] = np.float(vals[2]) yields[ri,ni,1] = np.float(vals[3])*np.float(vals[2]) else: # old ''traditional'' format # Count number of nuclides present in whole file nnuc = 0 for li, line in enumerate(lines): if li <= li_start+2: continue # skip header lines if 'isotope production' in line: N_bounds = line.strip().split('=')[-1].split() N_bounds = [int(i) for i in N_bounds] nnuc += N_bounds[1] - N_bounds[0] + 1 # Determine number of regions nreg = 0 for li, line in enumerate(lines): if li <= li_start+4: continue # skip header lines if len(line) < 2: break # reached end of first element block nreg += 1 yields = np.zeros((nreg,nnuc,2)) nuclide_names_yld = [] # Extract yield data ni = 0 # nuclide index ni_newstart = 0 ri = 0 # region index for li, line in enumerate(lines): if li <= li_start+2: continue # skip header lines if len(line) < 2: continue # skip line breaks if 'isotope production' in line: # extract Z and A info Z = int(line.strip().split('-')[0]) N_bounds = line.strip().split('=')[-1].split() N_bounds = [int(i) for i in N_bounds] N_list = [] for i in range(N_bounds[1]-N_bounds[0]+1): N_list.append(N_bounds[0]+i) nisotopes = len(N_list) A_list = [N+Z for N in N_list] ni_newstart = len(nuclide_names_yld) for A in A_list: ZAM = 10*A + 10000*Z nuclide_names_yld.append(ZAM_to_Dname(ZAM)) on_buffer_line = True continue if on_buffer_line: ri = 0 on_buffer_line = False continue if '# Information for Restart Calculation' in line: break # reached end of useful info # Only lines making it to this point will be ones with region and yield data vals = line.strip().split() if meshtype=='xyz': yvals = vals[3:] if err_dyld_found: yvals_rerr = lines_err[li].strip().split()[3:] jx,jy,jz = int(vals[0]),int(vals[1]),int(vals[2]) rii = jz + (jy-1)*nz + (jx-1)*(nz*ny) #if iredufmt==1: # rii = jx + (jy-1)*nx + (jz-1)*(nx*ny) #else: # rii = jz + (jy-1)*nz + (jx-1)*(nz*ny) else: yvals = vals[1:] if err_dyld_found: yvals_rerr = lines_err[li].split()[1:] rii = ri for i in range(nisotopes): yields[rii,ni_newstart+i,0] = np.float(yvals[i]) if err_dyld_found: yields[rii,ni_newstart+i,1] = np.float(yvals_rerr[i])*yields[rii,ni_newstart+i,0] ri += 1 return yields, nuclide_names_yld ''' ************************************************************************************************** ----------------------------------- PLOTTING-RELATED FUNCTIONS ----------------------------------- ************************************************************************************************** ''' def generate_line_bar_coordinates(xbins,yvals,yerrs=[]): """ Description: Converts a set of bin boundaries and bin contents to coordinates mapping a bar plot if drawn with a line Inputs: - xbins = list of length N+1 bin boundary values - yvals = list of length N bin content values - yerrs = (optional) list of length N absolute uncertainties of bin content values Outputs: - newx = list of length 2N + 2 of x-coordinates mapping a 'bar plot' of the input histogram data - newy = list of length 2N + 2 of y-coordinates mapping a 'bar plot' of the input histogram data - newyerr = (optional) list of length 2N + 2 of y-coordinates mapping a 'bar plot' of the input histogram data """ if len(yvals) != (len(xbins)-1): pstr = 'xbins should be a list of bin edges of length one more than yvals, the values associated with the contents of each bin' + '\n' pstr += 'provided input arrays had lengths of {} for xbins and {} for yvals'.format(str(len(xbins)),str(len(yvals))) print(pstr) return 0 newx = [xbins[0],xbins[0]] newy = [0,yvals[0]] if len(yerrs)!=0: newyerr = [0,yvals[0]] for i in range(len(xbins)-2): newx.append(xbins[i+1]) newx.append(xbins[i+1]) newy.append(yvals[i]) newy.append(yvals[i+1]) if len(yerrs)!=0: newyerr.append(yerrs[i]) newyerr.append(yerrs[i+1]) newx.append(xbins[-1]) newx.append(xbins[-1]) newy.append(yvals[-1]) newy.append(0) if len(yerrs)!=0: newyerr.append(yerrs[-1]) newyerr.append(0) return newx, newy, newyerr else: return newx, newy def colors_list_6(di): ''' Colorbrewer qualitative color cycle scheme 6 (modified) See: https://colorbrewer2.org/#type=qualitative&scheme=Set1&n=6 ''' #colors_list = ['b','g','r','c','m','y'] # Python 3 old default #colors_list = ['#b2182b','#d6604d','#f4a582','#92c5de','#4393c3','#2166ac'] # blue to red cold #colors_list = ['#d73027','#f46d43','#fdae61','#abd9e9','#74add1','#4575b4'] # blue to red warm #colors_list = ['#762a83','#9970ab','#c2a5cf','#a6dba0','#5aae61','#1b7837'] # purple to green #colors_list = ['#40004b','#762a83','#9970ab','#5aae61','#1b7837','#00441b'] # purple to green darker #colors_list = ["#afa83a","#7f63b8","#56ae6c","#b84c7d","#ac863f","#b94d3d"] # iWantHue 1 #colors_list = ['#1b9e77','#d95f02','#7570b3','#e7298a','#66a61e','#e6ab02'] # qualitative dark 1 colors_list = ['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#a65628'] # qualitative dark 2 return colors_list[di%6] def colors_list_12(di): ''' Colorbrewer qualitative color cycle scheme 6 (expanded) See: https://colorbrewer2.org/#type=qualitative&scheme=Set1&n=6 ''' colors_list = ['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#a65628','#8dd3c7','#bebada','#fb8072','#80b1d3','#fdb462','#b3de69'] return colors_list[di%12] def colors_list_10(di): ''' Default cycle as of matplotlib v3.1.1 See: https://matplotlib.org/3.1.1/users/dflt_style_changes.html ''' colors_list = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'] return colors_list[di%10] def colors_list_20(di): ''' List of 20 'distinct' colors See: https://sashamaps.net/docs/resources/20-colors/ ''' colors_list = ['#e6194B', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4', '#469990', '#dcbeff', '#9A6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#a9a9a9'] return colors_list[di%20] def colors_list_64(di): ''' List of 64 'distinct' colors See: https://stackoverflow.com/a/20298027 ''' colors_list = ['#00FF00','#0000FF','#FF0000','#01FFFE','#FFA6FE','#FFDB66','#006401','#010067','#95003A','#007DB5','#FF00F6','#FFEEE8','#774D00','#90FB92','#0076FF','#D5FF00','#FF937E','#6A826C','#FF029D','#FE8900','#7A4782','#7E2DD2','#85A900','#FF0056','#A42400','#00AE7E','#683D3B','#BDC6FF','#263400','#BDD393','#00B917','#9E008E','#001544','#C28C9F','#FF74A3','#01D0FF','#004754','#E56FFE','#788231','#0E4CA1','#91D0CB','#BE9970','#968AE8','#BB8800','#43002C','#DEFF74','#00FFC6','#FFE502','#620E00','#008F9C','#98FF52','#7544B1','#B500FF','#00FF78','#FF6E41','#005F39','#6B6882','#5FAD4E','#A75740','#A5FFD2','#FFB167','#009BFF','#E85EBE','#000000'] return colors_list[di%64] def get_colormap(cmap_str): ''' Description: Retrieve a matplotlib colormap using just its string name See available colormaps: https://matplotlib.org/3.3.1/tutorials/colors/colormaps.html Dependencies: - from matplotlib import cm Inputs: - cmap_str = string of name of colormap Outputs: - callable colormap object which can be provided to plotting functions or evaluated for any value from 0 to 1 ''' return cm.get_cmap(cmap_str) def truncate_colormap(cmap, min_val=0.0, max_val=1.0, n=100): ''' Description: Truncate a colormap object's bounds to a subset of the original colormap, renormalizing new bounds to [0,1]. For instance, the upper and lower 20% of a colormap could be removed with cmap=truncate_colormap(cmap, min_val=0.2, max_val=0.8). Dependencies: - from matplotlib.colors import LinearSegmentedColormap - import numpy as np Inputs: - cmap = callable colormap object or string of matplotlib colormap name to be truncated and rescaled - min_val = float specifying new lower bound of cmap in [0,1) (D=0.0) - max_val = float specifying new upper bound of cmap in (0,1] (D=1.0) - n = integer specifying number of bins cmap will be subdivided into (D=100); note that nearest neighbor linear interpolation will be used when providing values to and evaluating the new colormap. Higher values of n just correspond to a higher-quality and more accurate interpolation. Outputs: - callable colormap object which can be provided to plotting functions or evaluated for any value from 0 to 1 ''' if isinstance(cmap,str): cmap = get_colormap(cmap) new_cmap = LinearSegmentedColormap.from_list( 'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=min_val, b=max_val), cmap(np.linspace(min_val, max_val, n))) return new_cmap def makeErrorBoxes(ax,xdata,ydata,xerror,yerror,fc='None',ec='k',alpha=1.0,lw=0.5): ''' Description: Generate uncertainty/error "boxes" which are overlaid on points Dependencies: - import numpy as np - import matplotlib.pyplot as plt - from matplotlib.collections import PatchCollection - from matplotlib.patches import Rectangle Inputs: (required) - ax = axis handles onto which error boxes will be drawn - xdata = a list/array of x data - ydata = a list/array of y data - xerror = a list/array of 2 lists/arrays of x absolute uncertainties as [x_lower_errors, x_upper_errors] - yerror = a list/array of 2 lists/arrays of y absolute uncertainties as [y_lower_errors, y_upper_errors] Inputs: (optional) - fc = face color of boxes (D=None) - ec = edge color of boxes (D='k', black) - alpha = opacity of box filling (D=1.0) - lw = line width of box edge (D=0.5) Notes: For best results, repeat this function twice, first rendering the edges and then a second time for the filling as shown below: makeErrorBoxes(xdata,ydata,xerrbox,yerrbox,fc='None',ec=nx_color,alpha=1.0,lw=0.5) makeErrorBoxes(xdata,ydata,xerrbox,yerrbox,fc=nx_color,ec='None',alpha=0.1,lw=0.5) ''' xdata,ydata,xerror,yerror = np.array(xdata),np.array(ydata),np.array(xerror),np.array(yerror) # Create list for all the error patches errorboxes = [] # Loop over data points; create box from errors at each point for xc,yc,xe,ye in zip(xdata,ydata,xerror.T,yerror.T): rect = Rectangle((xc-xe[0],yc-ye[0]),xe.sum(),ye.sum()) errorboxes.append(rect) # Create patch collection with specified colour/alpha pc = PatchCollection(errorboxes,facecolor=fc,alpha=alpha,edgecolor=ec,linewidth=lw) # Add collection to axes ax.add_collection(pc) def fancy_plot( # Required data xdata_lists,ydata_lists, # Dictionaries dictionaries=None, # Optional data data_labels=[], xerr_lists=[[]], yerr_lists=[[]], # Standard basic settings (optional) figi=1, title_str='title', x_label_str='x-axis', y_label_str='y-axis', x_limits=[], y_limits=[], x_scale='log', y_scale='log', color='#FDFEFC', alpha=1.0, linestyle='', linewidth=1, marker='.', markersize=5, markerfacecolor=None, markeredgecolor=None, markeredgewidth=None, errorstyle='bar-band', error_band_opacity=0.15, elinewidth=None, capsize=None, fig_width_inch=9.5, fig_height_inch=6.5, title_fs=16, axis_fs=14, f_family='sans-serif',f_style='normal',f_variant='normal',f_weight='normal', # Advanced settings (optional) # Legend settings legend_position='outside right', legend_anchor=None, legend_ncol=1, legend_alpha=None, legend_columnspacing=None, # Errorbar settings errorbox_xdata_l=[[]], errorbox_xdata_r=[[]], errorbox_ydata_l=[[]], errorbox_ydata_u=[[]], errorbox_fc='k', errorbox_fa=0.1, errorbox_ec='k', errorbox_ea=1.0, errorbox_ew=0.5, # Subplot settings fig=None, ax=None, spnrows=1, spncols=1, spindex=1, man_sp_placement = False, spx0=0.1, spy0=0.1, sph0=0.4, spw0=0.4 ): ''' Description: Function which makes very customizable and beautiful plots. It is intended to be used when plotting multiple datasets at once with a legend but can also handle individual datasets Dependencies: - import numpy as np - import matplotlib.pyplot as plt Inputs: (Required) - xdata_lists = a list containing lists/arrays of x data (or single list of xdata applied to all ydata in y_data_lists) - ydata_lists = a list containing lists/arrays of y data (or single list of ydata) OR - dictionaries (see below) Dictionaries: - dictionaries = a list containing dictionary objects for each dataset to be plotted (or single dictionary object). This provides an alternate way of providing this function with data to be plotted. If wanting to use exclusively dictionaries, set xdata_lists=None and ydata_lists=None; otherwise, the two modes may be used together. The dictionaries are converted to the "standard" list of lists/strings/etc format native to this function. Below are listed the input keywords for these dictionaries; where not the same as the normal variables for this function, the equivalent name is provided in parentheses. - Required: 'xdata' (xdata_lists), 'ydata' (ydata_lists) - Optional (basic): 'data_label' (data_labels), 'xerr' (xerr_lists), 'yerr' (yerr_lists), 'color', 'alpha', 'linestyle', 'linewidth', 'marker', 'markersize', 'markerfacecolor', 'markeredgecolor', 'markeredgewidth', 'errorstyle', 'error_band_opacity', 'elinewidth', 'capsize' - Optional (advanced): 'errorbox_xdata_l', 'errorbox_xdata_r', 'errorbox_ydata_l', 'errorbox_ydata_u', 'errorbox_fc', 'errorbox_fa', 'errorbox_ec', 'errorbox_ea', 'errorbox_ew' For any entry omitted from a dictionary, the value provided to the function is checked first; otherwise the default value is assumed. For example, for entries missing the 'color' keyword, the value provided to the color variable is used. If it has not been changed from its default value, then the default behavior is used. Inputs: (Optional, basic) - data_labels = a list of strings to be used as data labels in the legend (D=[], no legend generated) - xerr_lists = a list containing lists/arrays of x data absolute uncertainties (or single list of xdata errors applied to all ydata in y_data_lists) (D=[[]], No error) - yerr_lists = a list containing lists/arrays of y data absolute uncertainties (or single list of ydata errors) (D=[[]], No error) - figi = figure index (D=1) - title_str = string to be used as the title of the plot (D='title') - x_label_str = string to be used as x-axis title (D='x-axis') - y_label_str = string to be used as y-axis title (D='y-axis') - x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=auto-calculated based on x_data_lists) - y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=auto-calculated based on y_data_lists) - x_scale = x-axis scale, either "linear", "log", "symlog", or "logit" - y_scale = y-axis scale, either "linear", "log", "symlog", or "logit" - color = list of color strings to be used of same length as y_data_lists (or individual color string) (D=Matplotlib default color cycle) - alpha = list of (or individual) alpha values (D=1.0) - linestyle = list of (or individual) strings denoting linestyle: '', '-', '--', '-.', or ':' (D='') - linewidth = list of (or individual) int/float of the width of line (D=1) - marker = list of (or individual) marker styles (D='.') For all options, see: https://matplotlib.org/3.1.0/api/markers_api.html - markersize = list of (or individual) int/float of marker size (D=5) - markerfacecolor = list of (or individual) marker face colors (D=None, use value of 'color') - markeredgecolor = list of (or individual) marker edge colors (D=None, use value of 'color') - markeredgewidth = list of (or individual) int/float of marker edge widths (D=None) - errorstyle = list of (or individual) strings specifying how y-error is represented (D='bar-band', ['bar-band','bar','band']) - error_band_opacity = list of (or individual) int/float of error band opacities (D=0.15) - elinewidth = list of (or individual) int/float line width of error bar lines (D=None, use current linewidth) - capsize = list of (or individual) int/float of length of the error bar caps in points (D=None) - fig_width_inch = figure width in inches (D=9.5) - fig_height_inch = figure height in inches (D=6.5) - title_fs = title font size (D=16) - axis_fs = axis label font size (D=14) - f_family = string specifying font family (D='sans-serif'); options include: ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] - f_style = string specifying font style (D='normal'); options include: ['normal', 'italic', 'oblique'] - f_variant = string specifying font variant (D='normal'); options include: ['normal', 'small-caps'] - f_weight = string specifying font weight (D='normal'); options include: ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] Inputs: (Optional, advanced) Legend settings - legend_position = one of the default matplotlib legend position strings ('best','upper right','lower center','lower left',etc.) to place the legend inside the plot or 'outside right' or 'outside bottom' to place the legend outside of the plot area (D='outside right', if legend is to be used) - legend_anchor = legend anchor position (x=left-right position, y=bottom-top position) only used when legend position is set to one of the "outside" options (D=None which becomes (1.0,0.75) if position is 'outside right' or (0.5,-0.17) if position is 'outside bottom') Note that only one coordinate usually should be adjusted. If using an 'outside right' legend, only the y-coordinate needs to be manipulated to scoot the legend up/down. Likewise, for 'outside bottom' legends only the x-coordinate needs adjusting for tuning left/right position - legend_ncol = number of columns in legend (D=1 in all cases except for legend_position='outside bottom' where D=len(ydata_lists)) - legend_alpha = alpha of legend background (D=None, auto determined by matplotlib) - legend_columnspacing = column spacing of legend (D=None, auto determined by matplotlib) Error boxes (can be used in addition to or in lieu of normal error bars) - errorbox_xdata_l = a list containing lists/arrays of errorbox left widths from center (x-data lower error) - errorbox_xdata_r = a list containing lists/arrays of errorbox right widths from center (x-data upper error) - errorbox_ydata_l = a list containing lists/arrays of errorbox lower heights from center (y-data lower error) - errorbox_ydata_u = a list containing lists/arrays of errorbox upper heights from center (y-data upper error) *Error boxes will only be drawn if at least one x list and one y list of the four above arrays is specified; unspecified lists will default to zero error.* - errorbox_fc = list of (or individual) error box face color (D='k', black) - errorbox_fa = list of (or individual) error box face alpha (D=0.1) - errorbox_ec = list of (or individual) error box edge color (D='k', black) - errorbox_ea = list of (or individual) error box edge alpha (D=1.0) - errorbox_ew = list of (or individual) error box edge width (D=0.5) Subplots - fig = figure handles from existing figure to draw on (D=None, fig=None should always be used for initial subplot unless a figure canvas has already been generated) - ax = axis handles from an existing figure to draw on (D=None, ax=None should always be used for initial subplot) - spnrows = number of rows in final subplot (D=1) - spncols = number of columns in final subplot (D=1) - spindex = index of current subplot (between 1 and spnrows*spncols) (D=1) - man_sp_placement = logical variable controlling manual sizing/placement of subplots using below variables (D=False, use automatic sizing) - spx0 = distance from canvas left edge where this plotting area should begin (D=0.1), generally a number around 0~1 - spy0 = distance from canvas bottom edge where this plotting area should begin (D=0.1), generally a number around 0~1 - spw0 = width of this plotting area on the canvas (D=0.4), generally a number around 0~1 - sph0 = height of this plotting area on the canvas (D=0.4), generally a number around 0~1 Outputs: - fig = pyplot figure - ax = pyplot figure plot/subplot axes handles ''' include_legend = True # used to toggle legend on/off single_dataset = False # Assume multiple datasets entered, but this can be tested to see if it is the case or not. # At the very start, check for dictionaries if dictionaries != None: # determine if single entry or list of entries if isinstance(dictionaries, dict): dictionaries = [dictionaries] num_dict = len(dictionaries) dxdata_lists = [] dydata_lists = [] ddata_labels = [] dxerr_lists = [] dyerr_lists = [] dcolor, dalpha, dlinestyle, dlinewidth, dmarker, dmarkersize, dmarkerfacecolor, dmarkeredgecolor, dmarkeredgewidth = [], [], [], [], [], [], [], [], [] derrorstyle, derror_band_opacity, delinewidth, dcapsize = [], [], [], [] derrorbox_xdata_l, derrorbox_xdata_r, derrorbox_ydata_l, derrorbox_ydata_u = [], [], [], [] derrorbox_fc, derrorbox_fa, derrorbox_ec, derrorbox_ea, derrorbox_ew = [], [], [], [], [] keylist = ['xdata','xdata_lists','ydata','ydata_lists','data_label','data_labels','xerr','xerr_lists','yerr','yerr_lists', 'color','alpha', 'linestyle', 'linewidth', 'marker', 'markersize', 'markerfacecolor', 'markeredgecolor','markeredgewidth', 'errorstyle', 'error_band_opacity', 'elinewidth, capsize', 'errorbox_xdata_l', 'errorbox_xdata_r', 'errorbox_ydata_l', 'errorbox_ydata_u', 'errorbox_fc', 'errorbox_fa', 'errorbox_ec', 'errorbox_ea', 'errorbox_ew'] settings_realvars=[color , alpha, linestyle, linewidth, marker, markersize, markerfacecolor, markeredgecolor, markeredgewidth, errorstyle, error_band_opacity, elinewidth, capsize, errorbox_xdata_l, errorbox_xdata_r, errorbox_ydata_l, errorbox_ydata_u, errorbox_fc, errorbox_fa, errorbox_ec, errorbox_ea, errorbox_ew] settings_keys = ['color' ,'alpha','linestyle','linewidth','marker','markersize','markerfacecolor','markeredgecolor','markeredgewidth','errorstyle','error_band_opacity','elinewidth','capsize','errorbox_xdata_l','errorbox_xdata_r','errorbox_ydata_l','errorbox_ydata_u','errorbox_fc','errorbox_fa','errorbox_ec','errorbox_ea','errorbox_ew'] settings_vars = [dcolor , dalpha, dlinestyle, dlinewidth, dmarker, dmarkersize, dmarkerfacecolor, dmarkeredgecolor, dmarkeredgewidth, derrorstyle, derror_band_opacity, delinewidth, dcapsize, derrorbox_xdata_l, derrorbox_xdata_r, derrorbox_ydata_l, derrorbox_ydata_u, derrorbox_fc, derrorbox_fa, derrorbox_ec, derrorbox_ea, derrorbox_ew] settings_defalts=['#FDFEFC', 1.0 , '' , 1 , '.' , 5 , None , None , None , 'bar-band' , 0.15 , None , None , [] , [] , [] , [] , 'k' , 0.1 , 'k' , 1.0 , 0.5 ] for i in range(num_dict): d = dictionaries[i] if not isinstance(d, dict): print('Index {} of dictionaries list is not a dictionary! Quitting...'.format(i)) return None # Check for any unrecognizable keys dkeys = list(d.keys()) for dkey in dkeys: if dkey not in keylist: print('Encountered unknown keyword {} in dictionary entry at index {}. Ignoring it...'.format(dkey,i)) # Check for each key that will be used if 'xdata' in d: dxdata_lists.append(d['xdata']) elif 'xdata_lists' in d: dxdata_lists.append(d['xdata_lists']) else: print('Dictionary at index {} is missing xdata. Quitting...'.format(i)) return None if 'ydata' in d: dydata_lists.append(d['ydata']) elif 'ydata_lists' in d: dydata_lists.append(d['ydata_lists']) else: print('Dictionary at index {} is missing ydata. Quitting...'.format(i)) return None if 'data_label' in d: ddata_labels.append(d['data_label']) elif 'data_labels' in d: ddata_labels.append(d['data_labels']) else: ddata_labels.append(None) if 'xerr' in d: dxerr_lists.append(d['xerr']) elif 'xerr_lists' in d: dxerr_lists.append(d['xerr_lists']) else: dxerr_lists.append([]) if 'yerr' in d: dyerr_lists.append(d['yerr']) elif 'yerr_lists' in d: dyerr_lists.append(d['yerr_lists']) else: dyerr_lists.append([]) for ski in range(len(settings_keys)): if settings_keys[ski] in d: settings_vars[ski].append(d[settings_keys[ski]]) elif not isinstance(settings_realvars[ski],(list,np.ndarray)): # if main entry is not a list, use it instead of default value settings_vars[ski].append(settings_realvars[ski]) else: settings_vars[ski].append(settings_defalts[ski]) # Now combine with data entered normally, if applicable if xdata_lists != None and ydata_lists != None: # combine if not isinstance(xdata_lists[0],(list,np.ndarray)): xdata_lists = [xdata_lists] xdata_lists = xdata_lists + dxdata_lists if not isinstance(ydata_lists[0],(list,np.ndarray)): ydata_lists = [ydata_lists] num_normal_datasets = len(ydata_lists) num_dict_datasets = len(dydata_lists) ydata_lists = ydata_lists + dydata_lists if data_labels==[]: if not all(x==None for x in ddata_labels): data_labels = num_normal_datasets*[None] + ddata_labels else: data_labels = data_labels + ddata_labels if xerr_lists == [[]]: if not all(x==[] for x in dxerr_lists): xerr_lists = num_normal_datasets*[[]] + dxerr_lists else: xerr_lists = xerr_lists + dxerr_lists if yerr_lists == [[]]: if not all(x==[] for x in dyerr_lists): yerr_lists = num_normal_datasets*[[]] + dyerr_lists else: yerr_lists = yerr_lists + dyerr_lists for ski in range(len(settings_keys)): if settings_keys[ski] in ['errorbox_xdata_l','errorbox_xdata_r','errorbox_ydata_l','errorbox_ydata_u']: # the special exceptions which can be lists if settings_realvars[ski] == [[]]: if not all(x==[] for x in settings_vars[ski]): settings_realvars[ski] = num_normal_datasets*[[]] + settings_vars[ski] else: settings_realvars[ski] = settings_realvars[ski] + settings_vars[ski] else: # for each possible setting option which could be a single value or list if not isinstance(settings_realvars[ski],(list,np.ndarray)): # if main entry isn't a list if not all(x==settings_realvars[ski] for x in settings_vars[ski]): # if not all dict entries are same as main entry settings_realvars[ski] = num_normal_datasets*[settings_realvars[ski]] + settings_vars[ski] else: # just combine the two lists #print(settings_vars[ski]) settings_realvars[ski] = settings_realvars[ski] + settings_vars[ski] color, alpha, linestyle, linewidth, marker, markersize, markerfacecolor, markeredgecolor, markeredgewidth, errorstyle, error_band_opacity, elinewidth, capsize, errorbox_xdata_l, errorbox_xdata_r, errorbox_ydata_l, errorbox_ydata_u, errorbox_fc, errorbox_fa, errorbox_ec, errorbox_ea, errorbox_ew = settings_realvars else: # the only data present are in dictionary form xdata_lists = dxdata_lists ydata_lists = dydata_lists if all([x == None for x in data_labels]): data_labels = None else: data_labels = ddata_labels xerr_lists = dxerr_lists yerr_lists = dyerr_lists for ski in range(len(settings_keys)): if settings_keys[ski] in ['errorbox_xdata_l','errorbox_xdata_r','errorbox_ydata_l','errorbox_ydata_u']: # the special exceptions which can be lists if all(x==[] for x in settings_vars[ski]): settings_vars[ski] = [[]] # set the error box parameters to appear as expected if empty color, alpha, linestyle, linewidth, marker, markersize, markerfacecolor, markeredgecolor, markeredgewidth, errorstyle, error_band_opacity, elinewidth, capsize, errorbox_xdata_l, errorbox_xdata_r, errorbox_ydata_l, errorbox_ydata_u, errorbox_fc, errorbox_fa, errorbox_ec, errorbox_ea, errorbox_ew = settings_vars # End of dictionary entry handling # Determine if error boxes are to be drawn draw_error_boxes = False if (errorbox_xdata_l!=[[]] or errorbox_xdata_r!=[[]]) and (errorbox_ydata_l!=[[]] or errorbox_ydata_u!=[[]]): draw_error_boxes = True if (not xdata_lists) and (not ydata_lists): print('Warning: Both xdata and ydata lists are empty (figure index = {}, titled "{}")'.format(figi,title_str)) single_dataset = True include_legend = False xdata_lists = [[]] ydata_lists = [] xerr_lists = [[]] yerr_lists = [] elif (not xdata_lists): print('Warning: xdata list is empty (figure index = {}, titled "{}")'.format(figi,title_str)) elif (not ydata_lists): print('Warning: ydata list is empty (figure index = {}, titled "{}")'.format(figi,title_str)) # If using a single dataset (user inputs a single list, not a list of list(s) #if len(np.shape(ydata_lists)) != 1: # not just a simple list if (all(isinstance(el, (int, float)) for el in ydata_lists)): # input ydata is a single dataset, not a list of lists/arrays, convert to a list containing a single list for compatability with remainder of code single_dataset = True ydata_lists = [ydata_lists] yerr_lists = [yerr_lists] include_legend = False if draw_error_boxes: errorbox_xdata_l = [errorbox_xdata_l] errorbox_xdata_r = [errorbox_xdata_r] errorbox_ydata_l = [errorbox_ydata_l] errorbox_ydata_u = [errorbox_ydata_u] if not data_labels: include_legend = False nds = len(ydata_lists) # Allow use of single set of xdata for multiple sets of ydata if (not single_dataset) and (all(isinstance(el, (int, float)) for el in xdata_lists)): # ydata is list of lists, xdata is a single list. Assume same xdata for each set of ydata xdata2 = [] for i in range(nds): xdata2.append(xdata_lists) xdata_lists = xdata2 if (all(isinstance(el, (int, float)) for el in xerr_lists)): # ydata is list of lists, xerr_data is a single list. Assume same xerr_data for each set of ydata xerr2 = [] for i in range(nds): xerr2.append(xerr_lists) xerr_lists = xerr2 if draw_error_boxes: errorbox_xdata_l2 = [] errorbox_xdata_r2 = [] for i in range(nds): errorbox_xdata_l2.append(errorbox_xdata_l) errorbox_xdata_r2.append(errorbox_xdata_r) errorbox_xdata_l = errorbox_xdata_l2 errorbox_xdata_r = errorbox_xdata_r2 fst = title_fs #16 fs = axis_fs #14 y_min = 1.0e10 # later used to set y-axis minimum y_max = 1.0e-14 # later used to set y-axis maximum x_min = 1.0e5 # later used to set x-axis minimum x_max = 1.0e1 # later used to set x-axis maximum plt.rc('font', family=f_family, style=f_style, variant=f_variant, weight=f_weight) if fig==None: fig = plt.figure(figi) #bg_color = '#FFFFFF' #'#E1E4E6' #fig.patch.set_facecolor(bg_color) #fig.patch.set_alpha(1.0) ax = plt.subplot(int(spnrows), int(spncols), int(spindex)) for i in range(nds): xdata = xdata_lists[i] ydata = np.array(ydata_lists[i]) xerr=None yerr=None xerr_present = False yerr_present = False if len(xerr_lists[0])>0: xerr = xerr_lists[i] if np.sum(xerr)==0: xerr = None else: xerr_present = True if len(yerr_lists[0])>0: yerr = yerr_lists[i] if np.sum(yerr)==0: yerr = None else: yerr_present = True if include_legend: label_str = data_labels[i] else: label_str = '' # Get settings which may be constant or vary by dataset (lists) if isinstance(color, (list,np.ndarray)): c = color[i] else: c = color if isinstance(alpha, (list,np.ndarray)): alp = alpha[i] else: alp = alpha if isinstance(linestyle, (list,np.ndarray)): ls = linestyle[i] else: ls = linestyle if isinstance(linewidth, (list,np.ndarray)): lw = linewidth[i] else: lw = linewidth if isinstance(marker, (list,np.ndarray)): mkr = marker[i] else: mkr = marker if isinstance(markersize, (list,np.ndarray)): mks = markersize[i] else: mks = markersize if isinstance(errorstyle, (list,np.ndarray)): ers = errorstyle[i] else: ers = errorstyle if isinstance(error_band_opacity, (list,np.ndarray)): ebo = error_band_opacity[i] else: ebo = error_band_opacity if isinstance(elinewidth, (list,np.ndarray)): elw = elinewidth[i] else: elw = elinewidth if isinstance(capsize, (list,np.ndarray)): ecs = capsize[i] else: ecs = capsize if isinstance(markerfacecolor, (list,np.ndarray)): mfc = markerfacecolor[i] else: mfc = markerfacecolor if isinstance(markeredgecolor, (list,np.ndarray)): mec = markeredgecolor[i] else: mec = markeredgecolor if isinstance(markeredgewidth, (list,np.ndarray)): mew = markeredgewidth[i] else: mew = markeredgewidth # Make actual plot if (not xerr_present and not yerr_present) or (ers=='band' and not xerr_present): if color=='#FDFEFC' or color[0]=='#FDFEFC': # assume user will never actually want/input this specific white color p = ax.plot(xdata,ydata,label=label_str,ls=ls,lw=lw,marker=mkr,ms=mks,mfc=mfc,mec=mec,mew=mew,alpha=alp) else: p = ax.plot(xdata,ydata,label=label_str,c=c,ls=ls,lw=lw,marker=mkr,ms=mks,mfc=mfc,mec=mec,mew=mew,alpha=alp) else: if color=='#FDFEFC' or color[0]=='#FDFEFC': # assume user will never actually want/input this specific white color p = ax.errorbar(xdata,ydata,xerr=xerr,yerr=yerr,label=label_str,ls=ls,lw=lw,marker=mkr,ms=mks,elinewidth=elw,capsize=ecs,mfc=mfc,mec=mec,mew=mew,alpha=alp) else: p = ax.errorbar(xdata,ydata,xerr=xerr,yerr=yerr,label=label_str,c=c,ls=ls,lw=lw,marker=mkr,ms=mks,elinewidth=elw,capsize=ecs,mfc=mfc,mec=mec,mew=mew,alpha=alp) if (ers=='bar-band' or ers=='band') and yerr_present: if color=='#FDFEFC' or color[0]=='#FDFEFC': # assume user will never actually want/input this specific white color c = p[0].get_color() # need to grab whatever color was just used if len(np.shape(yerr))==1: ax.fill_between(xdata, np.array(ydata)-np.array(yerr), np.array(ydata)+np.array(yerr),color=c,alpha=ebo) else: ax.fill_between(xdata, np.array(ydata)-np.array(yerr[0,:]), np.array(ydata)+np.array(yerr[1,:]),color=c,alpha=ebo) if draw_error_boxes: draw_error_box_for_this_dataset = True # Ensure x and y error arrays are correctly sized, accounting for possible datasets without error boxes # determine which, if either, x errors are 'None' if (errorbox_xdata_l!=[[]] and errorbox_xdata_r==[[]]): erb_x_l = errorbox_xdata_l[i] if len(erb_x_l) != len(ydata): draw_error_box_for_this_dataset = False else: erb_x_r = 0*np.array(erb_x_l) elif (errorbox_xdata_l==[[]] and errorbox_xdata_r!=[[]]): erb_x_r = errorbox_xdata_r[i] if len(erb_x_r) != len(ydata): draw_error_box_for_this_dataset = False else: erb_x_l = 0*np.array(erb_x_r) else: # both datasets possibly present erb_x_l = errorbox_xdata_l[i] erb_x_r = errorbox_xdata_r[i] if len(erb_x_l) != len(ydata) and len(erb_x_r) != len(ydata): draw_error_box_for_this_dataset = False elif len(erb_x_l) != len(ydata) or len(erb_x_r) != len(ydata): if len(erb_x_l) != len(ydata): erb_x_l = 0*np.array(erb_x_r) elif len(erb_x_r) != len(ydata): erb_x_r = 0*np.array(erb_x_l) # determine which, if either, y errors are 'None' if (errorbox_ydata_l!=[[]] and errorbox_ydata_u==[[]]): erb_y_l = errorbox_ydata_l[i] if len(erb_y_l) != len(ydata): draw_error_box_for_this_dataset = False else: erb_y_u = 0*np.array(erb_y_l) elif (errorbox_ydata_l==[[]] and errorbox_ydata_u!=[[]]): erb_y_u = errorbox_ydata_u[i] if len(erb_y_u) != len(ydata): draw_error_box_for_this_dataset = False else: erb_y_l = 0*np.array(erb_y_u) else: # both datasets possibly present erb_y_l = errorbox_ydata_l[i] erb_y_u = errorbox_ydata_u[i] if len(erb_y_l) != len(ydata) and len(erb_y_u) != len(ydata): draw_error_box_for_this_dataset = False elif len(erb_y_l) != len(ydata) or len(erb_y_u) != len(ydata): if len(erb_y_l) != len(ydata): erb_y_l = 0*np.array(erb_y_u) elif len(erb_y_u) != len(ydata): erb_y_u = 0*np.array(erb_y_l) if draw_error_box_for_this_dataset: xerrbox = [erb_x_l,erb_x_r] yerrbox = [erb_y_l,erb_y_u] # Get settings which may be constant or vary by dataset (lists) if isinstance(errorbox_fc, (list,np.ndarray)): efc = errorbox_fc[i] else: efc = errorbox_fc if isinstance(errorbox_ec, (list,np.ndarray)): eec = errorbox_ec[i] else: eec = errorbox_ec if isinstance(errorbox_ea, (list,np.ndarray)): eea = errorbox_ea[i] else: eea = errorbox_ea if isinstance(errorbox_fa, (list,np.ndarray)): efa = errorbox_fa[i] else: efa = errorbox_fa if isinstance(errorbox_ew, (list,np.ndarray)): eew = errorbox_ew[i] else: eew = errorbox_ew makeErrorBoxes(ax,xdata,ydata,xerrbox,yerrbox,fc='None',ec=eec ,alpha=eea,lw=eew) # outline makeErrorBoxes(ax,xdata,ydata,xerrbox,yerrbox,fc=efc ,ec='None',alpha=efa,lw=eew) # fill face if len(ydata) != 0: if all([yi==None for yi in ydata]): print("\t\tfancy_plot warning: Encountered set of only 'None' at index {}".format(i)) elif len(ydata[np.nonzero(ydata)]) != 0: if min(ydata[np.nonzero(ydata)])<y_min: y_min = min(ydata[np.nonzero(ydata)]) #if min(ydata)<y_min: y_min = min(ydata) if max(ydata[ydata!=None])>y_max: y_max = max(ydata[ydata!=None]) if min(xdata)<x_min: x_min = min(xdata) if max(xdata)>x_max: x_max = max(xdata) if title_str.strip() != '': window_title = slugify(title_str) # "comparison_fig" else: window_title = 'Figure ' + str(figi) #window_title = window_title.replace('b','',1) # remove leading 'b' character from slugify process fig.canvas.manager.set_window_title(window_title) # hangle figure/legend positioning/sizing # First, figure size default_fig_x_in = fig_width_inch default_fig_y_in = fig_height_inch fig_x_in = default_fig_x_in fig_y_in = default_fig_y_in fig.set_size_inches(fig_x_in,fig_y_in) mpl_leg_pos_names = ['best','upper right','upper left','lower left','lower right','right','center left','center right','lower center','upper center','center'] custom_leg_pos_names = ['outside right', 'outside bottom'] if include_legend and legend_position in custom_leg_pos_names: if legend_anchor==None: if legend_position=='outside right': legend_anchor = (1.0,0.75) elif legend_position=='outside bottom': legend_anchor = (0.5,-0.17) leg1_anchor = legend_anchor # varied items handles_l1, labels_l1 = ax.get_legend_handles_labels() if legend_position == 'outside right': legend1 = ax.legend(handles_l1, labels_l1,loc='upper left',bbox_to_anchor=leg1_anchor,ncol=legend_ncol,framealpha=legend_alpha,columnspacing=legend_columnspacing) elif legend_position == 'outside bottom': if legend_ncol == 1 and len(data_labels) > 1: legend_ncol = len(data_labels) legend1 = ax.legend(handles_l1, labels_l1,loc='upper center',bbox_to_anchor=leg1_anchor,ncol=legend_ncol,framealpha=legend_alpha,columnspacing=legend_columnspacing) ax.add_artist(legend1) fig.canvas.draw() f1 = legend1.get_frame() l1_w0_px, l1_h0_px = f1.get_width(), f1.get_height() l_w0_in, l_h0_in = l1_w0_px/fig.dpi, l1_h0_px/fig.dpi # width and height of legend, in inches else: l_w0_in, l_h0_in = 0.0, 0.0 if include_legend and legend_position not in custom_leg_pos_names: # use matplotlib default-style legend inside plot area ax.legend(loc=legend_position,ncol=legend_ncol,framealpha=legend_alpha,columnspacing=legend_columnspacing) n_title_lines = 0 if title_str.strip() != '': n_title_lines = 1 + title_str.count('\n') n_xlabel_lines = 0 if x_label_str.strip() != '': n_xlabel_lines = 1 + x_label_str.count('\n') n_ylabel_lines = 0 if y_label_str.strip() != '': n_ylabel_lines = 1 + y_label_str.count('\n') # These values are good, do not change them. (derived while working on SHAEDIT project) x0bar = 0.60 + 0.200*n_ylabel_lines # inches, horizontal space needed for ylabel y0bar = 0.45 + 0.200*n_xlabel_lines # inches, vertical space needed for xticks/numbers, xlabel and any extra lines it has t0bar = 0.10 + 0.300*n_title_lines # inches, vertical space needed for title del_l_in = 0.15 # inches, extra horizontal padding right of legend # adjust legend spacing depending on its position if legend_position=='outside right': l_h0_in = 0.0 elif legend_position=='outside bottom': l_w0_in = 0.0 # Plot window placement and sizing x0 = x0bar/fig_x_in # distance from left edge that plot area begins y0 = y0bar/fig_y_in + (l_h0_in/fig_y_in) # distance from bottom edge that plot area begins h0 = 1 - (y0bar+t0bar)/fig_y_in - (l_h0_in/fig_y_in) # height of plot area, set to be full height minus space needed for title, x-label, and potentially an outside bottom legend w0 = 1 - x0 - (l_w0_in/fig_x_in) - (del_l_in/fig_x_in) # width of plot area, set to be full width minus space needed for y-label and potentially an outside right legend if man_sp_placement: if spx0!=None: x0 = spx0 if spy0!=None: y0 = spy0 if sph0!=None: h0 = sph0 if spw0!=None: w0 = spw0 # Set size and location of the plot on the canvas box = ax.get_position() # all vals in [0,1]: left, bottom, width, height if not man_sp_placement and (spnrows != 1 or spncols != 1): pstr = 'Warning: It is highly encouraged that subplots be positioned manually.\n' pstr += ' This is done by setting man_sp_placement=True and then adjusting\n' pstr += ' the parameters spx0, spy0, sph0, and spw0 for each subplot.\n' pstr += ' The current plot was automatically sized by matplotlib.\n' print(pstr) else: ax.set_position([x0, y0, w0, h0]) plt.title(title_str,fontsize=fst) plt.xlabel(x_label_str,fontsize=fs) plt.ylabel(y_label_str,fontsize=fs) plt.xscale(x_scale) plt.yscale(y_scale) plt.grid(b=True, which='major', linestyle='-', alpha=0.25) plt.grid(b=True, which='minor', linestyle='-', alpha=0.10) # ensure at least minimum number of decades are present on a plot by increasing padding if necessary zoom_mult = 1.0 x_log_buffer = 0.15*zoom_mult y_log_buffer = 0.2*zoom_mult min_x_decs = 2 min_y_decs = 2 x_scale='linear' if not x_limits: if x_scale == 'log': # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if (np.log10(x_max)-np.log10(x_min)+2*x_log_buffer) < min_x_decs: x_log_buffer = 0.5*(min_x_decs - (np.log10(x_max)-np.log10(x_min))) plt.xlim([10**(np.log10(x_min)-x_log_buffer),10**(np.log10(x_max)+x_log_buffer)]) else: plt.xlim(x_limits) if not y_limits: if y_scale == 'log': # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if (np.log10(y_max)-np.log10(y_min)+2*y_log_buffer) < min_y_decs: y_log_buffer = 0.5*(min_y_decs - (np.log10(y_max)-np.log10(y_min))) plt.ylim([10**(np.log10(y_min)-y_log_buffer),10**(np.log10(y_max)+y_log_buffer)]) else: plt.ylim(y_limits) return fig, ax def fancy_3D_plot( # Required data xdata_lists,ydata_lists,zdata_lists, # Optional data plot_styles=None, data_labels=[], # Standard basic settings (optional) figi=1, title_str='', x_label_str='x-axis', y_label_str='y-axis', z_label_str='z-axis', x_limits=[], y_limits=[], z_limits=[], use_mpl_limits=True, x_scale='linear', y_scale='linear', z_scale='linear', OoB_z_handling = 'NaN', fig_width_inch=9.5, fig_height_inch=6.5, title_fs=16, axis_fs=14, f_family='sans-serif',f_style='normal',f_variant='normal',f_weight='normal', fig=None, ax=None, spnrows=1, spncols=1, spindex=1, man_sp_placement = False, spx0=0.1, spy0=0.1, sph0=0.4, spw0=0.4, color='#FDFEFC', cmap='viridis', facecolors=None, depthshade=True, linestyle='-', linewidth=1, marker='.', markersize=5, markerfacecolor=None, markeredgecolor=None, markeredgewidth=None, rstride=1,cstride=1,rcount=50,ccount=50, alpha=None, # Color map options x_meaning='mid',y_meaning='mid', cbar_fs=None,cbar_size=5,cbar_pad=0.1, # Legend settings legend_position='outside bottom', legend_anchor=None, legend_ncol=1, legend_alpha=None, ): ''' Description: Generate a 3D plot containing an arbitrary number of datasets. The z-axis of each of the datasets can either be a 1-D list (describing scatter points or a line) or a 2-D NumPy array (describing a surface); the x and y axes must be 1-D and match the correct dimension of the z-axis dataset. Dependencies: - import numpy as np - import matplotlib.pyplot as plt - from mpl_toolkits.mplot3d import Axes3D - import matplotlib.ticker as mticker - from mpl_toolkits.mplot3d.axis3d import Axis - import matplotlib.projections as proj - from matplotlib.colors import colorConverter Inputs: (Required) - xdata_lists = a list containing lists/arrays of 1-D x data (or single list of xdata applied to all zdata in z_data_lists) - ydata_lists = a list containing lists/arrays of 1-D y data (or single list of ydata applied to all zdata in z_data_lists) - zdata_lists = a list containing lists/arrays of z datasets (or a single list/array), - individual z datasets can be provided in either of two different formats: - 1) 1-D lists (of the same dimension of the corresponding x and y lists) - 2) 2-D NumPy arrays whose whidth and height match the dimensions of the x and y lists. Inputs: (Optional, basic, generic) - plot_styles = list of (or individual) strings denoting the plot style to be used for each dataset. Options include: + 1-D ['line','scatter','trisurface'] (D='line') + 2-D ['surface','wireframe','trisurface','contour','filledcontour'] (D='trisurface') + 2-D_colormaps ['map_pcolormesh','map_filledcontour','map_contour'] - data_labels = a list of strings to be used as data labels in the legend (D=[], no legend generated) (labels do not work for contours) - figi = figure index (D=1) - title_str = string to be used as the title of the plot (D='') - x_label_str = string to be used as x-axis title (D='x-axis') - y_label_str = string to be used as y-axis title (D='y-axis') - z_label_str = string to be used as z-axis title (D='z-axis') - x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=auto-calculated based on x_data_lists) - y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=auto-calculated based on y_data_lists) - z_limits = length 2 list specifying minimum and maximum z-axis bounds [zmin,zmax] (D=auto-calculated based on z_data_lists) - use_mpl_limits = boolean specifying if Matplotlib default (True) or specially calculated (False) log-scale axis limits are used when they aren't specified (D=True) - x_scale = x-axis scale, either "linear" or "log" - y_scale = y-axis scale, either "linear" or "log" - z_scale = z-axis scale, either "linear" or "log" - OoB_z_handling = string denoting how z values outside of z_limits, if provided, will be handled. (D='NaN', other option are 'limits' and 'None') By default, out of bounds (OoB) values are replaced with NaN. If this is set to 'limits', OoB values are set equal to the lower/upper limit provided. If 'None', no values are replaced (this may cause errors with 3-D plots). - fig_width_inch = figure width in inches (D=9.5) - fig_height_inch = figure height in inches (D=6.5) - title_fs = title font size (D=16) - axis_fs = axis label font size (D=14) - f_family = string specifying font family (D='sans-serif'); options include: ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] - f_style = string specifying font style (D='normal'); options include: ['normal', 'italic', 'oblique'] - f_variant = string specifying font variant (D='normal'); options include: ['normal', 'small-caps'] - f_weight = string specifying font weight (D='normal'); options include: ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] Inputs: (Optional, basic, 3D plot type specific) The below options are only applicable to specific plot_styles; this is denoted by the leftmost column. - L P S W T C F (L='line', P='scatter'(points), S='surface', W='wireframe', T='trisurface', C='contour', F='filledcontour'; 'o' indicates options used by each plot type) - o o o o o o o | alpha = list of (or individual) int/float of the alpha/opacity of each point/curve/surface/etc. (D=None) - o o o o o o o | color = list of color strings to be used of same length as z_data_lists (or individual color string) (D=Matplotlib default color cycle) - . . o . o o o | cmap = list of colormaps to be used of same length as z_data_lists, this will overwrite 'color' (or individual colormap) (D=None) - o . . o . o . | linestyle = list of (or individual) strings denoting linestyle: '', '-', '--', '-.', or ':' (D='-') - o . . o . o . | linewidth = list of (or individual) int/float of the width of line (D=1) - o o . . . . . | marker = list of (or individual) marker styles (D='.') For all options, see: https://matplotlib.org/3.1.0/api/markers_api.html - o o . . . . . | markersize = list of (or individual) int/float of marker size (D=5) - o o . . . . . | markerfacecolor = list of (or individual) marker face colors (D=None, use value of 'color') - o o . . . . . | markeredgecolor = list of (or individual) marker edge colors (D=None, use value of 'color') - o o . . . . . | markeredgewidth = list of (or individual) int/float of marker edge widths (D=None) - . o . . . . . | depthshade = list of (or individual) booleans to enable/disable marker shading for appearance of depth (D=True) - . . o o . . . | rstride = [DEPRECATED] list of (or individual) int/float of array row strides (D=1) - . . o o . . . | cstride = [DEPRECATED] list of (or individual) int/float of array column strides (D=1) - . . o o . . . | rcount = list of (or individual) int/float of maximum number of rows used (D=50) - . . o o . . . | ccount = list of (or individual) int/float of maximum number of columns used (D=50) - . . o . . . . | facecolors = list of (or individual) array mapping colors to each facet of the zdata (D=None) overwrites cmap Inputs: (Optional, 2D colormap type specific) - x_meaning = string specifying if x values describe x min, x max, or central x value for each corresponding z (D='mid'); options include: ['min','max','mid'] (think of like bin min/max/mid) - y_meaning = string specifying if y values describe y min, y max, or central y value for each corresponding z (D='mid'); options include: ['min','max','mid'] - cbar_fs = color bar label font size (D=axis_fs (D=14)) - cbar_size = color bar size expressed as an integer/float between 0 and 100 (D=5) (think of as percentage width) - cbar_pad = color bar padding (should be between 0 and 1) (D=0.1) Inputs: (Optional, advanced) Subplots - fig = figure handles from existing figure to draw on (D=None, fig=None should always be used for initial subplot unless a figure canvas has already been generated) - ax = axis handles from an existing figure to draw on (D=None, ax=None should always be used for initial subplot) - spnrows = number of rows in final subplot (D=1) - spncols = number of columns in final subplot (D=1) - spindex = index of current subplot (between 1 and spnrows*spncols) (D=1) - man_sp_placement = logical variable controlling manual sizing/placement of subplots using below variables (D=False, use automatic sizing) - spx0 = distance from canvas left edge where this plotting area should begin (D=0.1), generally a number around 0~1 - spy0 = distance from canvas bottom edge where this plotting area should begin (D=0.1), generally a number around 0~1 - spw0 = width of this plotting area on the canvas (D=0.4), generally a number around 0~1 - sph0 = height of this plotting area on the canvas (D=0.4), generally a number around 0~1 Legend settings - legend_position = one of the default matplotlib legend position strings ('best','upper right','lower center','lower left',etc.) to place the legend inside the plot or 'outside right' or 'outside bottom' to place the legend outside of the plot area (D='outside bottom', if legend is to be used) - legend_anchor = legend anchor position (x=left-right position, y=bottom-top position) only used when legend position is set to one of the "outside" options (D=None which becomes (1.0,0.75) if position is 'outside right' or (0.5,-0.17) if position is 'outside bottom') Note that only one coordinate usually should be adjusted. If using an 'outside right' legend, only the y-coordinate needs to be manipulated to scoot the legend up/down. Likewise, for 'outside bottom' legends only the x-coordinate needs adjusting for tuning left/right position - legend_ncol = number of columns in legend (D=1 in all cases except for legend_position='outside bottom' where D=len(ydata_lists)) - legend_alpha = alpha of legend background (D=None, auto determined by matplotlib) ''' ''' Notes: Leftover string not yet used for 2D colormap specific options: The below options are only applicable to specific plot styles; this is denoted by the leftmost column. P C F (P='map_pcolormesh', C='map_contour', F='map_filledcontour'; 'o' indicates options used by each plot type) ''' # This allows for implementation of hacked in minor gridlines class axis3d_custom(Axis): # https://stackoverflow.com/questions/31684448/how-to-color-a-specific-gridline-tickline-in-3d-matplotlib-scatter-plot-figure def __init__(self, adir, v_intervalx, d_intervalx, axes, *args, **kwargs): Axis.__init__(self, adir, v_intervalx, d_intervalx, axes, *args, **kwargs) self.gridline_colors = [] def set_gridline_color(self, *gridline_info): '''Gridline_info is a tuple containing the value of the gridline to change and the color to change it to. A list of tuples may be used with the * operator.''' self.gridline_colors.extend(gridline_info) def draw(self, renderer): # filter locations here so that no extra grid lines are drawn Axis.draw(self, renderer) which_gridlines = [] if self.gridline_colors: locmin, locmax = self.get_view_interval() if locmin > locmax: locmin, locmax = locmax, locmin # Rudimentary clipping majorLocs = [loc for loc in self.major.locator() if locmin <= loc <= locmax] for i, val in enumerate(majorLocs): for colored_val, color in self.gridline_colors: if val == colored_val: which_gridlines.append((i, color)) colors = self.gridlines.get_colors() for val, color in which_gridlines: colors[val] = colorConverter.to_rgba(color) self.gridlines.set_color(colors) self.gridlines.draw(renderer, project=True) class XAxis(axis3d_custom): def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.xy_dataLim.intervalx class YAxis(axis3d_custom): def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.xy_dataLim.intervaly class ZAxis(axis3d_custom): def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.zz_dataLim.intervalx class Axes3D_custom(Axes3D): """ 3D axes object. """ name = '3d_custom' def _init_axis(self): '''Init 3D axes; overrides creation of regular X/Y axes''' self.w_xaxis = XAxis('x', self.xy_viewLim.intervalx, self.xy_dataLim.intervalx, self) self.xaxis = self.w_xaxis self.w_yaxis = YAxis('y', self.xy_viewLim.intervaly, self.xy_dataLim.intervaly, self) self.yaxis = self.w_yaxis self.w_zaxis = ZAxis('z', self.zz_viewLim.intervalx, self.zz_dataLim.intervalx, self) self.zaxis = self.w_zaxis for ax in self.xaxis, self.yaxis, self.zaxis: ax.init3d() proj.projection_registry.register(Axes3D_custom) use_custom_3d_axis_class = False # custom axes broken in newer version of matplotlib? valid_plot_styles = ['line','scatter','surface','wireframe','trisurface','contour','filledcontour','map_pcolormesh','map_filledcontour','map_contour'] pls_by_dims = [['line','scatter','trisurface'],['surface','wireframe','trisurface','contour','filledcontour','map_pcolormesh','map_filledcontour','map_contour']] pls_maps = ['map_pcolormesh','map_filledcontour','map_contour'] if data_labels: include_legend = True # used to toggle legend on/off else: include_legend = False single_dataset = False # Assume multiple datasets entered, but this can be tested to see if it is the case or not. if (not xdata_lists) and (not ydata_lists): print('Warning: Both xdata and ydata lists are empty (figure index = {}, titled "{}")'.format(figi,title_str)) single_dataset = True include_legend = False xdata_lists = [[]] ydata_lists = [[]] elif (not xdata_lists): print('Warning: xdata list is empty (figure index = {}, titled "{}")'.format(figi,title_str)) elif (not ydata_lists): print('Warning: ydata list is empty (figure index = {}, titled "{}")'.format(figi,title_str)) # First, determine the number of datasets which have been provided # This is solely determined from the z axis entries. # A z list consisting of only floats/ints is interpreted only as being coordinates to corresponding # x and y lists of the same length. # If wanting to generate a plot with one axis whose values are unchanging in each dataset, please make that x or y. # The Z list can either be an individual an item or a list of supported structures. # Acceptable entries for Z are: # - list of values to plot a line or scatter # - 2D array (numpy, not a list of lists) whose shape matches the lengths of corresponding 1D arrays of x and y data if isinstance(zdata_lists, list): if (all(isinstance(el, (int, float)) for el in zdata_lists)): # just a single list of z coordinates provided ndatasets = 1 zdata_lists = [zdata_lists] elif len(zdata_lists)==1: # provided just a single dataset which could either be a list of values or 2D numpy array ndatasets = 1 else: # provided a number of datasets which could be composed of lists of values and/or 2D numpy arrays ndatasets = len(zdata_lists) elif isinstance(zdata_lists, np.ndarray): if len(np.shape(zdata_lists)) == 1: # single 1D array ndatasets = 1 zdata_lists = zdata_lists.tolist() elif len(np.shape(zdata_lists)) == 2: # single 2D array ndatasets = 1 zdata_lists = [zdata_lists] elif len(np.shape(zdata_lists)) == 3: # divide 3D array into multiple 2D slices ndatasets = np.shape(zdata_lists)[2] original_zdata_lists = zdata_lists zdata_lists = [] zdata_lists = [original_zdata_lists[:,:,i] for i in range(np.shape(original_zdata_lists)[2])] else: print('Dimensions of zdata_lists numpy array is incorrect') return 0 else: print('zdata_lists is invalid. Please enter either a list (of lists) or numpy array') return 0 if ndatasets>1: if isinstance(plot_styles, list): for i in plot_styles: if i in pls_maps: print('Only 1 dataset is allowed per call of fancy_3D_plot when a map plot style is selected.') return 0 else: if plot_styles in pls_maps: print('Only 1 dataset is allowed per call of fancy_3D_plot when a map plot style is selected.') return 0 # Determine if 2D color map or 3D plot plot_2D_map = False plot_pcolormesh = False if isinstance(plot_styles, list): if plot_styles[0] in pls_maps: plot_2D_map = True if plot_styles[0]=='map_pcolormesh': plot_pcolormesh = True else: if plot_styles in pls_maps: plot_2D_map = True if plot_styles=='map_pcolormesh': plot_pcolormesh = True zlen = ndatasets # At this point, zdata_lists if just a list containing either 1D lists or 2D numpy arrays # For each z dataset, determine if a list of z coords (1D) or xy map (2D) array nzdims = [] for i in range(len(zdata_lists)): nzdims.append(len(np.shape(np.array(zdata_lists[i])))) # Now determine how the provided x and y data map onto the provided z data # Check if either x or y data lists are lists of floats/ints rather than a list of lists xdata_only_vals = (all(isinstance(el, (int, float)) for el in xdata_lists)) ydata_only_vals = (all(isinstance(el, (int, float)) for el in ydata_lists)) if xdata_only_vals: xdata_lists = [xdata_lists for i in range(ndatasets)] if ydata_only_vals: ydata_lists = [ydata_lists for i in range(ndatasets)] xlen = len(xdata_lists) ylen = len(ydata_lists) # Check that all dimensions fit correctly and are consistent for i in range(ndatasets): xvals = xdata_lists[i] yvals = ydata_lists[i] zvals = zdata_lists[i] if nzdims[i]==1: # 1D list zlength = len(zvals) zwidth = 1 else: # 2D array zlength = np.shape(zvals)[0] zwidth = np.shape(zvals)[1] if zwidth==1: # points / line if not (len(xvals) == zlength and len(yvals) == zlength): print('Dimension mismatch of dataset i={} with x-length={}, y-length={}, and z-length={}, aborting.'.format(str(i),str(len(xvals)),str(len(yvals)),str(zlength))) return 0 else: # surface if not (len(xvals) == zlength and len(yvals) == zwidth): # if not fitting expected dimensions if ((len(yvals) == zlength and len(xvals) == zwidth) or (plot_pcolormesh and (len(yvals) == zlength+1 or len(xvals) == zwidth+1))): # z vals need to be transposed zdata_lists[i] = zdata_lists[i].T print('Warning: Transpozing Z dataset i={} with x-length={}, y-length={}, and original z-shape from {} to {}.'.format(str(i),str(len(xvals)),str(len(yvals)),str(np.shape(zvals)),str(np.shape(zdata_lists[i])))) elif plot_pcolormesh and (len(yvals) == zwidth+1 or len(xvals) == zlength+1): print('Note: For Z dataset i={} with x-length={}, y-length={}, and original z-shape from {} to {} can only be used with map_pcolormesh due to shape.'.format(str(i),str(len(xvals)),str(len(yvals)),str(np.shape(zvals)),str(np.shape(zdata_lists[i])))) else: print('Dimension mismatch of dataset i={} with x-length={}, y-length={}, and z-shape={}, aborting.'.format(str(i),str(len(xvals)),str(len(yvals)),str(zlength))) return 0 fst = title_fs #16 fs = axis_fs #14 z_min = 1.0e10 # later used to set z-axis minimum z_max = 1.0e-14 # later used to set z-axis maximum y_min = 1.0e10 # later used to set y-axis minimum y_max = 1.0e-14 # later used to set y-axis maximum x_min = 1.0e5 # later used to set x-axis minimum x_max = 1.0e1 # later used to set x-axis maximum if z_scale == 'log': z_min = np.log10(z_min) z_max = np.log10(z_max) if y_scale == 'log': y_min = np.log10(y_min) y_max = np.log10(y_max) if x_scale == 'log': x_min = np.log10(x_min) x_max = np.log10(x_max) plt.rc('font', family=f_family, style=f_style, variant=f_variant, weight=f_weight) if fig==None: fig = plt.figure(figi) if plot_2D_map: ax = fig.add_subplot(spnrows, spncols, spindex) elif use_custom_3d_axis_class: ax = fig.add_subplot(spnrows, spncols, spindex, projection='3d_custom') else: ax = fig.add_subplot(spnrows, spncols, spindex, projection='3d') #bg_color = '#FFFFFF' #'#E1E4E6' #fig.patch.set_facecolor(bg_color) #fig.patch.set_alpha(1.0) #ax = plt.subplot(spnrows, spncols, spindex) for i in range(ndatasets): if include_legend: label_str = data_labels[i] else: label_str = '' if isinstance(plot_styles, list): pls = plot_styles[i] elif plot_styles==None: if nzdims[i]==1: pls = 'line' else: pls = 'trisurface' else: pls = plot_styles if pls not in valid_plot_styles: print('Submitted plot style "{}" for index {} dataset is not a valid entry. Valid options include: '.format(pls,str(i)),valid_plot_styles,"Aborting.") return 0 elif pls not in pls_by_dims[int(nzdims[i]-1)]: print('Submitted plot style "{}" for index {} dataset is not a valid entry for a {}-D dataset. Valid options include: '.format(pls,str(i),str(nzdims[i])),pls_by_dims[int(nzdims[i]-1)],'Aborting.') return 0 # Get settings which may be constant or vary by dataset (lists) if isinstance(color, list): c = color[i] else: c = color if c=='#FDFEFC': c= None if isinstance(cmap, list): cmp = cmap[i] else: cmp = cmap if isinstance(cmap,str): cmp = plt.get_cmap(cmp) if isinstance(linestyle, list): ls = linestyle[i] else: ls = linestyle if isinstance(linewidth, list): lw = linewidth[i] else: lw = linewidth if isinstance(marker, list): mkr = marker[i] else: mkr = marker if isinstance(markersize, list): mks = markersize[i] else: mks = markersize if isinstance(markerfacecolor, list): mfc = markerfacecolor[i] else: mfc = markerfacecolor if isinstance(markeredgecolor, list): mec = markeredgecolor[i] else: mec = markeredgecolor if isinstance(markeredgewidth, list): mew = markeredgewidth[i] else: mew = markeredgewidth if isinstance(depthshade, list): depthshade_i = depthshade[i] else: depthshade_i = depthshade if isinstance(rstride, list): rstride_i = rstride[i] else: rstride_i = rstride if isinstance(cstride, list): cstride_i = cstride[i] else: cstride_i = cstride if isinstance(rcount, list): rcount_i = rcount[i] else: rcount_i = rcount if isinstance(ccount, list): ccount_i = ccount[i] else: ccount_i = ccount if isinstance(facecolors, list): facecolors_i = facecolors[i] else: facecolors_i = facecolors if isinstance(alpha, list): alpha_i = alpha[i] else: alpha_i = alpha # Make actual plot xvals = np.array(xdata_lists[i]).astype(float) yvals = np.array(ydata_lists[i]).astype(float) zvals = np.array(zdata_lists[i]).astype(float) # If user provided axis bounds, enforce them now if x_limits: if x_limits[0]: xvals[xvals<x_limits[0]] = np.NaN if x_limits[1]: xvals[xvals>x_limits[1]] = np.NaN if y_limits: if y_limits[0]: yvals[yvals<y_limits[0]] = np.NaN if y_limits[1]: yvals[yvals>y_limits[1]] = np.NaN if z_limits: if OoB_z_handling=='NaN': if z_limits[0]: zvals[zvals<z_limits[0]] = np.NaN if z_limits[1]: zvals[zvals>z_limits[1]] = np.NaN elif OoB_z_handling=='limits': if z_limits[0]: zvals[zvals<z_limits[0]] = z_limits[0] if z_limits[1]: zvals[zvals>z_limits[1]] = z_limits[1] if z_scale == 'log': zvals[(zvals<=0)] = np.NaN zvals = np.log10(zvals) if y_scale == 'log': yvals[yvals<=0] = np.NaN yvals = np.log10(yvals) if x_scale == 'log': xvals[xvals<=0] = np.NaN xvals = np.log10(xvals) if len(yvals) != 0: if len(yvals[np.nonzero(yvals)]) != 0: if min(yvals[np.nonzero(yvals)])<y_min: y_min = min(yvals[np.nonzero(yvals)]) #if min(yvals)<y_min: y_min = min(yvals) if np.nanmax(yvals)>y_max: y_max = np.nanmax(yvals) if np.nanmin(xvals)<x_min: x_min = np.nanmin(xvals) if np.nanmax(xvals)>x_max: x_max = np.nanmax(xvals) if np.nanmin(zvals)<z_min: z_min = np.nanmin(zvals) if np.nanmax(zvals)>z_max: z_max = np.nanmax(zvals) if nzdims[i]==1: # 1D list zlength = len(zvals) zwidth = 1 else: # 2D array zlength = np.shape(zvals)[0] zwidth = np.shape(zvals)[1] # Plotting functions if nzdims[i]==1: # line if pls=='line': ax.plot(xvals,yvals,zvals,label=label_str, color=c,linestyle=ls,linewidth=lw,alpha=alpha_i, marker=mkr,markersize=mks,markerfacecolor=mfc,markeredgecolor=mec,markeredgewidth=mew) # scatter elif pls=='scatter': if not c: c = mfc # if no color defined, check to see if marker face color was defined ax.scatter(xvals,yvals,zvals,label=label_str, color=c,depthshade=depthshade_i,alpha=alpha_i, marker=mkr,s=mks**2,linewidths=mew,edgecolors=mec) # trisurface elif pls=='trisurface': if cmp != None: c = None if facecolors != None: c = None cmap = None ps1 = ax.plot_trisurf(xvals,yvals,zvals,label=label_str, color=c,cmap=cmp,facecolors=facecolors_i,alpha=alpha_i) ps1._facecolors2d=ps1._facecolors3d ps1._edgecolors2d=ps1._edgecolors3d else: print('Encountered incompatability with plot style {} and data dimensionality {} for data index {}. Aborting.'.format(pls,str(nzdims[i]),str(i))) else: xvals_original,yvals_original = xvals,yvals xvals,yvals = np.meshgrid(xvals,yvals) # surface if pls=='surface': if cmp != None: c = None if facecolors != None: c = None cmap = None ps1 = ax.plot_surface(xvals,yvals, zvals.T,label=label_str, color=c,cmap=cmp,facecolors=facecolors_i,alpha=alpha_i, rcount=rcount_i,ccount=ccount_i, antialiased=False, vmin=z_min,vmax=z_max) # this line was once not needed ps1._facecolors2d=ps1._facecolors3d ps1._edgecolors2d=ps1._edgecolors3d # wireframe elif pls=='wireframe': ax.plot_wireframe(xvals,yvals, zvals.T,label=label_str, color=c,linestyle=ls,linewidth=lw,alpha=alpha_i, rcount=rcount_i,ccount=ccount_i) elif pls=='trisurface': xvals = np.reshape(xvals, -1) yvals = np.reshape(yvals, -1) xtri = [] ytri = [] ztri = [] for yi in range(np.shape(zvals)[1]): for xi in range(np.shape(zvals)[0]): ztri.append(zvals[xi,yi]) #xtri.append(xdata_lists[i][xi]) #ytri.append(ydata_lists[i][yi]) if cmp != None: c = None if facecolors != None: c = None cmap = None ps1 = ax.plot_trisurf(xvals,yvals,ztri,label=label_str, color=c,cmap=cmp,facecolors=facecolors_i,alpha=alpha_i) ps1._facecolors2d=ps1._facecolors3d ps1._edgecolors2d=ps1._edgecolors3d # contour elif pls=='contour': if cmp != None: c = None ax.contour(xvals,yvals, zvals.T, colors=c,cmap=cmp,linestyles=ls,linewidths=lw,alpha=alpha_i) # filled contour elif pls=='filledcontour': if cmp != None: c = None ax.contourf(xvals,yvals, zvals.T, colors=c,cmap=cmp,alpha=alpha_i) # map contour elif pls=='map_pcolormesh': if cmp != None: c = None # first, check if x and y dims are 1 larger than z dims expand_x, expand_y = False, False if np.shape(yvals)[0]==np.shape(zvals)[1]: expand_y = True if np.shape(xvals)[1]==np.shape(zvals)[0]: expand_x = True # make x any y bigger by 1 since pcolormesh takes all edges, not just midpoints if expand_x: dx = xvals[:,1:] - xvals[:,:-1] if x_meaning=='min': newx = (xvals[:,-1]+dx[:,-1]).reshape(len(xvals[:,0]),1) xvals = np.hstack(( xvals , newx )) yvals = np.hstack(( yvals, yvals[:,-1].reshape(len(yvals[:,-1]),1))) if x_meaning=='max': newx = (xvals[:,0]-dx[:,0]).reshape(len(xvals[:,0]),1) xvals = np.hstack(( newx , xvals )) yvals = np.hstack(( yvals[:,0].reshape(len(yvals[:,0]),1), yvals )) if x_meaning=='mid': newx = (xvals[:,0]-0.5*dx[:,0]).reshape(len(xvals[:,0]),1) dx =np.hstack((dx, np.tile(dx[:, [-1]], 1))) xvals = xvals + 0.5*dx xvals = np.hstack(( newx , xvals )) yvals = np.hstack(( yvals[:,0].reshape(len(yvals[:,0]),1), yvals )) if expand_y: dy = yvals[1:] - yvals[:-1] if y_meaning=='min': newy = yvals[-1,:]+dy[-1,:] yvals = np.vstack(( yvals , newy )) xvals = np.vstack(( xvals, xvals[-1,:] )) if y_meaning=='max': newy = yvals[0,:]-dy[0,:] yvals = np.vstack(( newy, yvals )) xvals = np.vstack(( xvals[0,:] , xvals )) if y_meaning=='mid': newy = yvals[0,:]-0.5*dy[0,:] dy =np.vstack((dy, np.tile(dy[[-1],:], 1))) yvals = yvals + 0.5*dy yvals = np.vstack(( newy , yvals )) xvals = np.vstack(( xvals[0,:], xvals )) pcm2d = ax.pcolormesh(xvals,yvals, zvals.T, cmap=cmp,linestyles=ls,linewidths=lw,alpha=alpha_i) # map contour (normal or filled) elif pls=='map_filledcontour' or pls=='map_contour': if cmp != None: c = None if x_meaning!='mid': # shift x values to be midpoints dx = xvals[:,1:] - xvals[:,:-1] if x_meaning=='min': dx =np.hstack((dx, np.tile(dx[:, [-1]], 1))) xvals = xvals + 0.5*dx if x_meaning=='max': dx = np.hstack((np.tile(dx[:, [0]], 1), dx)) xvals = xvals - 0.5*dx if y_meaning!='mid': # shift y values to be midpoints dy = yvals[1:] - yvals[:-1] if y_meaning=='min': dy =np.vstack((dy, np.tile(dy[[-1],:], 1))) yvals = yvals + 0.5*dy if y_meaning=='max': dy = np.vstack((np.tile(dy[[0],:], 1), dy)) yvals = yvals - 0.5*dy #print(yvals) if pls=='map_contour': pcm2d = ax.contour(xvals,yvals, zvals.T, cmap=cmp,linestyles=ls,alpha=alpha_i) else: pcm2d = ax.contourf(xvals,yvals, zvals.T, cmap=cmp,linestyles=ls,alpha=alpha_i) else: print('Encountered incompatability with plot style {} and data dimensionality {} for data index {}. Aborting.'.format(pls,str(nzdims[i]),str(i))) if title_str.strip() != '': window_title = slugify(title_str) # "comparison_fig" else: window_title = 'Figure ' + str(figi) #window_title = window_title.replace('b','',1) # remove leading 'b' character from slugify process fig.canvas.manager.set_window_title(window_title) # hangle figure/legend positioning/sizing # First, figure size default_fig_x_in = fig_width_inch default_fig_y_in = fig_height_inch fig_x_in = default_fig_x_in fig_y_in = default_fig_y_in fig.set_size_inches(fig_x_in,fig_y_in) mpl_leg_pos_names = ['best','upper right','upper left','lower left','lower right','right','center left','center right','lower center','upper center','center'] custom_leg_pos_names = ['outside right', 'outside bottom'] if include_legend and legend_position in custom_leg_pos_names: if legend_anchor==None: if legend_position=='outside right': legend_anchor = (1.0,0.75) elif legend_position=='outside bottom': legend_anchor = (0.5,-0.05) leg1_anchor = legend_anchor # varied items handles_l1, labels_l1 = ax.get_legend_handles_labels() if legend_position == 'outside right': legend1 = ax.legend(handles_l1, labels_l1,loc='upper left',bbox_to_anchor=leg1_anchor,ncol=legend_ncol,framealpha=legend_alpha) elif legend_position == 'outside bottom': if legend_ncol == 1 and len(data_labels) > 1: legend_ncol = len(data_labels) legend1 = ax.legend(handles_l1, labels_l1,loc='upper center',bbox_to_anchor=leg1_anchor,ncol=legend_ncol,framealpha=legend_alpha) ax.add_artist(legend1) fig.canvas.draw() f1 = legend1.get_frame() l1_w0_px, l1_h0_px = f1.get_width(), f1.get_height() l_w0_in, l_h0_in = l1_w0_px/fig.dpi, l1_h0_px/fig.dpi # width and height of legend, in inches else: l_w0_in, l_h0_in = 0.0, 0.0 if include_legend and legend_position not in custom_leg_pos_names: # use matplotlib default-style legend inside plot area ax.legend(loc=legend_position,ncol=legend_ncol,framealpha=legend_alpha) n_title_lines = 0 if title_str.strip() != '': n_title_lines = 1 + title_str.count('\n') n_xlabel_lines = 0 if x_label_str.strip() != '': n_xlabel_lines = 1 + x_label_str.count('\n') n_ylabel_lines = 0 if y_label_str.strip() != '': n_ylabel_lines = 1 + y_label_str.count('\n') n_zlabel_lines = 1 if z_label_str.strip() != '': n_zlabel_lines = 1 + z_label_str.count('\n') if plot_2D_map: # These values are good, do not change them. (derived while working on SHAEDIT project) # INCORPORATE WIDTH OF COLORBAR AND ITS LABEL? x0bar = 0.60 + 0.200*n_ylabel_lines # inches, horizontal space needed for ylabel y0bar = 0.45 + 0.200*n_xlabel_lines # inches, vertical space needed for xticks/numbers, xlabel and any extra lines it has t0bar = 0.10 + 0.300*n_title_lines # inches, vertical space needed for title del_l_in = 0.15 # inches, extra horizontal padding right of legend else: # These values are good, do not change them. (derived while working on on this function specifically for 3D plotting) x0bar = 0.00 + 0.200*(n_zlabel_lines-1) # inches, horizontal space needed for ylabel y0bar = 0.45 + 0.200*max(n_xlabel_lines,n_ylabel_lines) # inches, vertical space needed for xticks/numbers, xlabel and any extra lines it has t0bar = 0.10 + 0.300*n_title_lines # inches, vertical space needed for title del_l_in = 0.15 # inches, extra horizontal padding right of legend # adjust legend spacing depending on its position if legend_position=='outside right': l_h0_in = 0.0 elif legend_position=='outside bottom': l_w0_in = 0.0 # Plot window placement and sizing x0 = x0bar/fig_x_in # distance from left edge that plot area begins y0 = y0bar/fig_y_in + (l_h0_in/fig_y_in) # distance from bottom edge that plot area begins h0 = 1 - (y0bar+t0bar)/fig_y_in - (l_h0_in/fig_y_in) # height of plot area, set to be full height minus space needed for title, x-label, and potentially an outside bottom legend w0 = 1 - x0 - (l_w0_in/fig_x_in) - (del_l_in/fig_x_in) # width of plot area, set to be full width minus space needed for y-label and potentially an outside right legend if man_sp_placement: x0 = spx0 y0 = spy0 h0 = sph0 w0 = spw0 # Set size and location of the plot on the canvas box = ax.get_position() # all vals in [0,1]: left, bottom, width, height if not man_sp_placement and (spnrows != 1 or spncols != 1): pstr = 'Warning: It is highly encouraged that subplots be positioned manually.\n' pstr += ' This is done by setting man_sp_placement=True and then adjusting\n' pstr += ' the parameters spx0, spy0, sph0, and spw0 for each subplot.\n' pstr += ' The current plot was automatically sized by matplotlib.\n' print(pstr) else: ax.set_position([x0, y0, w0, h0]) if plot_2D_map: ax.set_title(title_str,fontsize=fst) plt.xlabel(x_label_str,fontsize=fs) plt.ylabel(y_label_str,fontsize=fs) plt.xscale(x_scale) plt.yscale(y_scale) zoom_mult = 1.0 x_log_buffer = 0.15*zoom_mult y_log_buffer = 0.2*zoom_mult min_x_decs = 2 min_y_decs = 2 x_scale='linear' if not x_limits: if x_scale == 'log': # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if (np.log10(x_max)-np.log10(x_min)+2*x_log_buffer) < min_x_decs: x_log_buffer = 0.5*(min_x_decs - (np.log10(x_max)-np.log10(x_min))) plt.xlim([10**(np.log10(x_min)-x_log_buffer),10**(np.log10(x_max)+x_log_buffer)]) else: plt.xlim(x_limits) if not y_limits: if y_scale == 'log': # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if (np.log10(y_max)-np.log10(y_min)+2*y_log_buffer) < min_y_decs: y_log_buffer = 0.5*(min_y_decs - (np.log10(y_max)-np.log10(y_min))) plt.ylim([10**(np.log10(y_min)-y_log_buffer),10**(np.log10(y_max)+y_log_buffer)]) else: plt.ylim(y_limits) if z_limits: if z_scale == 'log': zlogmin = None zlogmax = None if z_limits[0]: zlogmin=np.log10(z_limits[0]) if z_limits[1]: zlogmax=np.log10(z_limits[1]) pcm2d.set_clim(vmin=zlogmin, vmax=zlogmax) else: pcm2d.set_clim(vmin=z_limits[0], vmax=z_limits[1]) def fmt(x, pos): a, b = '{:.2e}'.format(x).split('e') b = int(b) if z_scale=='log': return r'$10^{{{:g}}}$'.format(x) else: if b < -2 or b > 3: return r'${:g} \times 10^{{{}}}$'.format(np.float(a), b) else: return '{:g}'.format(x) divider = make_axes_locatable(ax) cbar_size_str = '{:g}'.format(cbar_size) + '%' cax = divider.append_axes("right", size=cbar_size_str, pad=cbar_pad) cbar = plt.colorbar(pcm2d, cax=cax, format=ticker.FuncFormatter(fmt)) if cbar_fs==None: cbar_fs = fs cbar.set_label(z_label_str,fontsize=cbar_fs) #cbar.solids.set_rasterized(True) cbar.solids.set_edgecolor("face") #cbar.set_alpha(alpha_i) #cbar.draw_all() ax.set_position([x0, y0, w0, h0]) else: # if 3D-plot ax.set_title(title_str,fontsize=fst) plt.xlabel(x_label_str,fontsize=fs) plt.ylabel(y_label_str,fontsize=fs) ax.set_zlabel(z_label_str,fontsize=fs) # Current matplotlib set_scale commands for log scale are borked completely beyond use, manually add log support #ax.set_xscale(x_scale) #ax.set_yscale(y_scale) #ax.set_zscale(z_scale) #plt.grid(b=True, which='major', linestyle='-', alpha=0.25) # doesn't affect 3D axis #plt.grid(b=True, which='minor', linestyle='-', alpha=0.10) # ensure at least minimum number of decades are present on a plot by increasing padding if necessary zoom_mult = 1.0 x_log_buffer = 0.15*zoom_mult y_log_buffer = 0.2*zoom_mult z_log_buffer = 0.2*zoom_mult min_x_decs = 1 min_y_decs = 1 min_z_decs = 1 manually_calculate_axis_bounds = not use_mpl_limits # if False, use default Matplotlib axis bounds; if True, use specially calculated axis bounds if not x_limits: if x_scale == 'log' and manually_calculate_axis_bounds: # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if ((x_max)-(x_min)+2*x_log_buffer) < min_x_decs: x_log_buffer = 0.5*(min_x_decs - ((x_max)-(x_min))) ax.set_xlim([((x_min)-x_log_buffer),((x_max)+x_log_buffer)]) else: if x_scale=='log': xlimsnew = [] for limi in range(2): if x_limits[limi]: xlimsnew.append(np.log10(x_limits[limi])) else: xlimsnew.append(None) ax.set_xlim(xlimsnew) else: ax.set_xlim(x_limits) if not y_limits: if y_scale == 'log' and manually_calculate_axis_bounds: # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if ((y_max)-(y_min)+2*y_log_buffer) < min_y_decs: y_log_buffer = 0.5*(min_y_decs - ((y_max)-(y_min))) ax.set_ylim([((y_min)-y_log_buffer),((y_max)+y_log_buffer)]) else: if y_scale=='log': ylimsnew = [] for limi in range(2): if y_limits[limi]: ylimsnew.append(np.log10(y_limits[limi])) else: ylimsnew.append(None) ax.set_ylim(ylimsnew) else: ax.set_ylim(y_limits) if not z_limits: if z_scale == 'log' and manually_calculate_axis_bounds: # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if ((z_max)-(z_min)+2*z_log_buffer) < min_z_decs: z_log_buffer = 0.5*(min_z_decs - ((z_max)-(z_min))) ax.set_zlim([((z_min)-z_log_buffer),((z_max)+z_log_buffer)]) else: if z_scale=='log': zlimsnew = [] for limi in range(2): if z_limits[limi]: zlimsnew.append(np.log10(z_limits[limi])) else: zlimsnew.append(None) ax.set_zlim(zlimsnew) else: ax.set_zlim(z_limits) act_xmin, act_xmax = ax.get_xlim() act_ymin, act_ymax = ax.get_ylim() act_zmin, act_zmax = ax.get_zlim() def round_up_to_nearest_multiple(val,mult=1): round_val = np.ceil(val/mult)*mult if isinstance(mult,int) or (abs(round_val)%1<0.01): round_val = int(round_val) return round_val def round_down_to_nearest_multiple(val,mult=1): round_val = np.floor(val/mult)*mult if isinstance(mult,int) or (abs(round_val)%1<0.01): round_val = int(round_val) return round_val def get_ints_between_2_vals(vmin,vmax): stepval = 1 #vmini, vmaxi = int(np.ceil(vmin)),int(np.floor(vmax)) if (vmax-vmin) <=1: stepval = 0.25 elif (vmax-vmin) <=2: stepval = 0.5 vmini = round_up_to_nearest_multiple(vmin,stepval) vmaxi = round_down_to_nearest_multiple(vmax,stepval) tick_list = list(np.arange(vmini,vmaxi+stepval,stepval)) return tick_list def get_log_minor_ticks_between_bounds(vmin,vmax): minor_tick_list = [] # get powers of min and max minpower = np.sign(vmin)*divmod(abs(vmin),1)[0] # integer portion of vmin maxpower = np.sign(vmax)*divmod(abs(vmax),1)[0] # integer portion of vmax # determine leading number in base 10 min_lead_digit = divmod(((10**vmin)/(10**minpower)),1)[0] + 1 max_lead_digit = divmod(((10**vmax)/(10**maxpower)),1)[0] cdigit = min_lead_digit cpower = minpower cval = cdigit*(10**cpower) maxval = max_lead_digit*(10**maxpower) while cval < maxval: minor_tick_list.append(np.log10(cval)) cdigit += 1 if cdigit == 10: cdigit = 2 cpower += 1 cval = cdigit*(10**cpower) return minor_tick_list def log_tick_formatter(vals_list): tstr_list = [] for val in vals_list: tstr = r'10$^{{{:g}}}$'.format(val) tstr_list.append(tstr) return tstr_list if z_scale == 'log': zticks = get_ints_between_2_vals(act_zmin, act_zmax) ztick_labs = log_tick_formatter(zticks) ax.set_zticks(zticks) ax.set_zticklabels(ztick_labs) if y_scale == 'log': yticks = get_ints_between_2_vals(act_ymin, act_ymax) ytick_labs = log_tick_formatter(yticks) ax.set_yticks(yticks) ax.set_yticklabels(ytick_labs) if x_scale == 'log': xticks = get_ints_between_2_vals(act_xmin, act_xmax) xtick_labs = log_tick_formatter(xticks) ax.set_xticks(xticks) ax.set_xticklabels(xtick_labs) #ax.set_yticks(yticks+np.log10(np.array([2,3,4,5,8])).tolist()) #yticks = yticks + np.log10(np.array([2,3,4,5,8])).tolist() grid_alpha = 0.25 grid_alpha_minor = 0.05 if x_scale == 'log': ax.xaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha_minor) else: ax.xaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha) if y_scale == 'log': ax.yaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha_minor) else: ax.yaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha) if z_scale == 'log': ax.zaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha_minor) else: ax.zaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha) if use_custom_3d_axis_class: # If log scale, add minor grid lines if x_scale == 'log': xticks_minor = get_log_minor_ticks_between_bounds(act_xmin, act_xmax) xgridlines = [] for i in range(len(xticks)): xgridlines.append((xticks[i],(0,0,0,grid_alpha))) for i in range(len(xticks_minor)): xgridlines.append((xticks_minor[i],(1,1,1,grid_alpha_minor))) ax.set_xticks(xticks+xticks_minor) ax.xaxis.set_gridline_color(*xgridlines) if y_scale == 'log': yticks_minor = get_log_minor_ticks_between_bounds(act_ymin, act_ymax) ygridlines = [] for i in range(len(yticks)): ygridlines.append((yticks[i],(0,0,0,grid_alpha))) for i in range(len(yticks_minor)): ygridlines.append((yticks_minor[i],(0,0,0,grid_alpha_minor))) ax.set_yticks(yticks+yticks_minor) ax.yaxis.set_gridline_color(*ygridlines) if z_scale == 'log': zticks_minor = get_log_minor_ticks_between_bounds(act_zmin, act_zmax) zgridlines = [] for i in range(len(zticks)): zgridlines.append((zticks[i],(0,0,0,grid_alpha))) for i in range(len(zticks_minor)): zgridlines.append((zticks_minor[i],(0,0,0,grid_alpha_minor))) ax.set_zticks(zticks+zticks_minor) ax.zaxis.set_gridline_color(*zgridlines) # For some more info, see https://dawes.wordpress.com/2014/06/27/publication-ready-3d-figures-from-matplotlib/ # Tick positioning [t.set_va('center') for t in ax.get_yticklabels()] [t.set_ha('center') for t in ax.get_yticklabels()] [t.set_va('center') for t in ax.get_xticklabels()] [t.set_ha('center') for t in ax.get_xticklabels()] [t.set_va('center') for t in ax.get_zticklabels()] [t.set_ha('center') for t in ax.get_zticklabels()] '' tick_infactor = 0.0 tick_outfactor = 0.2 # adjusts length of ticks on inside/outside of plot ax.xaxis._axinfo['tick']['inward_factor'] = tick_infactor ax.xaxis._axinfo['tick']['outward_factor'] = tick_outfactor ax.yaxis._axinfo['tick']['inward_factor'] = tick_infactor ax.yaxis._axinfo['tick']['outward_factor'] = tick_outfactor ax.zaxis._axinfo['tick']['inward_factor'] = tick_infactor ax.zaxis._axinfo['tick']['outward_factor'] = tick_outfactor # Background ax.xaxis.pane.set_edgecolor('black') ax.yaxis.pane.set_edgecolor('black') #ax.zaxis.pane.set_edgecolor('black') ax.xaxis.pane.fill = False ax.yaxis.pane.fill = False ax.zaxis.pane.fill = False #ax.view_init(elev=10, azim=135) return fig, ax ''' ************************************************************************************************** ----------------------------------- END OF DEFINED FUNCTIONS ------------------------------------- ************************************************************************************************** ''' debugging_table_generators = False if debugging_table_generators: tab = [ ['1',1.00,r'$\beta^-$',r'\texttt{B-} '], ['2','3.00',r'$IT$',r'\texttt{IT} '], ['3','n/a ',r'$(n,x)$',r'\texttt{nx} '], ['4','2.00',r'$\beta^+/e.c.$',r'\texttt{B+} '], ['5','4.00',r'$\alpha$',r'\texttt{\char32 a} '], ['6','5.00',r'$n$',r'\texttt{\char32 n} '], ['7','6.00',r'$s.f.$',r'\texttt{sf} '], ['8','--',r'$other$',r'\texttt{or} ']] col_heads = ['a','b'] col_head_span = [2,2] row_heads = [['o','t','f','s'],['a','b','c']] row_head_span = [2,[3,3,2]] #print(np.shape(tab)) y = Latex_table_generator(tab,row_headers=row_heads,row_header_spans=row_head_span,column_headers=col_heads,column_header_spans=col_head_span,title='test', hline_row_indices=6,cline_row_cstart_cend_indices_triplets=[[2,1,1],[3,2,2],[4,1,1]]) #x = Excel_table_generator(y) print(y) debugging_fancy_3d_plot = False if debugging_fancy_3d_plot: figi = 0 theta = np.linspace(-4 * np.pi, 4 * np.pi, 100) z1 = np.linspace(-2, 2, 100) r1 = z1**2 + 1 xdata_lists = [ np.linspace(0,2*np.pi,50)+1,np.linspace(0,2*np.pi,50)+1,np.linspace(0,2*np.pi,20)+1,np.linspace(0,2*np.pi,20)+1,np.linspace(0,2*np.pi,20)+1,np.linspace(0,2*np.pi,20)+1,np.linspace(0,2*np.pi,20)+1] ydata_lists = [ np.linspace(2,100,50),np.linspace(2,100,50),100*(np.cos(xdata_lists[2]-1)+1),100*(np.cos(xdata_lists[2]-1)+1),100*(np.cos(xdata_lists[2]-1)+1),100*(np.cos(xdata_lists[2]-1)+1),100*(np.cos(xdata_lists[2]-1)+1)] X, Y = np.meshgrid(xdata_lists[2], ydata_lists[2]) R = np.sqrt(X**2 + Y**2) Z = np.sin(R) multx=1.3 zdata_lists = [np.exp(xdata_lists[0])/7, np.exp(xdata_lists[1])/4, 20*(Z+1.1), 20*(Z+1.1)+50*multx, 20*(Z+1.1)+100*multx, 20*(Z+1.1)+150*multx , 20*(Z+1.1)+200*multx ] pls=['scatter', 'line', 'surface','trisurface','filledcontour','contour','wireframe'] # include only some plots incli = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 ] #incli = [ 2 ] xdata_lists = [xdata_lists[i] for i in incli] ydata_lists = [ydata_lists[i] for i in incli] zdata_lists = [zdata_lists[i] for i in incli] pls = [pls[i] for i in incli] labs = pls xlims = [1e0,1e1] ylims = [1e0,1e3] zlims = [1e0,1e3] fancy_3D_plot( # REQUIRED INPUTS xdata_lists, # a list containing lists/arrays of 1-D x data (or single list of xdata applied to all zdata in z_data_lists) ydata_lists, # a list containing lists/arrays of 1-D y data (or single list of ydata applied to all zdata in z_data_lists) zdata_lists, # a list containing lists/arrays of z datasets (or a single list/array), individual entries are either 1-D lists or 2-D NumPy arrays # OPTIONAL (basic) KEYWORD ARGUMENTS (for all plot styles) plot_styles=pls, # list of (or individual) strings denoting the plot style to be used for each dataset. valid options for 1/2-D z-data = ['line','scatter','trisurface']/['surface','wireframe','trisurface','contour','filledcontour'] data_labels=labs, # a list of strings to be used as data labels in the legend figi=figi, # figi = figure index (D=1) #title_str=title_str, # title_str = string to be used as the title of the plot (D='title') #x_label_str=x_label_str, # x_label_str = string to be used as x-axis title (D='x-axis') #y_label_str=y_label_str, # y_label_str = string to be used as y-axis title (D='y-axis') x_limits=xlims, # x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=[], auto-calculated based on x_data_lists) y_limits=ylims, # y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=[], auto-calculated based on y_data_lists) z_limits=zlims, # z_limits = length 2 list specifying minimum and maximum z-axis bounds [zmin,zmax] (D=[], auto-calculated based on z_data_lists) use_mpl_limits=True, # use_mpl_limits = use Matplotlib axis limits (True) or specially calculated limits (False) for log scale when limits aren't specified title_fs=16, # title_fs = title font size (D=16) axis_fs=14, # axis_fs = axis label font size (D=14) f_family='sans-serif', # f_family = string specifying font family (D='sans-serif'); options include: ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] f_style='normal', # f_style = string specifying font style (D='normal'); options include: ['normal', 'italic', 'oblique'] f_variant='normal', # f_variant = string specifying font variant (D='normal'); options include: ['normal', 'small-caps'] f_weight='normal', # f_weight = string specifying font weight (D='normal'); options include: ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] fig_width_inch=8.0, # fig_width_inch = figure width in inches (D=9.5) fig_height_inch=6.25, # fig_height_inch = figure height in inches (D=6.5) x_scale='log', # x_scale = x-axis scale, either "linear", "log", "symlog", or "logit" (D='linear') y_scale='log', # y_scale = y-axis scale, either "linear", "log", "symlog", or "logit" (D='linear') z_scale='log', # z_scale = z-axis scale, either "linear", "log", "symlog", or "logit" (D='linear') # OPTIONAL (basic) KEYWORD ARGUMENTS (for specific plot styles) #color=plotting_colors_list, # color = list of color strings to be used of same length as y_data_lists (or individual color string) (D='#FDFEFC', results in Matplotlib default color cycle) #linestyle='--', # line style, choose from: '', '-', '--', '-.', or ':' (D='') #linewidth=5, # line width (D=1) marker='o', # marker style (D='.'), choose from those listed at https://matplotlib.org/3.1.0/api/markers_api.html markersize=5, # marker size (D=5) markerfacecolor='g', markeredgecolor='r', markeredgewidth=2, alpha = 0.5, #OoB_z_handling = None, #'limits', # OPTIONAL (advanced) KEYWORD ARGUMENTS fig = None, # figure handles from existing figure to draw on (D=None, fig=None should always be used for initial subplot unless a figure canvas has already been generated) ax = None, # axis handles from an existing figure to draw on (D=None, ax=None should always be used for initial subplot) spnrows=1, # number of rows in final subplot (D=1) spncols=1, # number of columns in final subplot (D=1) spindex=1, # index of current subplot (between 1 and spnrows*spncols) (D=1) man_sp_placement = False, # logical variable controlling manual sizing/placement of subplots using below variables (D=False, use automatic sizing) spx0=0.1, # distance from canvas left edge where this plotting area should begin (D=0.1), generally a number around 0~1 spy0=0.5, # distance from canvas bottom edge where this plotting area should begin (D=0.1), generally a number around 0~1 sph0=0.4, # width of this plotting area on the canvas (D=0.4), generally a number around 0~1 spw0=0.8 # height of this plotting area on the canvas (D=0.4), generally a number around 0~1 ) plt.show() debugging_fancy_plot = False if debugging_fancy_plot: # For the sake of convenience, the fancy_plot function being called with all options listed and described is included here. # This can be easily copy/pasted into other scripts and retooled for the specific application at hand. xdata_lists = [np.linspace(0,2*np.pi,50),np.linspace(0,2*np.pi,40)] ydata_lists = [np.sin(xdata_lists[0]), np.cos(xdata_lists[1]) ] data_labels = ['sine','cosine'] figi = 1 title_str = 'demo of fancy_plot' x_label_str = 'x-axis label' y_label_str = 'y-axis label' yerr_lists = [0.05*np.sin(xdata_lists[0]), 0.2*np.cos(xdata_lists[1]) ] rand_err = 0.5*np.random.rand(40) xerboxl = [[],rand_err*xdata_lists[1]] xerboxr = [[],rand_err*xdata_lists[1]] yerboxl = [[],rand_err*ydata_lists[1]] yerboxu = [[],rand_err*ydata_lists[1]] d1 = {'xdata':np.array([1,2,3,4]),'ydata':np.array([-1,0,0.5,0.25]),'alpha':1.0} d2 = {'xdata':[1,2,3,4],'ydata':[-0.5,-0.75,-0.5,0],'markersize':15} dictionaries = [d1, d2] #xdata_lists, ydata_lists = None, None fig, ax = fancy_plot( # REQUIRED INPUTS xdata_lists, # a list containing lists/arrays of x data (or single list of xdata applied to all ydata in y_data_lists) ydata_lists, # a list containing lists/arrays of y data (or single list of ydata) # DICTIONARIES dictionaries = dictionaries, # OPTIONAL (basic) KEYWORD ARGUMENTS data_labels=[], # a list of strings to be used as data labels in the legend #xerr_lists=xerr_lists, # xerr_lists = a list containing lists/arrays of x data absolute uncertainties (or single list of xdata errors applied to all ydata in y_data_lists) (D=[[]], No error) yerr_lists=yerr_lists, # yerr_lists = a list containing lists/arrays of y data absolute uncertainties (or single list of ydata errors) (D=[[]], No error) figi=figi, # figi = figure index (D=1) title_str=title_str, # title_str = string to be used as the title of the plot (D='title') x_label_str=x_label_str, # x_label_str = string to be used as x-axis title (D='x-axis') y_label_str=y_label_str, # y_label_str = string to be used as y-axis title (D='y-axis') #x_limits=xlims, # x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=[], auto-calculated based on x_data_lists) #y_limits=ylims, # y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=[], auto-calculated based on y_data_lists) title_fs=16, # title_fs = title font size (D=16) axis_fs=14, # axis_fs = axis label font size (D=14) f_family='sans-serif', # f_family = string specifying font family (D='sans-serif'); options include: ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] f_style='normal', # f_style = string specifying font style (D='normal'); options include: ['normal', 'italic', 'oblique'] f_variant='normal', # f_variant = string specifying font variant (D='normal'); options include: ['normal', 'small-caps'] f_weight='normal', # f_weight = string specifying font weight (D='normal'); options include: ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] fig_width_inch=5.0, # fig_width_inch = figure width in inches (D=9.5) fig_height_inch=4.25, # fig_height_inch = figure height in inches (D=6.5) x_scale='linear', # x_scale = x-axis scale, either "linear", "log", "symlog", or "logit" (D='log') y_scale='linear', # y_scale = y-axis scale, either "linear", "log", "symlog", or "logit" (D='log') #color=plotting_colors_list, # color = list of color strings to be used of same length as y_data_lists (or individual color string) (D='#FDFEFC', results in Matplotlib default color cycle) alpha=0.1, # alpha = list of (or individual) alpha values (D=1.0) linestyle='', # line style, choose from: '', '-', '--', '-.', or ':' (D='') linewidth=1, # line width (D=1) marker=['.','o'], # marker style (D='.'), choose from those listed at https://matplotlib.org/3.1.0/api/markers_api.html markersize=5, # marker size (D=5) markerfacecolor=['k','g'], markeredgecolor=None, markeredgewidth=None, errorstyle = 'bar-band', # list of (or individual) strings specifying how y-error is represented (D='bar-band', ['bar-band','bar','band'] error_band_opacity = 0.15, # list of (or individual) int/float of error band opacities (D=0.15) elinewidth=None, # error bar line width (D=None, use linewidth value) capsize=None, # error bar cap width (D=None) # OPTIONAL (advanced) KEYWORD ARGUMENTS legend_position='outside bottom',# legend_position = placement of legend using a default matplotlib string ('best','upper right','lower left',etc.) inside plot area or outside using 'outside right' or 'outside bottom' (D='outside right') legend_anchor = None, # legend_anchor = legend anchor position (D=(1.0,0.75) if outside right, (0.5,-0.17) if outside bottom), only used for 'outside' legend positions legend_ncol = 1, # legend_ncol = number of columns in legend (D=1) (if 'outside bottom' position, ncol=1 causes the function to default to ncol=len(data_labels) instead) legend_alpha = None, # legend_alpha = alpha of legend background #errorbox_xdata_l = xerboxl, # a list containing lists/arrays of errorbox left widths from center (x-data lower error) #errorbox_xdata_r = xerboxr, # a list containing lists/arrays of errorbox right widths from center (x-data upper error) #errorbox_ydata_l = yerboxl, # a list containing lists/arrays of errorbox lower heights from center (y-data lower error) #errorbox_ydata_u = yerboxu, # a list containing lists/arrays of errorbox upper heights from center (y-data upper error) #errorbox_fc = 'k', # errorbox_fc = error box face color (D='k') #errorbox_fa = 0.1, # errorbox_fa = error box face alpha (D=0.1) #errorbox_ec = 'k', # errorbox_ec = error box edge color (D='k', black) #errorbox_ea = 1.0, # errorbox_ea = error box edge alpha (D=0.1) #errorbox_ew = 0.5 # errorbox_ew = error box edge width (D=0.5) fig = None, # figure handles from existing figure to draw on (D=None, fig=None should always be used for initial subplot unless a figure canvas has already been generated) ax = None, # axis handles from an existing figure to draw on (D=None, ax=None should always be used for initial subplot) spnrows=1, # number of rows in final subplot (D=1) spncols=1, # number of columns in final subplot (D=1) spindex=1, # index of current subplot (between 1 and spnrows*spncols) (D=1) man_sp_placement = False, # logical variable controlling manual sizing/placement of subplots using below variables (D=False, use automatic sizing) spx0=0.1, # distance from canvas left edge where this plotting area should begin (D=0.1), generally a number around 0~1 spy0=0.5, # distance from canvas bottom edge where this plotting area should begin (D=0.1), generally a number around 0~1 sph0=0.4, # width of this plotting area on the canvas (D=0.4), generally a number around 0~1 spw0=0.8 # height of this plotting area on the canvas (D=0.4), generally a number around 0~1 ) ''' fig, ax = fancy_plot( # REQUIRED INPUTS xdata_lists, # a list containing lists/arrays of x data (or single list of xdata applied to all ydata in y_data_lists) ydata_lists, # a list containing lists/arrays of y data (or single list of ydata) # OPTIONAL (basic) KEYWORD ARGUMENTS data_labels=data_labels, # a list of strings to be used as data labels in the legend #xerr_lists=xerr_lists, # xerr_lists = a list containing lists/arrays of x data absolute uncertainties (or single list of xdata errors applied to all ydata in y_data_lists) (D=[[]], No error) yerr_lists=yerr_lists, # yerr_lists = a list containing lists/arrays of y data absolute uncertainties (or single list of ydata errors) (D=[[]], No error) figi=figi, # figi = figure index (D=1) title_str=' ', # title_str = string to be used as the title of the plot (D='title') x_label_str='x_label_str', # x_label_str = string to be used as x-axis title (D='x-axis') y_label_str=y_label_str, # y_label_str = string to be used as y-axis title (D='y-axis') #x_limits=xlims, # x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=[], auto-calculated based on x_data_lists) #y_limits=ylims, # y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=[], auto-calculated based on y_data_lists) title_fs=16, # title_fs = title font size (D=16) axis_fs=14, # axis_fs = axis label font size (D=14) fig_width_inch=5.0, # fig_width_inch = figure width in inches (D=9.5) fig_height_inch=4.25, # fig_height_inch = figure height in inches (D=6.5) x_scale='linear', # x_scale = x-axis scale, either "linear", "log", "symlog", or "logit" (D='log') y_scale='linear', # y_scale = y-axis scale, either "linear", "log", "symlog", or "logit" (D='log') #color=plotting_colors_list, # color = list of color strings to be used of same length as y_data_lists (or individual color string) (D='#FDFEFC', results in Matplotlib default color cycle) linestyle='', # line style, choose from: '', '-', '--', '-.', or ':' (D='') linewidth=1, # line width (D=1) marker='.', # marker style (D='.'), choose from those listed at https://matplotlib.org/3.1.0/api/markers_api.html markersize=5, # marker size (D=5) elinewidth=None, # error bar line width (D=None, use linewidth value) capsize=None, # error bar cap width (D=None) # OPTIONAL (advanced) KEYWORD ARGUMENTS legend_position='upper right',# legend_position = placement of legend using a default matplotlib string ('best','upper right','lower left',etc.) inside plot area or outside using 'outside right' or 'outside bottom' (D='outside right') legend_anchor = None, # legend_anchor = legend anchor position (D=(1.0,0.75) if outside right, (0.5,-0.17) if outside bottom), only used for 'outside' legend positions legend_ncol = 1, # legend_ncol = number of columns in legend (D=1) (if 'outside bottom' position, ncol=1 causes the function to default to ncol=len(data_labels) instead) legend_alpha = None, # legend_alpha = alpha of legend background #errorbox_xdata_l = xerboxl, # a list containing lists/arrays of errorbox left widths from center (x-data lower error) #errorbox_xdata_r = xerboxr, # a list containing lists/arrays of errorbox right widths from center (x-data upper error) #errorbox_ydata_l = yerboxl, # a list containing lists/arrays of errorbox lower heights from center (y-data lower error) #errorbox_ydata_u = yerboxu, # a list containing lists/arrays of errorbox upper heights from center (y-data upper error) #errorbox_fc = 'k', # errorbox_fc = error box face color (D='k') #errorbox_fa = 0.1, # errorbox_fa = error box face alpha (D=0.1) #errorbox_ec = 'k', # errorbox_ec = error box edge color (D='k', black) #errorbox_ea = 1.0, # errorbox_ea = error box edge alpha (D=0.1) #errorbox_ew = 0.5 # errorbox_ew = error box edge width (D=0.5) fig = fig, ax = ax, spnrows=2, spncols=1, spindex=2, man_sp_placement = False, spx0=0.1, spy0=0.1, sph0=0.4, spw0=0.8 ) ''' ''' - errorbox_xdata_l = a list containing lists/arrays of errorbox left widths from center (x-data lower error) - errorbox_xdata_r = a list containing lists/arrays of errorbox right widths from center (x-data upper error) - errorbox_ydata_l = a list containing lists/arrays of errorbox lower heights from center (y-data lower error) - errorbox_ydata_u = a list containing lists/arrays of errorbox upper heights from center (y-data upper error) *** Error boxes will only be drawn if at least one x list and one y list of the four above arrays is specified; unspecified lists will default to zero error - errorbox_fc = error box face color (D='k') - errorbox_fa = error box face alpha (D=0.1) - errorbox_ec = error box edge color (D='k', black) - errorbox_ew = error box edge width (D=0.5) - errorbox_ea = error box edge alpha (D=0.1) ''' if debugging_fancy_plot or debugging_fancy_3d_plot: plt.show() ## Functions  def Element_Sym_to_Z(sym)  ## Description Returns atomic number Z for a provided elemental symbol ## Dependencies find() (function within the "Hunter's tools" package) ## Inputs • sym = string of elemental symbol for element of atomic number Z ## Outputs • Z = atomic number Expand source code def Element_Sym_to_Z(sym): ''' Description: Returns atomic number Z for a provided elemental symbol Dependencies: find (function within the "Hunter's tools" package) Inputs: - sym = string of elemental symbol for element of atomic number Z Outputs: - Z = atomic number ''' elms = ["n ",\ "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\ "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\ "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\ "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\ "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\ "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\ "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\ "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\ "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\ "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\ "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\ "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"] if len(sym.strip())>2: print('Please provide a valid elemental symbol (1 or 2 characters), {} is too long'.format(sym)) return -1 # handle exception for neutron first if sym.strip()=='XX': return 0 # make sure string is formatted to match entries in elms list sym2 = sym.strip() if len(sym2)==1: sym2 += ' ' sym2 = sym2[0].upper() + sym2[1].lower() Z = find(sym2,elms) if Z==None: print('Z could not be found for element "{}"; please make sure entry is correct.'.format(sym)) return -1 return Z  def Element_Z_to_Sym(Z)  ## Description Returns elemental symbol for a provided atomic number Z ## Inputs • Z = atomic number ## Outputs • sym = string of elemental symbol for element of atomic number Z Expand source code def Element_Z_to_Sym(Z): ''' Description: Returns elemental symbol for a provided atomic number Z Inputs: - Z = atomic number Outputs: - sym = string of elemental symbol for element of atomic number Z ''' elms = ["n ",\ "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\ "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\ "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\ "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\ "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\ "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\ "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\ "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\ "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\ "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\ "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\ "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"] i = int(Z) if i < 0 or i > len(elms): print('Z={} is not valid, please select a number from 0 to 118 (inclusive).'.format(str(Z))) return None return elms[i].strip()  def Element_ZorSym_to_mass(Z)  ## 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 Expand source code def Element_ZorSym_to_mass(Z): ''' 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]  def Element_ZorSym_to_name(Z)  ## Description Returns an element's name provided its atomic number Z or elemental symbol ## Inputs • Z = string of elemental symbol or atomic number Z ## Outputs • name = element name Expand source code def Element_ZorSym_to_name(Z): ''' Description: Returns an element's name provided its atomic number Z or elemental symbol Inputs: - Z = string of elemental symbol or atomic number Z Outputs: - name = element name ''' element_names = ['neutron','Hydrogen','Helium','Lithium','Beryllium','Boron','Carbon','Nitrogen','Oxygen','Fluorine', 'Neon','Sodium','Magnesium','Aluminium','Silicon','Phosphorus','Sulfur','Chlorine','Argon', 'Potassium','Calcium','Scandium','Titanium','Vanadium','Chromium','Manganese','Iron','Cobalt', 'Nickel','Copper','Zinc','Gallium','Germanium','Arsenic','Selenium','Bromine','Krypton', 'Rubidium','Strontium','Yttrium','Zirconium','Niobium','Molybdenum','Technetium','Ruthenium', 'Rhodium','Palladium','Silver','Cadmium','Indium','Tin','Antimony','Tellurium','Iodine','Xenon', 'Caesium','Barium','Lanthanum','Cerium','Praseodymium','Neodymium','Promethium','Samarium', 'Europium','Gadolinium','Terbium','Dysprosium','Holmium','Erbium','Thulium','Ytterbium', 'Lutetium','Hafnium','Tantalum','Tungsten','Rhenium','Osmium','Iridium','Platinum','Gold', 'Mercury','Thallium','Lead','Bismuth','Polonium','Astatine','Radon','Francium','Radium', 'Actinium','Thorium','Protactinium','Uranium','Neptunium','Plutonium','Americium','Curium', 'Berkelium','Californium','Einsteinium','Fermium','Mendelevium','Nobelium','Lawrencium', 'Rutherfordium','Dubnium','Seaborgium','Bohrium','Hassium','Meitnerium','Darmstadtium', 'Roentgenium','Copernicium','Nihonium','Flerovium','Moscovium','Livermorium','Tennessine','Oganesson'] try: zi = int(Z) except: zi = Element_Sym_to_Z(Z) return element_names[zi]  def Excel_table_generator(core_table, title=None, row_headers=None, row_header_spans=None, column_headers=None, column_header_spans=None, float_formatting='{:g}')  ## Description This function generates a string containing a Excel-formatted (tab delimited) table from an input array supplemented with other formatting and header information. ## Dependencies • import numpy as np ## Notes At its core, a table at minimum consists of an array of 1x1 cells. In addition, it may be padded to its left and top with row and column headers, respectively, which may span multiple rows/columns. The number of row-header columns or column-header rows can be arbitrary. ## Inputs (required) • core_table = an RxC array (number of rows x number of columns) or equivalent list of table values constituting the core portion of the table composed of 1x1 cell elements ## Inputs (optional) • title = (optional) string to be placed above the table (D=None) • row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable: • row_header_spans = A list (or list of lists) of the number of rows each row header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead. • column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable: • column_header_spans = A list (or list of lists) of the number of column each column header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead. • float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}') ## Outputs • tab_str = text string containing the formatted table Expand source code def Excel_table_generator(core_table,title=None,row_headers=None,row_header_spans=None,column_headers=None,column_header_spans=None,float_formatting='{:g}'): ''' Description: This function generates a string containing a Excel-formatted (tab delimited) table from an input array supplemented with other formatting and header information. Dependencies: - import numpy as np Notes: At its core, a table at minimum consists of an array of 1x1 cells. In addition, it may be padded to its left and top with row and column headers, respectively, which may span multiple rows/columns. The number of row-header columns or column-header rows can be arbitrary. Inputs: (required) - core_table = an RxC array (number of rows x number of columns) or equivalent list of table values constituting the core portion of the table composed of 1x1 cell elements Inputs: (optional) - title = (optional) string to be placed above the table (D=None) - row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable: - row_header_spans = A list (or list of lists) of the number of rows each row header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead. - column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable: - column_header_spans = A list (or list of lists) of the number of column each column header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead. - float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}') Outputs: - tab_str = text string containing the formatted table ''' core_table = np.array(core_table) if row_headers or column_headers: # need to generate comprehensive table array using headers and provided core array table_array = table_array_generator(core_table,row_headers=row_headers,row_header_spans=row_header_spans,column_headers=column_headers,column_header_spans=column_header_spans,float_formatting=float_formatting) num_rows, num_cols = np.shape(table_array) tab_str = '' for ri in range(num_rows): for ci in range(num_cols): if isinstance(table_array[ri,ci],str): val = table_array[ri,ci] elif isinstance(table_array[ri,ci],float): val=float_formatting.format(table_array[ri,ci]) else: val = str(table_array[ri,ci]) tab_str += val + '\t' tab_str = tab_str[:-1] + '\n' if title: tab_str = title + '\n' + tab_str return tab_str  def ICRP116_effective_dose_coeff(E=1.0, particle='photon', geometry='AP', interp_scale='log', interp_type='cubic', extrapolation_on=False)  ## Description For a given particle at a given energy in a given geometry, returns its effective dose conversion coefficient from ICRP 116 ## Dependencies • import numpy as np • from scipy.interpolate import CubicSpline, lagrange, interp1d • find() (function within the "Hunter's tools" package) ## Inputs • E = energy of the particle in MeV (D=1) • particle = select particle (D='photon', options include: ['photon', 'electron', 'positron' ,'neutron' ,'proton', 'negmuon', 'posmuon', 'negpion', 'pospion', 'He3ion']) • geometry = geometric arrangement (D='AP', options include: ['AP', 'PA', 'LLAT', 'RLAT', 'ROT', 'ISO', 'H*(10)'] ('LLAT','RLAT','ROT' only available for photon, proton, and neutron)) • Meanings: AP, antero-posterior; PA, postero-anterior; LLAT, left lateral; RLAT, right lateral; ROT, rotational; ISO, isotropic. • Note: 'H*(10)' ambient dose equivalent is available for photons only • interp_scale = interpolation scale (D='log' to interpolate on a log scale, options include: ['log','lin'], ICRP 74/116 suggest log-log cubic interpolation) • interp_type = interpolation method (D='cubic' to interpolate with a cubic spline, options include: ['cubic','linear'], ICRP 74/116 suggest log-log cubic interpolation) technically, any options available for scipy.interpolate.interp1d() can be used: ['linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic', 'previous'] • extrapolation_on = boolean designating whether values outside of the tabulated energies will be extrapolated (D=False) if False & E < E_min f(E) = 0 if False & E > E_max f(E) = f(E_max) if True & E < E_min f(E) is linearly interpolated between (0,0) and (E_min,f(E_min)) if True & E > E_max f(E) is extrapolated using the specified interpolation scale and type ## Outputs • f = effective dose conversion coefficient in pSv*cm^2 Expand source code def ICRP116_effective_dose_coeff(E=1.0,particle='photon',geometry='AP',interp_scale='log',interp_type='cubic',extrapolation_on=False): ''' Description: For a given particle at a given energy in a given geometry, returns its effective dose conversion coefficient from ICRP 116 Dependencies: - import numpy as np - from scipy.interpolate import CubicSpline, lagrange, interp1d - find (function within the "Hunter's tools" package) Inputs: - E = energy of the particle in MeV (D=1) - particle = select particle (D='photon', options include: ['photon', 'electron', 'positron' ,'neutron' ,'proton', 'negmuon', 'posmuon', 'negpion', 'pospion', 'He3ion']) - geometry = geometric arrangement (D='AP', options include: ['AP', 'PA', 'LLAT', 'RLAT', 'ROT', 'ISO', 'H*(10)'] ('LLAT','RLAT','ROT' only available for photon, proton, and neutron)) - Meanings: AP, antero-posterior; PA, postero-anterior; LLAT, left lateral; RLAT, right lateral; ROT, rotational; ISO, isotropic. - Note: 'H*(10)' ambient dose equivalent is available for photons only - interp_scale = interpolation scale (D='log' to interpolate on a log scale, options include: ['log','lin'], ICRP 74/116 suggest log-log cubic interpolation) - interp_type = interpolation method (D='cubic' to interpolate with a cubic spline, options include: ['cubic','linear'], ICRP 74/116 suggest log-log cubic interpolation) technically, any options available for scipy.interpolate.interp1d() can be used: ['linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic', 'previous'] - extrapolation_on = boolean designating whether values outside of the tabulated energies will be extrapolated (D=False) | | | | -------------------- | --------------------------------------------------------------------- | | if False & E < E_min | f(E) = 0 | | if False & E > E_max | f(E) = f(E_max) | | if True & E < E_min | f(E) is linearly interpolated between (0,0) and (E_min,f(E_min)) | | if True & E > E_max | f(E) is extrapolated using the specified interpolation scale and type | Outputs: - f = effective dose conversion coefficient in pSv*cm^2 ''' pars_list = ['photon','electron','positron','neutron','proton','negmuon','posmuon','negpion','pospion','He3ion'] geo_list_all = ['AP','PA','LLAT','RLAT','ROT','ISO','H*(10)'] geo_list_short = ['AP','PA','ISO'] if particle not in pars_list or geometry not in geo_list_all: pstr = 'Please select a valid particle and geometry.\n' pstr += "Particle selected = {}, options include: ['photon','electron','positron','neutron','proton','negmuon','posmuon','negpion','pospion','He3ion']".format(particle) pstr += "Geometry selected = {}, options include: ['AP','PA','LLAT','RLAT','ROT','ISO'] ('LLAT','RLAT','ROT' only available for photon, proton, and neutron)" print(pstr) return None if (particle not in ['photon','neutron','proton'] and geometry in ['LLAT','RLAT','ROT']) or (particle!='photon' and geometry=='H*(10)'): if (particle!='photon' and geometry=='H*(10)'): pstr = "geometry = {} is only available for photons\n".format(geometry) else: pstr = "geometry = {} is only available for photon, neutron, and proton\n".format(geometry) pstr += "For selected particle = {}, please choose geometry from ['AP','PA','ISO']".format(particle) print(pstr) return None E_photon = [0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.511, 0.6, 0.662, 0.8, 1, 1.117, 1.33, 1.5, 2, 3, 4, 5, 6, 6.129, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000] f_photon = [ [0.0685, 0.156, 0.225, 0.313, 0.351, 0.37, 0.39, 0.413, 0.444, 0.519, 0.748, 1, 1.51, 2, 2.47, 2.52, 2.91, 3.17, 3.73, 4.49, 4.9, 5.59, 6.12, 7.48, 9.75, 11.7, 13.4, 15, 15.1, 17.8, 20.5, 26.1, 30.8, 37.9, 43.1, 47.1, 50.1, 54.5, 57.8, 63.3, 67.3, 72.3, 75.5, 77.5, 78.9, 80.5, 81.7, 83.8, 85.2, 86.9, 88.1, 88.9, 89.5, 90.2, 90.7], [0.0184, 0.0155, 0.026, 0.094, 0.161, 0.208, 0.242, 0.271, 0.301, 0.361, 0.541, 0.741, 1.16, 1.57, 1.98, 2.03, 2.38, 2.62, 3.13, 3.83, 4.22, 4.89, 5.39, 6.75, 9.12, 11.2, 13.1, 15, 15.2, 18.6, 22, 30.3, 38.2, 51.4, 62, 70.4, 76.9, 86.6, 93.2, 104, 111, 119, 124, 128, 131, 135, 138, 142, 145, 148, 150, 152, 153, 155, 155], [0.0189, 0.0416, 0.0655, 0.11, 0.14, 0.16, 0.177, 0.194, 0.214, 0.259, 0.395, 0.552, 0.888, 1.24, 1.58, 1.62, 1.93, 2.14, 2.59, 3.23, 3.58, 4.2, 4.68, 5.96, 8.21, 10.2, 12, 13.7, 13.9, 17, 20.1, 27.4, 34.4, 47.4, 59.2, 69.5, 78.3, 92.4, 103, 121, 133, 148, 158, 165, 170, 178, 183, 193, 198, 206, 212, 216, 219, 224, 228], [0.0182, 0.039, 0.0573, 0.0891, 0.114, 0.133, 0.15, 0.167, 0.185, 0.225, 0.348, 0.492, 0.802, 1.13, 1.45, 1.49, 1.78, 1.98, 2.41, 3.03, 3.37, 3.98, 4.45, 5.7, 7.9, 9.86, 11.7, 13.4, 13.6, 16.6, 19.7, 27.1, 34.4, 48.1, 60.9, 72.2, 82, 97.9, 110, 130, 143, 161, 172, 180, 186, 195, 201, 212, 220, 229, 235, 240, 244, 251, 255], [0.0337, 0.0664, 0.0986, 0.158, 0.199, 0.226, 0.248, 0.273, 0.297, 0.355, 0.528, 0.721, 1.12, 1.52, 1.92, 1.96, 2.3, 2.54, 3.04, 3.72, 4.1, 4.75, 5.24, 6.55, 8.84, 10.8, 12.7, 14.4, 14.6, 17.6, 20.6, 27.7, 34.4, 46.1, 56, 64.4, 71.2, 82, 89.7, 102, 111, 121, 128, 133, 136, 142, 145, 152, 156, 161, 165, 168, 170, 172, 175], [0.0288, 0.056, 0.0812, 0.127, 0.158, 0.18, 0.199, 0.218, 0.239, 0.287, 0.429, 0.589, 0.932, 1.28, 1.63, 1.67, 1.97, 2.17, 2.62, 3.25, 3.6, 4.2, 4.66, 5.9, 8.08, 10, 11.8, 13.5, 13.7, 16.6, 19.6, 26.8, 33.8, 46.1, 56.9, 66.2, 74.1, 87.2, 97.5, 116, 130, 147, 159, 168, 174, 185, 193, 208, 218, 232, 243, 251, 258, 268, 276], [0.061, 0.83, 1.05, 0.81, 0.64, 0.55, 0.51, 0.52, 0.53, 0.61, 0.89, 1.20, 1.80, 2.38, 2.93, 2.99, 3.44, 3.73, 4.38, 5.20, 5.60, 6.32, 6.90, 8.60, 11.10, 13.40, 15.50, 17.60, 17.86, 21.60, 25.60, 8.53, 8.29, 8.23, 8.26, 8.64, 8.71, 8.86, 9.00, 9.60, 10.20, 10.73, 11.27, 11.80, 11.78, 11.74, 11.70, 11.60, 11.50, 12.10, 12.70, 13.30, 13.08, 12.64, 12.20] ] E_electron = [0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000] f_electron = [ [0.0269, 0.0404, 0.0539, 0.081, 0.108, 0.135, 0.163, 0.218, 0.275, 0.418, 0.569, 0.889, 1.24, 1.63, 2.05, 4.04, 7.1, 15, 22.4, 36.1, 48.2, 59.3, 70.6, 97.9, 125, 188, 236, 302, 329, 337, 341, 346, 349, 355, 359, 365, 369, 372, 375, 379, 382, 387, 391, 397, 401, 405, 407, 411, 414], [0.0268, 0.0402, 0.0535, 0.0801, 0.107, 0.133, 0.16, 0.213, 0.267, 0.399, 0.53, 0.787, 1.04, 1.28, 1.5, 1.68, 1.68, 1.62, 1.62, 1.95, 2.62, 3.63, 5.04, 9.46, 18.3, 53.1, 104, 220, 297, 331, 344, 358, 366, 379, 388, 399, 408, 414, 419, 428, 434, 446, 455, 468, 477, 484, 490, 499, 507], [0.0188, 0.0283, 0.0377, 0.0567, 0.0758, 0.0948, 0.114, 0.152, 0.191, 0.291, 0.393, 0.606, 0.832, 1.08, 1.35, 1.97, 2.76, 4.96, 7.24, 11.9, 16.4, 21, 25.5, 35.5, 46.7, 76.9, 106, 164, 212, 249, 275, 309, 331, 363, 383, 410, 430, 445, 457, 478, 495, 525, 549, 583, 608, 628, 646, 675, 699] ] E_positron = [0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000] f_positron = [ [3.28, 3.29, 3.3, 3.33, 3.36, 3.39, 3.42, 3.47, 3.53, 3.67, 3.84, 4.16, 4.52, 4.9, 5.36, 7.41, 10.5, 18.3, 25.7, 39.1, 51, 61.7, 72.9, 99, 126, 184, 229, 294, 320, 327, 333, 339, 342, 349, 354, 362, 366, 369, 372, 376, 379, 385, 389, 395, 399, 402, 404, 408, 411], [1.62, 1.64, 1.65, 1.68, 1.71, 1.73, 1.76, 1.82, 1.87, 2.01, 2.14, 2.4, 2.65, 2.9, 3.12, 3.32, 3.37, 3.44, 3.59, 4.19, 5.11, 6.31, 8.03, 14, 23.6, 59, 111, 221, 291, 321, 334, 349, 357, 371, 381, 393, 402, 409, 415, 424, 430, 443, 451, 465, 473, 480, 486, 495, 503], [1.39, 1.4, 1.41, 1.43, 1.45, 1.47, 1.49, 1.53, 1.57, 1.67, 1.77, 1.98, 2.21, 2.45, 2.72, 3.38, 4.2, 6.42, 8.7, 13.3, 18, 22.4, 26.9, 36.7, 47.6, 75.5, 104, 162, 209, 243, 268, 302, 323, 356, 377, 405, 425, 440, 453, 474, 491, 522, 545, 580, 605, 627, 645, 674, 699] ] E_neutron = [1.00E-09, 1.00E-08, 2.50E-08, 1.00E-07, 2.00E-07, 5.00E-07, 1.00E-06, 2.00E-06, 5.00E-06, 1.00E-05, 2.00E-05, 5.00E-05, 1.00E-04, 2.00E-04, 5.00E-04, 0.001, 0.002, 0.005, 0.01, 0.02, 0.03, 0.05, 0.07, 0.1, 0.15, 0.2, 0.3, 0.5, 0.7, 0.9, 1, 1.2, 1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 30, 50, 75, 100, 130, 150, 180, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 5000, 10000] f_neutron = [ [3.09, 3.55, 4, 5.2, 5.87, 6.59, 7.03, 7.39, 7.71, 7.82, 7.84, 7.82, 7.79, 7.73, 7.54, 7.54, 7.61, 7.97, 9.11, 12.2, 15.7, 23, 30.6, 41.9, 60.6, 78.8, 114, 177, 232, 279, 301, 330, 365, 407, 458, 483, 494, 498, 499, 499, 500, 500, 499, 495, 493, 490, 484, 477, 474, 453, 433, 420, 402, 382, 373, 363, 359, 363, 389, 422, 457, 486, 508, 524, 537, 612, 716, 933], [1.85, 2.11, 2.44, 3.25, 3.72, 4.33, 4.73, 5.02, 5.3, 5.44, 5.51, 5.55, 5.57, 5.59, 5.6, 5.6, 5.62, 5.95, 6.81, 8.93, 11.2, 15.7, 20, 25.9, 34.9, 43.1, 58.1, 85.9, 112, 136, 148, 167, 195, 235, 292, 330, 354, 371, 383, 392, 398, 404, 412, 417, 419, 420, 422, 423, 423, 422, 428, 439, 444, 446, 446, 447, 448, 464, 496, 533, 569, 599, 623, 640, 654, 740, 924, 1.17E+03], [1.04, 1.15, 1.32, 1.7, 1.94, 2.21, 2.4, 2.52, 2.64, 2.65, 2.68, 2.66, 2.65, 2.66, 2.62, 2.61, 2.6, 2.74, 3.13, 4.21, 5.4, 7.91, 10.5, 14.4, 20.8, 27.2, 39.7, 63.7, 85.5, 105, 115, 130, 150, 179, 221, 249, 269, 284, 295, 303, 310, 316, 325, 333, 336, 338, 343, 347, 348, 360, 380, 399, 409, 416, 420, 425, 427, 441, 472, 510, 547, 579, 603, 621, 635, 730, 963, 1.23E+03], [0.893, 0.978, 1.12, 1.42, 1.63, 1.86, 2.02, 2.11, 2.21, 2.24, 2.26, 2.24, 2.23, 2.24, 2.21, 2.21, 2.2, 2.33, 2.67, 3.6, 4.62, 6.78, 8.95, 12.3, 17.9, 23.4, 34.2, 54.4, 72.6, 89.3, 97.4, 110, 128, 153, 192, 220, 240, 255, 267, 276, 284, 290, 301, 310, 313, 317, 323, 328, 330, 345, 370, 392, 404, 413, 418, 425, 429, 451, 483, 523, 563, 597, 620, 638, 651, 747, 979, 1.26E+03], [1.7, 2.03, 2.31, 2.98, 3.36, 3.86, 4.17, 4.4, 4.59, 4.68, 4.72, 4.73, 4.72, 4.67, 4.6, 4.58, 4.61, 4.86, 5.57, 7.41, 9.46, 13.7, 18, 24.3, 34.7, 44.7, 63.8, 99.1, 131, 160, 174, 193, 219, 254, 301, 331, 351, 365, 374, 381, 386, 390, 395, 398, 398, 399, 399, 398, 398, 395, 395, 402, 406, 411, 414, 418, 422, 443, 472, 503, 532, 558, 580, 598, 614, 718, 906, 1.14E+03], [1.29, 1.56, 1.76, 2.26, 2.54, 2.92, 3.15, 3.32, 3.47, 3.52, 3.54, 3.55, 3.54, 3.52, 3.47, 3.46, 3.48, 3.66, 4.19, 5.61, 7.18, 10.4, 13.7, 18.6, 26.6, 34.4, 49.4, 77.1, 102, 126, 137, 153, 174, 203, 244, 271, 290, 303, 313, 321, 327, 332, 339, 344, 346, 347, 350, 352, 353, 358, 371, 387, 397, 407, 412, 421, 426, 455, 488, 521, 553, 580, 604, 624, 642, 767, 1.01E+03, 1.32E+03] ] E_proton = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000] f_proton = [ [5.46, 8.2, 10.9, 16.4, 21.9, 27.3, 32.8, 43.7, 54.9, 189, 428, 750, 1.02E+03, 1.18E+03, 1.48E+03, 2.16E+03, 2.51E+03, 2.38E+03, 1.77E+03, 1.38E+03, 1.23E+03, 1.15E+03, 1.16E+03, 1.11E+03, 1.09E+03, 1.15E+03, 1.12E+03, 1.23E+03, 1.27E+03, 1.23E+03, 1.37E+03, 1.45E+03, 1.41E+03], [5.47, 8.21, 10.9, 16.4, 21.9, 27.3, 32.8, 43.7, 54.6, 56.1, 43.6, 36.1, 45.5, 71.5, 156, 560, 1.19E+03, 2.82E+03, 1.93E+03, 1.45E+03, 1.30E+03, 1.24E+03, 1.23E+03, 1.23E+03, 1.23E+03, 1.25E+03, 1.28E+03, 1.34E+03, 1.40E+03, 1.45E+03, 1.53E+03, 1.65E+03, 1.74E+03], [2.81, 4.21, 5.61, 8.43, 11.2, 14, 16.8, 22.4, 28.1, 50.7, 82.8, 180, 290, 379, 500, 799, 994, 1.64E+03, 2.15E+03, 1.44E+03, 1.27E+03, 1.21E+03, 1.20E+03, 1.19E+03, 1.18E+03, 1.21E+03, 1.25E+03, 1.32E+03, 1.31E+03, 1.39E+03, 1.44E+03, 1.56E+03, 1.63E+03], [2.81, 4.2, 5.62, 8.41, 11.2, 14, 16.8, 22.4, 28.1, 48.9, 78.8, 172, 278, 372, 447, 602, 818, 1.46E+03, 2.18E+03, 1.45E+03, 1.28E+03, 1.21E+03, 1.20E+03, 1.20E+03, 1.20E+03, 1.23E+03, 1.25E+03, 1.32E+03, 1.33E+03, 1.41E+03, 1.45E+03, 1.59E+03, 1.67E+03], [4.5, 6.75, 8.98, 13.4, 17.8, 22.1, 26.3, 34.5, 50.1, 93.7, 165, 296, 422, 532, 687, 1.09E+03, 1.44E+03, 2.16E+03, 1.96E+03, 1.44E+03, 1.28E+03, 1.22E+03, 1.22E+03, 1.20E+03, 1.19E+03, 1.23E+03, 1.23E+03, 1.30E+03, 1.29E+03, 1.35E+03, 1.41E+03, 1.49E+03, 1.56E+03], [3.52, 5.28, 7.02, 10.5, 13.9, 17.3, 20.5, 26.8, 45.8, 80.1, 136, 249, 358, 451, 551, 837, 1.13E+03, 1.79E+03, 1.84E+03, 1.42E+03, 1.25E+03, 1.18E+03, 1.17E+03, 1.17E+03, 1.15E+03, 1.21E+03, 1.22E+03, 1.31E+03, 1.40E+03, 1.43E+03, 1.57E+03, 1.71E+03, 1.78E+03] ] E_negmuon = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000] f_negmuon = [ [180, 180, 184, 188, 193, 205, 242, 293, 332, 414, 465, 657, 735, 755, 628, 431, 382, 340, 326, 319, 320, 321, 325, 327, 333, 331, 333, 336, 337, 337, 337, 337, 338], [75.2, 76.8, 78.3, 81.4, 84.8, 87.7, 86.7, 86.8, 88.6, 100, 122, 251, 457, 703, 775, 485, 402, 345, 329, 321, 321, 324, 326, 332, 337, 338, 341, 344, 345, 346, 346, 347, 347], [78.7, 79.5, 80.9, 83.7, 87.1, 91.5, 98.1, 113, 127, 161, 191, 275, 363, 446, 496, 498, 432, 354, 332, 321, 321, 323, 326, 331, 337, 338, 341, 344, 346, 347, 347, 348, 348] ] E_posmuon = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000] f_posmuon = [ [194, 196, 198, 202, 207, 216, 251, 300, 340, 425, 481, 674, 751, 768, 635, 431, 381, 339, 326, 318, 319, 320, 322, 325, 327, 331, 333, 336, 337, 337, 337, 337, 339], [82.6, 84.1, 85.7, 88.9, 92.1, 94.3, 92.5, 92.8, 94.8, 108, 133, 265, 473, 721, 787, 483, 399, 345, 328, 320, 321, 323, 325, 330, 333, 339, 341, 344, 345, 346, 346, 347, 347], [85.2, 86.2, 87.5, 90.3, 93.6, 97.7, 103, 117, 132, 167, 199, 284, 373, 456, 506, 502, 432, 354, 332, 320, 320, 322, 324, 329, 333, 338, 341, 344, 346, 347, 347, 348, 348] ] E_negpion = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 15000, 20000, 30000, 40000, 50000, 60000, 80000, 100000, 150000, 200000] f_negpion = [ [406, 422, 433, 458, 491, 528, 673, 965, 1.09E+03, 1.25E+03, 1.28E+03, 1.77E+03, 1.92E+03, 1.93E+03, 1.68E+03, 1.14E+03, 995, 927, 902, 848, 844, 869, 901, 947, 977, 1.03E+03, 1.05E+03, 1.03E+03, 1.03E+03, 1.06E+03, 1.09E+03, 1.14E+03, 1.17E+03, 1.21E+03, 1.24E+03, 1.30E+03, 1.35E+03, 1.39E+03, 1.42E+03, 1.48E+03, 1.54E+03, 1.67E+03, 1.78E+03], [194, 201, 210, 225, 233, 237, 208, 181, 178, 197, 244, 547, 1.02E+03, 1.70E+03, 1.99E+03, 1.31E+03, 991, 889, 871, 843, 850, 880, 917, 976, 1.02E+03, 1.08E+03, 1.12E+03, 1.11E+03, 1.13E+03, 1.18E+03, 1.22E+03, 1.29E+03, 1.34E+03, 1.41E+03, 1.47E+03, 1.56E+03, 1.63E+03, 1.70E+03, 1.75E+03, 1.86E+03, 1.95E+03, 2.15E+03, 2.33E+03], [176, 189, 198, 215, 232, 251, 271, 317, 361, 439, 508, 676, 868, 1.02E+03, 1.15E+03, 1.15E+03, 1.03E+03, 857, 815, 794, 807, 838, 875, 935, 979, 1.05E+03, 1.09E+03, 1.11E+03, 1.15E+03, 1.20E+03, 1.26E+03, 1.36E+03, 1.43E+03, 1.55E+03, 1.64E+03, 1.79E+03, 1.91E+03, 2.02E+03, 2.11E+03, 2.29E+03, 2.46E+03, 2.80E+03, 3.04E+03] ] E_pospion = [1, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 80, 100, 150, 200, 300, 400, 500, 600, 800, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000, 15000, 20000, 30000, 40000, 50000, 60000, 80000, 100000, 150000, 200000] f_pospion = [ [314, 324, 340, 379, 429, 489, 540, 717, 819, 1000, 1.10E+03, 1.52E+03, 1.75E+03, 1.83E+03, 1.66E+03, 1.22E+03, 1.13E+03, 1.22E+03, 1.25E+03, 1.07E+03, 969, 943, 952, 999, 1.04E+03, 1.10E+03, 1.10E+03, 1.06E+03, 1.06E+03, 1.07E+03, 1.10E+03, 1.14E+03, 1.17E+03, 1.22E+03, 1.25E+03, 1.30E+03, 1.34E+03, 1.38E+03, 1.42E+03, 1.48E+03, 1.54E+03, 1.67E+03, 1.78E+03], [121, 125, 133, 151, 170, 183, 185, 177, 179, 201, 247, 494, 906, 1.48E+03, 1.82E+03, 1.38E+03, 1.12E+03, 1.15E+03, 1.23E+03, 1.10E+03, 998, 970, 980, 1.04E+03, 1.09E+03, 1.16E+03, 1.19E+03, 1.16E+03, 1.16E+03, 1.20E+03, 1.24E+03, 1.31E+03, 1.35E+03, 1.42E+03, 1.48E+03, 1.57E+03, 1.64E+03, 1.70E+03, 1.75E+03, 1.84E+03, 1.94E+03, 2.14E+03, 2.33E+03], [151, 160, 168, 183, 198, 216, 233, 265, 296, 367, 439, 602, 787, 953, 1.09E+03, 1.16E+03, 1.10E+03, 1.05E+03, 1.08E+03, 1.02E+03, 953, 930, 938, 993, 1.05E+03, 1.13E+03, 1.16E+03, 1.16E+03, 1.18E+03, 1.23E+03, 1.28E+03, 1.37E+03, 1.43E+03, 1.55E+03, 1.64E+03, 1.79E+03, 1.90E+03, 2.01E+03, 2.10E+03, 2.27E+03, 2.42E+03, 2.76E+03, 3.07E+03] ] E_He3ion = [1, 2, 3, 5, 10, 14, 20, 30, 50, 75, 100, 150, 200, 300, 500, 700, 1000, 2000, 3000, 5000, 10000, 20000, 50000, 100000] f_He3ion = [ [219, 438, 656, 1.09E+03, 2.19E+03, 4.61E+03, 1.72E+04, 3.01E+04, 4.75E+04, 8.05E+04, 1.01E+05, 9.25E+04, 6.74E+04, 5.14E+04, 4.27E+04, 4.11E+04, 4.00E+04, 4.02E+04, 4.08E+04, 4.12E+04, 4.56E+04, 5.12E+04, 6.12E+04, 7.14E+04], [219, 438, 657, 1.09E+03, 2.19E+03, 2.56E+03, 1.74E+03, 1.44E+03, 2.88E+03, 1.75E+04, 4.84E+04, 1.10E+05, 7.29E+04, 5.33E+04, 4.49E+04, 4.60E+04, 4.47E+04, 4.80E+04, 5.01E+04, 5.17E+04, 6.26E+04, 6.10E+04, 8.14E+04, 1.01E+05], [141, 281, 419, 689, 1.82E+03, 2.81E+03, 5.46E+03, 9.86E+03, 1.78E+04, 3.00E+04, 4.55E+04, 6.95E+04, 7.01E+04, 5.25E+04, 4.27E+04, 4.19E+04, 4.09E+04, 4.31E+04, 4.50E+04, 4.76E+04, 5.73E+04, 7.10E+04, 9.67E+04, 1.24E+05] ] E_all = [E_photon, E_electron, E_positron, E_neutron, E_proton, E_negmuon, E_posmuon, E_negpion, E_pospion, E_He3ion] f_all = [f_photon, f_electron, f_positron, f_neutron, f_proton, f_negmuon, f_posmuon, f_negpion, f_pospion, f_He3ion] pi = find(particle, pars_list) if particle in ['photon','neutron','proton']: gi = find(geometry, geo_list_all) else: gi = find(geometry, geo_list_short) E_list = E_all[pi] f_list = f_all[pi][gi] # Interpolate f given E if E in E_list: f = f_list[find(E,E_list)] else: if not extrapolation_on and (E < E_list[0] or E > E_list[-1]): # E is outside of bounds and extrapolation is off if E < E_list[0]: f = 0 # assume negligibly low energy particle if E > E_list[-1]: f = f_list[-1] # just set equal to max energy particle's coefficient else: if E < E_list[0]: E_list = [0] + E_list f_list = [0] + f_list interp_scale = 'linear' if interp_scale=='log': cs = interp1d(np.log10(np.array(E_list)),np.log10(np.array(f_list)), kind=interp_type,fill_value='extrapolate') f = 10**cs(np.log10(E)) else: cs = interp1d(np.array(E_list),np.array(f_list), kind=interp_type,fill_value='extrapolate') f = cs(E) # for sake of sanity, return zero for values quite below minimum coefficients if f < 1e-4: f = 0.0 #if interp_type=='cubic': # if interp_scale=='log': # cs = interp1d(np.log10(np.array(E_list)),np.log10(np.array(f_list)), kind='cubic',fill_value='extrapolate') # f = 10**cs(np.log10(E)) # else: # cs = interp1d(np.array(E_list),np.array(f_list), kind='cubic',fill_value='extrapolate') # f = cs(E) #else: # if interp_scale=='log': # f = 10**np.interp(np.log10(E),np.log10(np.array(E_list)),np.log10(np.array(f_list))) # else: # f = np.interp(E,np.array(E_list),np.array(f_list)) #if interp_type=='cubic': # if interp_scale=='log': # cs = lagrange(np.log10(np.array(E_list)),np.log10(np.array(f_list))) # f = 10**cs(np.log10(E)) # else: # cs = lagrange(np.array(E_list),np.array(f_list)) # f = cs(E) #if interp_type=='cubic': # if interp_scale=='log': # cs = CubicSpline(np.log10(np.array(E_list)),np.log10(np.array(f_list))) # f = 10**cs(np.log10(E)) # else: # cs = CubicSpline(np.array(E_list),np.array(f_list)) # f = cs(E) return f  def Latex_table_generator(core_table, title=None, row_headers=None, row_header_spans=None, column_headers=None, column_header_spans=None, float_formatting='{:g}', table_positioning='[h]', label=None, use_table_ruling=True, coulmn_formatting=None, hline_row_indices=None, cline_row_cstart_cend_indices_triplets=None, return_only_core_tabular_environment=False, nest_in_ruledtabular=False, colormap=None, color_transform_fcn=None, color_scale='linear', color_min_val=None, color_max_val=None)  ## Description This function generates a string containing a LaTeX-formatted table from an input array supplemented with other formatting and header information. ## Dependencies import numpy as np ## Notes At its core, a table at minimum consists of an array of 1x1 cells. In addition, it may be padded to its left and top with row and column headers, respectively, which may span multiple rows/columns. The number of row-header columns or column-header rows can be arbitrary. ## Inputs (required) • core_table = an RxC array (number of rows x number of columns) or equivalent list of table values constituting the core portion of the table composed of 1x1 cell elements ## Inputs (optional) • title = (optional) string to be placed above the table (D=None) • row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable: • row_header_spans = A list (or list of lists) of the number of rows each row header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead. • column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable: • column_header_spans = A list (or list of lists) of the number of column each column header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead. • float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}') • table_positioning = string of table positioning argument for LaTeX (D='[h]') • label = string of table label (D = slugify()(title)) • use_table_ruling = boolean controlling automatic use of toprule, midrule, and bottomrule (D=True) • coulmn_formatting = string controlling column borders and justification (D='{ccc...ccc}' (all columns centered)) • hline_row_indices = list (or single) of integer row indices beneath which a hline, spanning all columns, should be drawn (D=None) (-1 places line above very first row) • cline_row_cstart_cend_indices_triplets = list of length-3 lists (or an individual length-3 list) composed of [row index, column start, column end] for the \cline{start-stop} command • return_only_core_tabular_environment = boolean specifying whether the whole table (False) or just the inner tabular environment (True) will be returned (D=False) • nest_in_ruledtabular = boolean specifying if the tabular environment will (True) or will not (False) be nested inside of the ruledtabular environment used by REVTeX (D=False) • colormap = callable colormap object or string of matplotlib colormap name to be used in setting the background color of each cell in the table. By default, colormap = None and the table is produced normally. Otherwise, the specified colormap will be used to color the background of each cell based on its value. For the colors used, the values of core_table are rescaled from [min(core_table),max(core_table)] to [0,1] and then used to sample the provided colormap. As a special case, if colormap='default' then colormap=truncate_colormap('bwr',0.35,0.65) is used; ref:truncate_colormap() Note that in the LaTeX document \usepackage[table]{xcolor} must be included in the preamble for table colors to function. • color_transform_fcn = user-supplied function called to alter each value of core_table used for color assignment, prior to rescaling/normalization. This only affects color assignment, not the actual value printed to the final table. (D=None) • color_scale = string specifying whether the mapping of colors to the table values should use 'linear' (default) or 'log' scaling • color_min_val = float/int specifying new lower bound of core_table to be used in colormap normalization; all values less than or equal to col_min_val will be set to 0 in the array used to sample the colormap. • color_max_val = float/int specifying new upper bound of core_table to be used in colormap normalization; all values greater than or equal to col_max_val will be set to 1 in the array used to sample the colormap. ## Outputs • tab_str = text string containing the formatted table Expand source code def Latex_table_generator(core_table,title=None,row_headers=None,row_header_spans=None,column_headers=None,column_header_spans=None, float_formatting='{:g}',table_positioning='[h]',label=None,use_table_ruling=True,coulmn_formatting=None, hline_row_indices=None,cline_row_cstart_cend_indices_triplets = None,return_only_core_tabular_environment=False, nest_in_ruledtabular=False,colormap=None,color_transform_fcn=None,color_scale='linear',color_min_val=None,color_max_val=None): ''' Description: This function generates a string containing a LaTeX-formatted table from an input array supplemented with other formatting and header information. Dependencies: import numpy as np Notes: At its core, a table at minimum consists of an array of 1x1 cells. In addition, it may be padded to its left and top with row and column headers, respectively, which may span multiple rows/columns. The number of row-header columns or column-header rows can be arbitrary. Inputs: (required) - core_table = an RxC array (number of rows x number of columns) or equivalent list of table values constituting the core portion of the table composed of 1x1 cell elements Inputs: (optional) - title = (optional) string to be placed above the table (D=None) - row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable: - row_header_spans = A list (or list of lists) of the number of rows each row header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead. - column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable: - column_header_spans = A list (or list of lists) of the number of column each column header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead. - float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}') - table_positioning = string of table positioning argument for LaTeX (D='[h]') - label = string of table label (D = slugify(title)) - use_table_ruling = boolean controlling automatic use of toprule, midrule, and bottomrule (D=True) - coulmn_formatting = string controlling column borders and justification (D='{ccc...ccc}' (all columns centered)) - hline_row_indices = list (or single) of integer row indices beneath which a hline, spanning all columns, should be drawn (D=None) (-1 places line above very first row) - cline_row_cstart_cend_indices_triplets = list of length-3 lists (or an individual length-3 list) composed of [row index, column start, column end] for the \cline{start-stop} command - return_only_core_tabular_environment = boolean specifying whether the whole table (False) or just the inner tabular environment (True) will be returned (D=False) - nest_in_ruledtabular = boolean specifying if the tabular environment will (True) or will not (False) be nested inside of the ruledtabular environment used by REVTeX (D=False) - colormap = callable colormap object or string of matplotlib colormap name to be used in setting the background color of each cell in the table. By default, colormap = None and the table is produced normally. Otherwise, the specified colormap will be used to color the background of each cell based on its value. For the colors used, the values of core_table are rescaled from [min(core_table),max(core_table)] to [0,1] and then used to sample the provided colormap. As a special case, if colormap='default' then colormap=truncate_colormap('bwr',0.35,0.65) is used; ref:truncate_colormap Note that in the LaTeX document \\usepackage[table]{xcolor} must be included in the preamble for table colors to function. - color_transform_fcn = user-supplied function called to alter each value of core_table used for color assignment, prior to rescaling/normalization. This only affects color assignment, not the actual value printed to the final table. (D=None) - color_scale = string specifying whether the mapping of colors to the table values should use 'linear' (default) or 'log' scaling - color_min_val = float/int specifying new lower bound of core_table to be used in colormap normalization; all values less than or equal to col_min_val will be set to 0 in the array used to sample the colormap. - color_max_val = float/int specifying new upper bound of core_table to be used in colormap normalization; all values greater than or equal to col_max_val will be set to 1 in the array used to sample the colormap. Outputs: - tab_str = text string containing the formatted table ''' core_table = np.array(core_table) core_nrows, core_ncols = np.shape(core_table) # ensure everything is sorted nicely into the table array table_array = table_array_generator(core_table,row_headers=row_headers,row_header_spans=row_header_spans,column_headers=column_headers,column_header_spans=column_header_spans,float_formatting=float_formatting) num_rows, num_cols = np.shape(table_array) num_column_header_rows, num_row_header_columns = num_rows-core_nrows, num_cols-core_ncols if colormap: if isinstance(colormap,str): if colormap=='default' or colormap=='Default' or colormap=='DEFAULT': cmap = truncate_colormap('bwr', 0.35, 0.65) else: cmap = get_colormap(cmap) else: cmap = colormap color_val_table = np.copy(core_table) if color_transform_fcn!=None: # user supplied function to transform values for ri in range(core_nrows): for ci in range(core_ncols): color_val_table[ri,ci] = color_transform_fcn(color_val_table[ri,ci]) if color_min_val==None: col_min_val = np.min(color_val_table) else: col_min_val = color_min_val if color_max_val==None: col_max_val = np.max(color_val_table) else: col_max_val = color_max_val color_val_table[color_val_table<col_min_val] = col_min_val color_val_table[color_val_table>col_max_val] = col_max_val if color_scale=='log': if np.any(color_val_table<0): print('Warning: col_min_val<0 & core_table contains negative values and thus cannot be color scaled logarithmically; reverting to linear color scale.') else: if np.any(color_val_table==0): print('Warning: core_table contains zero values; they will be ignored while assigning colors.') color_val_table[color_val_table==0] = np.NaN # set to NaN and use this as a flag to ignore later color_val_table = np.log(color_val_table) col_min_val = np.log(col_min_val) col_max_val = np.log(col_max_val) color_val_table = (color_val_table - col_min_val)/(col_max_val - col_min_val) # double check values are within bounds color_val_table[color_val_table<0] = 0 color_val_table[color_val_table>1] = 1 if hline_row_indices: if not isinstance(hline_row_indices,list): hline_row_indices = [hline_row_indices] if cline_row_cstart_cend_indices_triplets: if not isinstance(cline_row_cstart_cend_indices_triplets,list): print('cline_row_cstart_cend_indices_triplets elements must be length-3 lists of the row index, starting column, and ending column') return None if not isinstance(cline_row_cstart_cend_indices_triplets[0],list): cline_row_cstart_cend_indices_triplets = [cline_row_cstart_cend_indices_triplets] tab_str = '' if not return_only_core_tabular_environment: tab_str += r'\begin{table}' + table_positioning + '\n' #tab_str += r'%\vspace*{-0.4cm}' + '\n' if title: tab_str += '\t' + r'\caption{'+title+'}' + '\n' else: tab_str += '\t' + r'%\caption{Title}' + '\n' if label: tab_str += '\t' + r'\label{tab:'+label+'}' + '\n' elif title: tab_str += '\t' + r'\label{tab:'+slugify(title)+'}' + '\n' else: tab_str += '\t' + r'%\label{tab:Label}' + '\n' tab_str += '\t' + r'\centering' + '\n' if nest_in_ruledtabular: tab_str += '\t' + r'\begin{ruledtabular}' + '\n' if coulmn_formatting: tab_str += '\t' + r'\begin{tabular}' + coulmn_formatting + '\n' else: cstr = 'c'*num_cols tab_str += '\t' + r'\begin{tabular}{'+cstr+'}' + '\n' if use_table_ruling: tab_str += '\t\t' + r'\toprule' + '\n' if hline_row_indices: if -1 in hline_row_indices: tab_str += '\t\t' + r'\hline' + '\n' chi_list = [0 for i in range(num_row_header_columns)] for ri in range(num_rows): line = '' # handle header row separately if ri < num_column_header_rows: if num_column_header_rows==1: this_header_set = column_headers this_header_spans = column_header_spans else: this_header_set = column_headers[ri] this_header_spans = column_header_spans[ri] if not isinstance(this_header_spans,list): tmp = this_header_spans this_header_spans = [tmp for i in this_header_set] for hi in range(len(this_header_set)-1,-1,-1): if this_header_spans[hi] == 1: line = this_header_set[hi] + ' & ' + line else: line = r'\multicolumn{'+'{}'.format(this_header_spans[hi])+'}{c}{' + this_header_set[hi] + '} & ' + line if np.sum(this_header_spans) < num_cols: for i in range(num_cols-int(np.sum(this_header_spans))): line = ' & ' + line tab_str += '\t\t' + line[:-2] + ' \\\\' + '\n' if hline_row_indices: if ri in hline_row_indices: tab_str += '\t\t' + r'\hline' + '\n' if cline_row_cstart_cend_indices_triplets: for i in range(len(cline_row_cstart_cend_indices_triplets)): if ri == cline_row_cstart_cend_indices_triplets[i][0]: tab_str += '\t\t' + r'\cline{' + '{}-{}'.format(str(int(cline_row_cstart_cend_indices_triplets[i][1])),str(int(cline_row_cstart_cend_indices_triplets[i][2]))) + '}' + '\n' continue if use_table_ruling and ri==num_column_header_rows and num_column_header_rows>0: tab_str += '\t\t' + r'\midrule' + '\n' for ci in range(num_cols): val = table_array[ri,ci] if not isinstance(val,str): val = float_formatting.format(val) # add multicolumns where appropriate if ci < num_row_header_columns: if num_row_header_columns==1: this_header_set = row_headers this_header_spans = row_header_spans else: this_header_set = row_headers[ci] this_header_spans = row_header_spans[ci] if not isinstance(this_header_spans,list): tmp = this_header_spans this_header_spans = [tmp for i in this_header_set] if val != ' ' and this_header_spans[ci]>1: line += r'\multirow{'+'{}'.format(this_header_spans[chi_list[ci]])+'}{*}{' + this_header_set[chi_list[ci]] + '} & ' chi_list[ci] += 1 else: line += val + ' & ' continue if colormap: if not np.isnan(color_val_table[ri-num_column_header_rows,ci-num_row_header_columns]): # skip zero values assigned NaN when log scaled col_rgb = cmap(color_val_table[ri-num_column_header_rows,ci-num_row_header_columns]) col_hex = rgb_to_hex(col_rgb)[1:] # remove leading '#' character line += r'\cellcolor[HTML]{'+col_hex+'} ' line += val + ' & ' tab_str += '\t\t' + line[:-2] + ' \\\\' + '\n' if hline_row_indices: if ri in hline_row_indices: tab_str += '\t\t' + r'\hline' + '\n' if cline_row_cstart_cend_indices_triplets: for i in range(len(cline_row_cstart_cend_indices_triplets)): if ri == cline_row_cstart_cend_indices_triplets[i][0]: tab_str += '\t\t' + r'\cline{' + '{}-{}'.format(str(int(cline_row_cstart_cend_indices_triplets[i][1])),str(int(cline_row_cstart_cend_indices_triplets[i][2]))) + '}' + '\n' if use_table_ruling: tab_str += '\t\t' + r'\bottomrule' + '\n' tab_str += '\t' + r'\end{tabular}' + '\n' if nest_in_ruledtabular: tab_str += '\t' + r'\end{ruledtabular}' + '\n' if not return_only_core_tabular_environment: tab_str += r'\end{table}' + '\n' if return_only_core_tabular_environment: tab_str = tab_str[1:].replace('\n\t','\n') # remove extra tab character from start of each line return tab_str  def Lorentz_B2_from_Tn(Tn)  ## Description Calculate the Lorentz variable beta^2 provided a neutron kinetic energy in MeV ## Inputs • Tn = neutron kinetic energy in MeV (float ot int) ## Outputs • beta_squared = Lorentz variable beta^2 Expand source code def Lorentz_B2_from_Tn(Tn): ''' Description: Calculate the Lorentz variable beta^2 provided a neutron kinetic energy in MeV Inputs: - Tn = neutron kinetic energy in MeV (float ot int) Outputs: - beta_squared = Lorentz variable beta^2 ''' m_n = 939.5654133 # neutron mass in MeV/c^2 c_vac = 29979245800 # cm/s refractive_index_air = 1.0003 c = c_vac/refractive_index_air c_cm_per_ns = c/(10**9) gamma = 1 + (Tn/m_n) beta_squared = 1 - (1/(gamma**2)) return beta_squared  def Lorentz_gamma(v)  ## Description Calculate the Lorentz variable gamma provided a velocity in cm/ns ## Inputs • v = velocity in cm/ns (float ot int) ## Outputs • gamma = Lorentz variable gamma Expand source code def Lorentz_gamma(v): ''' Description: Calculate the Lorentz variable gamma provided a velocity in cm/ns Inputs: - v = velocity in cm/ns (float ot int) Outputs: - gamma = Lorentz variable gamma ''' m_n = 939.5654133 # neutron mass in MeV/c^2 c_vac = 29979245800 # cm/s refractive_index_air = 1.0003 c = c_vac/refractive_index_air c_cm_per_ns = c/(10**9) beta = v/c_cm_per_ns gamma = np.sqrt(1/(1 - beta**2)) return gamma  def N_to_relative_error(N)  ## Description Convert an "N" value to a relative uncertainty ## Inputs • N = "number of counts"-like number ## Outputs • relerr = a relative uncertainty Expand source code def N_to_relative_error(N): ''' Description: Convert an "N" value to a relative uncertainty Inputs: - N = "number of counts"-like number Outputs: - relerr = a relative uncertainty ''' if N == 0: # or N < 0: relerr = 0 else: relerr = (1/(N**0.5)) return relerr  def SI_prefix_converter(SI1, SI2='')  ## Description Provides the multiplication constant needed to convert between SI prefixes ## Dependencies find() (function within the "Hunter's tools" package) ## Inputs • SI1 = string of SI prefix of the current value • SI2 = string of the desired SI prefix of the value (D='', no SI prefix) These values should be selected from this list: ['y', 'z', 'a', 'f', 'p', 'n', 'u', 'm', 'c', 'd', '', 'da', 'h', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] ## Outputs • m = multiplier to convert a value of SI prefix SI1 to one of SI prefix SI2 Expand source code def SI_prefix_converter(SI1,SI2=''): ''' Description: Provides the multiplication constant needed to convert between SI prefixes Dependencies: find (function within the "Hunter's tools" package) Inputs: - SI1 = string of SI prefix of the current value - SI2 = string of the desired SI prefix of the value (D='', no SI prefix) These values should be selected from this list: ['y', 'z', 'a', 'f', 'p', 'n', 'u', 'm', 'c', 'd', '', 'da', 'h', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] Outputs: - m = multiplier to convert a value of SI prefix SI1 to one of SI prefix SI2 ''' SI_prefixes = ['y','z','a','f','p','n','u','m','c','d','','da','h','k','M','G','T','P','E','Z','Y'] SI_powers = [-24,-21,-18,-15,-12, -9, -6, -3, -2, -1, 0, 1, 2, 3, 6, 9, 12, 15, 18, 21, 24] if SI1 not in SI_prefixes or SI2 not in SI_prefixes: if SI1 not in SI_prefixes and SI2 not in SI_prefixes: pstr = 'Both SI1={} and SI2={} are not valid entries.'.format(SI1,SI2) elif SI1 not in SI_prefixes: pstr = 'SI1={} is not a valid entry.'.format(SI1) elif SI1 not in SI_prefixes: pstr = 'SI2={} is not a valid entry.'.format(SI2) pstr += '\nPlease select from the valid SI prefixes:' print(pstr,SI_prefixes) return None v1=SI_powers(find(SI1)) v2=SI_powers(find(SI2)) m = 10**(v2-v1) return m  def ZZZAAAM_to_nuclide_plain_str(ZZZAAAM, include_Z=False, ZZZAAA=False, delimiter='-')  ## Description Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000*Z + 10*A + M ## Dependencies Element_Z_to_Sym() (function within the "Hunter's tools" package) ## Input • ZZZAAAM = integer equal to 10000Z + 10A + M, where M designates the metastable state (0=ground) • include_Z = Boolean denoting whether the Z number should be included in the output string (D=False) • ZZZAAA = Boolean denoting whether the input should be interpreted as a ZZZAAA value (1000Z+A) instead (D=False) • delimiter = string which will be used to separate elements of the output string (D=-) ## Output • nuc_str = string describing the input nuclide formatted as [Z]-[Symbol]-[A][m] Expand source code def ZZZAAAM_to_nuclide_plain_str(ZZZAAAM,include_Z=False,ZZZAAA=False,delimiter='-'): ''' Description: Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000\*Z + 10\*A + M Dependencies: Element_Z_to_Sym (function within the "Hunter's tools" package) Input: - ZZZAAAM = integer equal to 10000*Z + 10*A + M, where M designates the metastable state (0=ground) - include_Z = Boolean denoting whether the Z number should be included in the output string (D=False) - ZZZAAA = Boolean denoting whether the input should be interpreted as a ZZZAAA value (1000Z+A) instead (D=False) - delimiter = string which will be used to separate elements of the output string (D=-) Output: - nuc_str = string describing the input nuclide formatted as [Z]-[Symbol]-[A][m] ''' ZZZAAAM = int(ZZZAAAM) if ZZZAAA: ZZZAAAM = ZZZAAAM*10 m = ZZZAAAM % 10 A = (ZZZAAAM % 10000) // 10 Z = ZZZAAAM // 10000 symbol = Element_Z_to_Sym(Z) m_str = '' if m>0: m_str = 'm' + str(m) nuc_str = '' if include_Z: nuc_str += str(Z) + delimiter nuc_str += symbol + delimiter + str(A) + m_str return nuc_str  def absolute_difference(test_val, ref_val)  ## Description Calculate absolute difference of a reference value and an experimental test value ## Inputs • test_val = new/tested/experimental value (float ot int) • ref_val = old/established/reference value (float ot int) ## Outputs • absdiff = difference of reference and test values Expand source code def absolute_difference(test_val,ref_val): ''' Description: Calculate absolute difference of a reference value and an experimental test value Inputs: - test_val = new/tested/experimental value (float ot int) - ref_val = old/established/reference value (float ot int) Outputs: - absdiff = difference of reference and test values ''' absdiff = ref_val-test_val return absdiff  def assemble_GCR_flux(W, Z_list, nEbins=1000)  ## Description Composes a NumPy array containing GCR flux from 10 MeV/n to 1 TeV/n for each GCR ion specified ## Dependencies • import numpy as np • calc_GCR_intensity() (function within the "Hunter's tools" package) ## Inputs • W = solar modulation parameter • Z_list = list of element atomic numbers to form GCR spectra for • nEbins = number of evenly-logspaced energy bins (D=1000) ## Outputs • GCR_flux(len(Z_list),4,nEbins) = array containing flux values in (s*sr*cm^2*MeV/n)^-1 ; [emin/emid/emax/flux] Expand source code def assemble_GCR_flux(W,Z_list,nEbins=1000): ''' Description: Composes a NumPy array containing GCR flux from 10 MeV/n to 1 TeV/n for each GCR ion specified Dependencies: - import numpy as np - calc_GCR_intensity (function within the "Hunter's tools" package) Inputs: - W = solar modulation parameter - Z_list = list of element atomic numbers to form GCR spectra for - nEbins = number of evenly-logspaced energy bins (D=1000) Outputs: - GCR_flux(len(Z_list),4,nEbins) = array containing flux values in (s\*sr\*cm^2\*MeV/n)^-1 ; [emin/emid/emax/flux] ''' GCR_flux = np.zeros((len(Z_list),4,nEbins)) Emin = 10 # MeV Emax = 1e6 # MeV logEmin = np.log10(Emin) logEmax = np.log10(Emax) logdE = (logEmax-logEmin)/nEbins logE = logEmin for k in range(nEbins): GCR_flux[:,0,k] = 10**(logE) GCR_flux[:,1,k] = 10**(logE+0.5*logdE) GCR_flux[:,2,k] = 10**(logE+logdE) logE += logdE for j in range(len(Z_list)): Z = Z_list[j] for k in range(nEbins): GCR_flux[j,3,k] = calc_GCR_intensity(Z,W,GCR_flux[j,1,k]) return GCR_flux   def calc_GCR_intensity(Z, W, E)  ## Description Calculate GCR flux for a given ion at a given energy using the Matthia model https://www.sciencedirect.com/science/article/pii/S0273117712005947?via%3Dihub ## Dependencies import numpy as np ## Inputs • Z = GCR ion Z • W = solar modulation parameter • E = GCR ion energy (in MeV/n) ## Outputs • IOSI = ion flux in (s*sr*cm^2*MeV/n)^-1 Expand source code def calc_GCR_intensity(Z,W,E): ''' Description: Calculate GCR flux for a given ion at a given energy using the Matthia model https://www.sciencedirect.com/science/article/pii/S0273117712005947?via%3Dihub Dependencies: import numpy as np Inputs: - Z = GCR ion Z - W = solar modulation parameter - E = GCR ion energy (in MeV/n) Outputs: - IOSI = ion flux in (s\*sr\*cm^2\*MeV/n)^-1 ''' if Z<1 or Z>28 or W<0 or W>200: return -99 if E<10: return 0 AI = [1.0 ,4.0, 6.9, 9.0, 10.8, 12.0, 14.0, 16.0, 19.0, 20.2, 23.0, 24.3, 27.0, 28.1, 31.0, 32.1, 35.4, 39.9, 39.1, 40.1, 44.9, 47.9, 50.9, 52.0, 54.9, 55.8, 58.9, 58.7] CI = [1.85e4, 3.69e3, 19.5, 17.7, 49.2, 103.0, 36.7, 87.4, 3.19, 16.4, 4.4300, 19.300, 4.17, 13.4, 1.15, 3.060, 1.30, 2.33, 1.87, 2.17, 0.74, 2.63, 1.23, 2.12, 1.14, 9.32, 0.10, 0.48] gammaI = [2.74, 2.77, 2.82, 3.05, 2.96, 2.76, 2.89, 2.70, 2.82, 2.76, 2.84, 2.70, 2.77, 2.66, 2.89, 2.71, 3.00, 2.93, 3.05, 2.77, 2.97, 2.99, 2.94, 2.89, 2.74, 2.63, 2.63, 2.63] alphaI = [2.85, 3.12, 3.41, 4.30, 3.93, 3.18, 3.77, 3.11, 4.05, 3.11, 3.14, 3.65, 3.46, 3.00, 4.04, 3.30, 4.40, 4.33, 4.49, 2.93, 3.78, 3.79, 3.50, 3.28, 3.29, 3.01, 4.25, 3.52] P = [0.02,4.7] i = int(Z-1) E0S = 938.0 # rest mass in MeV/n if Z>1: E0S = 939.0 # rest mass in MeV/n E0SS = E0S/1000 # rest mass in GeV/n ES = E/1000 # energy in GeV/n RigS = (AI[i]/Z*np.sqrt(ES*(ES+2*E0SS))) #rigidity in GV betaS2 = (np.sqrt(ES*(ES+2.*E0SS))/(ES+E0SS)) #convert kinetic energy per nucleon to beta=v/c R0S = (0.37+0.0003*(W**1.45)) DELTAI = (P[1]+P[0]*W) PHII = CI[i]*(betaS2**alphaI[i])/(RigS**gammaI[i]) PHII = PHII*((RigS/(RigS+R0S))**DELTAI) IOSI = 0.0001*PHII*AI[i]/Z*0.001/betaS2 return IOSI  def chi_squared(y, y_fit, num_fit_params=0)  ## Description Calculate chi^2 (chi-squared) value between two sets of data, an experimental "y" and fitted "y_fit" ## Dependencies from scipy.stats import chisquare ## Inputs • y = list/array of y values (experimental) • y_fit = list/array of fitted y values to be compared against y • num_deg_freedom = number of degrees of freedom (DoF) in fit function (number of optimized parameters) (D=0) ## Outputs • chi_squared() = calculated chi-squared value • reduced_chi_squared = calculated reduced chi-squared value (chi^2 / DoF) Expand source code def chi_squared(y,y_fit,num_fit_params=0): ''' Description: Calculate chi^2 (chi-squared) value between two sets of data, an experimental "y" and fitted "y_fit" Dependencies: from scipy.stats import chisquare Inputs: - y = list/array of y values (experimental) - y_fit = list/array of fitted y values to be compared against y - num_deg_freedom = number of degrees of freedom (DoF) in fit function (number of optimized parameters) (D=0) Outputs: - chi_squared = calculated chi-squared value - reduced_chi_squared = calculated reduced chi-squared value (chi^2 / DoF) ''' # This normalization shouldn't be necessary, but a past build of scipy broke the chisquare function from working without it y_fit = y_fit*sum(y)/sum(y_fit) chi2,p = chisquare(y,f_exp=y_fit,ddof=num_fit_params) ndf = len(y) - num_fit_params # Hand calc #O_k = y # observed, from measurement #E_k = y_fit # expected, from some distribution e.g. Gaussian #chi2 = np.sum( ((O_k-E_k)**2)/E_k ) #num_deg_freedom = len(O_k) - num_fit_params #red_chi2 = chi2/num_deg_freedom return chi2, p, ndf  def circ_solid_angle(pp, a, b, r)  ## Description Calculates the solid angle subtended on (circular-faced) detector from point pp [xp,yp] to two points on a detector a [xa, ya] and b [xb, yb] ## Dependencies import numpy as np ## Inputs • pp = point of emission (radiation source), N-dimensional, list of floats/ints • a = point on edge of circular detector face, N-dimensional, list of floats/ints • b = point on edge of circular detector face opposite a, N-dimensional, list of floats/ints • r = radius of the detector (in the same units as the coordinates) ## Outputs • omega = solid angle of source at pp as seen by a detector whose circular face has a diameter with ends at a and b Expand source code def circ_solid_angle(pp,a,b,r): ''' Description: Calculates the solid angle subtended on (circular-faced) detector from point pp [xp,yp] to two points on a detector a [xa, ya] and b [xb, yb] Dependencies: import numpy as np Inputs: - pp = point of emission (radiation source), N-dimensional, list of floats/ints - a = point on edge of circular detector face, N-dimensional, list of floats/ints - b = point on edge of circular detector face opposite a, N-dimensional, list of floats/ints - r = radius of the detector (in the same units as the coordinates) Outputs: - omega = solid angle of source at pp as seen by a detector whose circular face has a diameter with ends at a and b ''' d_pa = dist(pp,a) d_pb = dist(pp,b) theta = 0.5*np.arccos((d_pa**2 + d_pb**2 - (2*r)**2)/(2*d_pa*d_pb)) omega = 2*np.pi*(1-np.cos(theta)) return omega  def circumcenter(xvals, yvals)  ## Description Calculates circumcenter between three 2-dimensional points ## Inputs • xvals = list of 3 x-coordinates • yvals = list of 3 y-coordinates ## Outputs • x = x-coordinate of circumcenter • y = y-coordinate of circumcenter Expand source code def circumcenter(xvals,yvals): ''' Description: Calculates circumcenter between three 2-dimensional points Inputs: - xvals = list of 3 x-coordinates - yvals = list of 3 y-coordinates Outputs: - x = x-coordinate of circumcenter - y = y-coordinate of circumcenter ''' x1, x2, x3, y1, y2, y3 = xvals[0], xvals[1], xvals[2], yvals[0], yvals[1], yvals[2] A = 0.5*((x2-x1)*(y3-y2)-(y2-y1)*(x3-x2)) if ( A == 0 ): print('Failed: points are either collinear or not distinct') return 0 xnum = ((y3 - y1)*(y2 - y1)*(y3 - y2)) - ((x2**2 - x1**2)*(y3 - y2)) + ((x3**2 - x2**2)*(y2 - y1)) x = xnum/(-4*A) y = (-1*(x2 - x1)/(y2 - y1))*(x-0.5*(x1 + x2)) + 0.5*(y1 + y2) return x, y  def circumradius(xvals, yvals)  ## Description Calculates circumradius between three 2-dimensional points ## Inputs • xvals = list of 3 x-coordinates • yvals = list of 3 y-coordinates ## Outputs • R = circumradius of the three points Expand source code def circumradius(xvals,yvals): ''' Description: Calculates circumradius between three 2-dimensional points Inputs: - xvals = list of 3 x-coordinates - yvals = list of 3 y-coordinates Outputs: - R = circumradius of the three points ''' x1, x2, x3, y1, y2, y3 = xvals[0], xvals[1], xvals[2], yvals[0], yvals[1], yvals[2] den = 2*((x2-x1)*(y3-y2)-(y2-y1)*(x3-x2)) num = ( (((x2-x1)**2) + ((y2-y1)**2)) * (((x3-x2)**2)+((y3-y2)**2)) * (((x1-x3)**2)+((y1-y3)**2)) )**(0.5) if ( den == 0 ): print('Failed: points are either collinear or not distinct') return 0 R = abs(num/den) return R  def colors_list_10(di)  Default cycle as of matplotlib v3.1.1 See: https://matplotlib.org/3.1.1/users/dflt_style_changes.html Expand source code def colors_list_10(di): ''' Default cycle as of matplotlib v3.1.1 See: https://matplotlib.org/3.1.1/users/dflt_style_changes.html ''' colors_list = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'] return colors_list[di%10]  def colors_list_12(di)  Colorbrewer qualitative color cycle scheme 6 (expanded) See: https://colorbrewer2.org/#type=qualitative&scheme=Set1&n=6 Expand source code def colors_list_12(di): ''' Colorbrewer qualitative color cycle scheme 6 (expanded) See: https://colorbrewer2.org/#type=qualitative&scheme=Set1&n=6 ''' colors_list = ['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#a65628','#8dd3c7','#bebada','#fb8072','#80b1d3','#fdb462','#b3de69'] return colors_list[di%12]  def colors_list_20(di)  List of 20 'distinct' colors See: https://sashamaps.net/docs/resources/20-colors/ Expand source code def colors_list_20(di): ''' List of 20 'distinct' colors See: https://sashamaps.net/docs/resources/20-colors/ ''' colors_list = ['#e6194B', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4', '#469990', '#dcbeff', '#9A6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#a9a9a9'] return colors_list[di%20]  def colors_list_6(di)  Colorbrewer qualitative color cycle scheme 6 (modified) See: https://colorbrewer2.org/#type=qualitative&scheme=Set1&n=6 Expand source code def colors_list_6(di): ''' Colorbrewer qualitative color cycle scheme 6 (modified) See: https://colorbrewer2.org/#type=qualitative&scheme=Set1&n=6 ''' #colors_list = ['b','g','r','c','m','y'] # Python 3 old default #colors_list = ['#b2182b','#d6604d','#f4a582','#92c5de','#4393c3','#2166ac'] # blue to red cold #colors_list = ['#d73027','#f46d43','#fdae61','#abd9e9','#74add1','#4575b4'] # blue to red warm #colors_list = ['#762a83','#9970ab','#c2a5cf','#a6dba0','#5aae61','#1b7837'] # purple to green #colors_list = ['#40004b','#762a83','#9970ab','#5aae61','#1b7837','#00441b'] # purple to green darker #colors_list = ["#afa83a","#7f63b8","#56ae6c","#b84c7d","#ac863f","#b94d3d"] # iWantHue 1 #colors_list = ['#1b9e77','#d95f02','#7570b3','#e7298a','#66a61e','#e6ab02'] # qualitative dark 1 colors_list = ['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#a65628'] # qualitative dark 2 return colors_list[di%6]  def colors_list_64(di)  List of 64 'distinct' colors See: https://stackoverflow.com/a/20298027 Expand source code def colors_list_64(di): ''' List of 64 'distinct' colors See: https://stackoverflow.com/a/20298027 ''' colors_list = ['#00FF00','#0000FF','#FF0000','#01FFFE','#FFA6FE','#FFDB66','#006401','#010067','#95003A','#007DB5','#FF00F6','#FFEEE8','#774D00','#90FB92','#0076FF','#D5FF00','#FF937E','#6A826C','#FF029D','#FE8900','#7A4782','#7E2DD2','#85A900','#FF0056','#A42400','#00AE7E','#683D3B','#BDC6FF','#263400','#BDD393','#00B917','#9E008E','#001544','#C28C9F','#FF74A3','#01D0FF','#004754','#E56FFE','#788231','#0E4CA1','#91D0CB','#BE9970','#968AE8','#BB8800','#43002C','#DEFF74','#00FFC6','#FFE502','#620E00','#008F9C','#98FF52','#7544B1','#B500FF','#00FF78','#FF6E41','#005F39','#6B6882','#5FAD4E','#A75740','#A5FFD2','#FFB167','#009BFF','#E85EBE','#000000'] return colors_list[di%64]  def curvature(x_data, y_data)  ## Description Calculates curvature for all interior points on a curve whose coordinates are provided ## Dependencies circumradius() (function within the "Hunter's tools" package) ## Inputs • x_data = list of N x-coordinates • y_data = list of N y-coordinates ## Outputs • curvature() = list of N-2 curvature values Expand source code def curvature(x_data,y_data): ''' Description: Calculates curvature for all interior points on a curve whose coordinates are provided Dependencies: circumradius (function within the "Hunter's tools" package) Inputs: - x_data = list of N x-coordinates - y_data = list of N y-coordinates Outputs: - curvature = list of N-2 curvature values ''' curvature = [] for i in range(1,len(x_data)-1): R = circumradius(x_data[i-1:i+2],y_data[i-1:i+2]) if ( R == 0 ): print('Failed: points are either collinear or not distinct') return 0 curvature.append(1/R) return curvature  def dist(a, b)  ## Description Calculate the distance between two N-dimensional Cartesian points a and b ## Dependencies import numpy as np ## Inputs • a = length N list containing coordinates of the first point, a (ex. [ax,ay] or [ax,ay,az]) • b = length N list containing coordinates of the second point, b (same format as a) ## Outputs • d = Cartesian distance between points a and b Expand source code def dist(a,b): ''' Description: Calculate the distance between two N-dimensional Cartesian points a and b Dependencies: import numpy as np Inputs: - a = length N list containing coordinates of the first point, a (ex. [ax,ay] or [ax,ay,az]) - b = length N list containing coordinates of the second point, b (same format as a) Outputs: - d = Cartesian distance between points a and b ''' if len(a) != len(b): print('a ({}) and b ({}) are not of the same dimension'.format(str(len(a)),str(len(b)))) return 0 d = 0 for i in range(len(a)): d += (a[i]-b[i])**2 d = np.sqrt(d) return d  def energy_resolution_of_peak(y, x)  ## Description Provided a spectrum of counts (or equivalent) and it's bin centers or edges, and optionally the range of bins to evaluate, determine the energy resolution FWHM/H0 (also sigma/H0) of peak with average value H0. ## Inputs • y = list/array of y values (length N) • x = list/array of x bin centers (length N) or bin edges (length N+1) corresponding to y • peak_guess_location = single value (float/int) denoting an initial guess of the peak's x location • peak_search_range = length-2 list of x values (not indices) between which the peak will be searched for ## Notes At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to amplitude/height, x position/centering, and width. ## Outputs • y_fit = list/array of evaluated y values using the optimally found fit parameters • dist_info = dictionary object containing the distribution short_name and full_name; optimized fit parameters a, mu, and sigma; calculated FWHM, r2/r_squared() R^2, and chi2/chi_squared() values; Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str Expand source code def energy_resolution_of_peak(y,x,): ''' Description: Provided a spectrum of counts (or equivalent) and it's bin centers or edges, and optionally the range of bins to evaluate, determine the energy resolution FWHM/H0 (also sigma/H0) of peak with average value H0. Inputs: - y = list/array of y values (length N) - x = list/array of x bin centers (length N) or bin edges (length N+1) corresponding to y - peak_guess_location = single value (float/int) denoting an initial guess of the peak's x location - peak_search_range = length-2 list of x values (not indices) between which the peak will be searched for Notes: At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to amplitude/height, x position/centering, and width. Outputs: - y_fit = list/array of evaluated y values using the optimally found fit parameters - dist_info = dictionary object containing the distribution short_name and full_name; optimized fit parameters a, mu, and sigma; calculated FWHM, r2/r_squared R^2, and chi2/chi_squared values; Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str ''' return None   def eval_distribution(x, dist_name='gauss', a=1, mu=0, sigma=0.3)  ## Description Evaluates a provided array of x values (or single value) for a desired distribution provided its defining parameters. ## Dependencies from munch import * ## Inputs • x = list/array of x values or single x value • dist_name = string denoting the distribution used (D='Gaussian'), options include: ['gauss','normal','Logistic','sech'], Read more here on the Gaussian/normal, Logistic, and Hyperbolic secant distributions. • a = distribution amplitude/height parameter • mu = distribution mean/center parameter • sigma = distribution width parameter ## Notes At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to amplitude/height, x position/centering, and width. ## Outputs • y_eval = list/array of evaluated y values or single y value • dist_info = dictionary object containing the distribution short_name and full_name; assigned fit parameters a, mu, and sigma; Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str Expand source code def eval_distribution(x,dist_name='gauss',a=1,mu=0,sigma=0.3): ''' Description: Evaluates a provided array of x values (or single value) for a desired distribution provided its defining parameters. Dependencies: from munch import * Inputs: - x = list/array of x values or single x value - dist_name = string denoting the distribution used (D='Gaussian'), options include: ['gauss','normal','Logistic','sech'], Read more here on the [Gaussian/normal](https://en.wikipedia.org/wiki/Normal_distribution), [Logistic](https://en.wikipedia.org/wiki/Logistic_distribution), and [Hyperbolic secant](https://en.wikipedia.org/wiki/Hyperbolic_secant_distribution) distributions. - a = distribution amplitude/height parameter - mu = distribution mean/center parameter - sigma = distribution width parameter Notes: At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to amplitude/height, x position/centering, and width. Outputs: - y_eval = list/array of evaluated y values or single y value - dist_info = dictionary object containing the distribution short_name and full_name; assigned fit parameters a, mu, and sigma; Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str ''' dist_names_list = ['gauss','normal','Logistic','sech'] dist_name = dist_name.lower() if dist_name not in dist_names_list: print('Selected distribution name, ',dist_name,' is not in the list of allowed distribution names: ',dist_names_list,'\n exiting function... Please pick from this list and try again.') return None if type(x) is list: x = np.array(x) if dist_name=='gauss' or dist_name=='normal': y_eval = a*np.exp(-(x-mu)**2/(2*sigma**2)) fcn_py_str = 'f = a*np.exp(-(x-mu)**2/(2*sigma**2))' fcn_tex_str = r'f(x) = A$\cdot$exp$(\frac{-(x-\mu)^2}{2\sigma^2})$' if dist_name=='gauss': dist_full_name = 'Gaussian' else: dist_full_name = 'Normal' elif dist_name=='logistic': y_eval = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))**2 fcn_py_str = 'f = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))**2' fcn_tex_str = r'f(x) =$\frac{A}{4\sigma}$sech$^2(\frac{x-\mu}{2\sigma})$' dist_full_name = 'Logistic' else: #if dist_name=='sech' y_eval = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma))) fcn_py_str = 'f = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))' fcn_tex_str = r'f(x) =$\frac{A}{4\sigma}$sech$(\frac{x-\mu}{2\sigma})$' dist_full_name = 'Hyperbolic secant' dist_info = {'short_name':dist_name, 'full_name':dist_full_name, 'a':a, 'mu':mu, 'sigma':sigma, 'fcn_py_str':fcn_py_str, 'fcn_tex_str':fcn_tex_str} try: dist_info = Munch(dist_info) except: print("Munch failed. Returned object is a conventional dictionary rather than a munch object.") return y_eval, dist_info  def fancy_3D_plot(xdata_lists, ydata_lists, zdata_lists, plot_styles=None, data_labels=[], figi=1, title_str='', x_label_str='x-axis', y_label_str='y-axis', z_label_str='z-axis', x_limits=[], y_limits=[], z_limits=[], use_mpl_limits=True, x_scale='linear', y_scale='linear', z_scale='linear', OoB_z_handling='NaN', fig_width_inch=9.5, fig_height_inch=6.5, title_fs=16, axis_fs=14, f_family='sans-serif', f_style='normal', f_variant='normal', f_weight='normal', fig=None, ax=None, spnrows=1, spncols=1, spindex=1, man_sp_placement=False, spx0=0.1, spy0=0.1, sph0=0.4, spw0=0.4, color='#FDFEFC', cmap='viridis', facecolors=None, depthshade=True, linestyle='-', linewidth=1, marker='.', markersize=5, markerfacecolor=None, markeredgecolor=None, markeredgewidth=None, rstride=1, cstride=1, rcount=50, ccount=50, alpha=None, x_meaning='mid', y_meaning='mid', cbar_fs=None, cbar_size=5, cbar_pad=0.1, legend_position='outside bottom', legend_anchor=None, legend_ncol=1, legend_alpha=None)  ## Description Generate a 3D plot containing an arbitrary number of datasets. The z-axis of each of the datasets can either be a 1-D list (describing scatter points or a line) or a 2-D NumPy array (describing a surface); the x and y axes must be 1-D and match the correct dimension of the z-axis dataset. ## Dependencies • import numpy as np • import matplotlib.pyplot as plt • from mpl_toolkits.mplot3d import Axes3D • import matplotlib.ticker as mticker • from mpl_toolkits.mplot3d.axis3d import Axis • import matplotlib.projections as proj • from matplotlib.colors import colorConverter ## Inputs (Required) • xdata_lists = a list containing lists/arrays of 1-D x data (or single list of xdata applied to all zdata in z_data_lists) • ydata_lists = a list containing lists/arrays of 1-D y data (or single list of ydata applied to all zdata in z_data_lists) • zdata_lists = a list containing lists/arrays of z datasets (or a single list/array), • individual z datasets can be provided in either of two different formats: • 1) 1-D lists (of the same dimension of the corresponding x and y lists) • 2) 2-D NumPy arrays whose whidth and height match the dimensions of the x and y lists. ## Inputs (Optional, basic, generic) • plot_styles = list of (or individual) strings denoting the plot style to be used for each dataset. Options include: • 1-D ['line','scatter','trisurface'] (D='line') • 2-D ['surface','wireframe','trisurface','contour','filledcontour'] (D='trisurface') • 2-D_colormaps ['map_pcolormesh','map_filledcontour','map_contour'] • data_labels = a list of strings to be used as data labels in the legend (D=[], no legend generated) (labels do not work for contours) • figi = figure index (D=1) • title_str = string to be used as the title of the plot (D='') • x_label_str = string to be used as x-axis title (D='x-axis') • y_label_str = string to be used as y-axis title (D='y-axis') • z_label_str = string to be used as z-axis title (D='z-axis') • x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=auto-calculated based on x_data_lists) • y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=auto-calculated based on y_data_lists) • z_limits = length 2 list specifying minimum and maximum z-axis bounds [zmin,zmax] (D=auto-calculated based on z_data_lists) • use_mpl_limits = boolean specifying if Matplotlib default (True) or specially calculated (False) log-scale axis limits are used when they aren't specified (D=True) • x_scale = x-axis scale, either "linear" or "log" • y_scale = y-axis scale, either "linear" or "log" • z_scale = z-axis scale, either "linear" or "log" • OoB_z_handling = string denoting how z values outside of z_limits, if provided, will be handled. (D='NaN', other option are 'limits' and 'None') By default, out of bounds (OoB) values are replaced with NaN. If this is set to 'limits', OoB values are set equal to the lower/upper limit provided. If 'None', no values are replaced (this may cause errors with 3-D plots). • fig_width_inch = figure width in inches (D=9.5) • fig_height_inch = figure height in inches (D=6.5) • title_fs = title font size (D=16) • axis_fs = axis label font size (D=14) • f_family = string specifying font family (D='sans-serif'); options include: ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] • f_style = string specifying font style (D='normal'); options include: ['normal', 'italic', 'oblique'] • f_variant = string specifying font variant (D='normal'); options include: ['normal', 'small-caps'] • f_weight = string specifying font weight (D='normal'); options include: ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] ## Inputs (Optional, basic, 3D plot type specific) The below options are only applicable to specific plot_styles; this is denoted by the leftmost column. • L P S W T C F (L='line', P='scatter'(points), S='surface', W='wireframe', T='trisurface', C='contour', F='filledcontour'; 'o' indicates options used by each plot type) • o o o o o o o | alpha = list of (or individual) int/float of the alpha/opacity of each point/curve/surface/etc. (D=None) • o o o o o o o | color = list of color strings to be used of same length as z_data_lists (or individual color string) (D=Matplotlib default color cycle) • . . o . o o o | cmap = list of colormaps to be used of same length as z_data_lists, this will overwrite 'color' (or individual colormap) (D=None) • o . . o . o . | linestyle = list of (or individual) strings denoting linestyle: '', '-', '--', '-.', or ':' (D='-') • o . . o . o . | linewidth = list of (or individual) int/float of the width of line (D=1) • o o . . . . . | marker = list of (or individual) marker styles (D='.') For all options, see: https://matplotlib.org/3.1.0/api/markers_api.html • o o . . . . . | markersize = list of (or individual) int/float of marker size (D=5) • o o . . . . . | markerfacecolor = list of (or individual) marker face colors (D=None, use value of 'color') • o o . . . . . | markeredgecolor = list of (or individual) marker edge colors (D=None, use value of 'color') • o o . . . . . | markeredgewidth = list of (or individual) int/float of marker edge widths (D=None) • . o . . . . . | depthshade = list of (or individual) booleans to enable/disable marker shading for appearance of depth (D=True) • . . o o . . . | rstride = [DEPRECATED] list of (or individual) int/float of array row strides (D=1) • . . o o . . . | cstride = [DEPRECATED] list of (or individual) int/float of array column strides (D=1) • . . o o . . . | rcount = list of (or individual) int/float of maximum number of rows used (D=50) • . . o o . . . | ccount = list of (or individual) int/float of maximum number of columns used (D=50) • . . o . . . . | facecolors = list of (or individual) array mapping colors to each facet of the zdata (D=None) overwrites cmap ## Inputs (Optional, 2D colormap type specific) • x_meaning = string specifying if x values describe x min, x max, or central x value for each corresponding z (D='mid'); options include: ['min','max','mid'] (think of like bin min/max/mid) • y_meaning = string specifying if y values describe y min, y max, or central y value for each corresponding z (D='mid'); options include: ['min','max','mid'] • cbar_fs = color bar label font size (D=axis_fs (D=14)) • cbar_size = color bar size expressed as an integer/float between 0 and 100 (D=5) (think of as percentage width) • cbar_pad = color bar padding (should be between 0 and 1) (D=0.1) ## Inputs (Optional, advanced) Subplots • fig = figure handles from existing figure to draw on (D=None, fig=None should always be used for initial subplot unless a figure canvas has already been generated) • ax = axis handles from an existing figure to draw on (D=None, ax=None should always be used for initial subplot) • spnrows = number of rows in final subplot (D=1) • spncols = number of columns in final subplot (D=1) • spindex = index of current subplot (between 1 and spnrows*spncols) (D=1) • man_sp_placement = logical variable controlling manual sizing/placement of subplots using below variables (D=False, use automatic sizing) • spx0 = distance from canvas left edge where this plotting area should begin (D=0.1), generally a number around 0~1 • spy0 = distance from canvas bottom edge where this plotting area should begin (D=0.1), generally a number around 0~1 • spw0 = width of this plotting area on the canvas (D=0.4), generally a number around 0~1 • sph0 = height of this plotting area on the canvas (D=0.4), generally a number around 0~1 Legend settings • legend_position = one of the default matplotlib legend position strings ('best','upper right','lower center','lower left',etc.) to place the legend inside the plot or 'outside right' or 'outside bottom' to place the legend outside of the plot area (D='outside bottom', if legend is to be used) • legend_anchor = legend anchor position (x=left-right position, y=bottom-top position) only used when legend position is set to one of the "outside" options (D=None which becomes (1.0,0.75) if position is 'outside right' or (0.5,-0.17) if position is 'outside bottom') Note that only one coordinate usually should be adjusted. If using an 'outside right' legend, only the y-coordinate needs to be manipulated to scoot the legend up/down. Likewise, for 'outside bottom' legends only the x-coordinate needs adjusting for tuning left/right position • legend_ncol = number of columns in legend (D=1 in all cases except for legend_position='outside bottom' where D=len(ydata_lists)) • legend_alpha = alpha of legend background (D=None, auto determined by matplotlib) Expand source code def fancy_3D_plot( # Required data xdata_lists,ydata_lists,zdata_lists, # Optional data plot_styles=None, data_labels=[], # Standard basic settings (optional) figi=1, title_str='', x_label_str='x-axis', y_label_str='y-axis', z_label_str='z-axis', x_limits=[], y_limits=[], z_limits=[], use_mpl_limits=True, x_scale='linear', y_scale='linear', z_scale='linear', OoB_z_handling = 'NaN', fig_width_inch=9.5, fig_height_inch=6.5, title_fs=16, axis_fs=14, f_family='sans-serif',f_style='normal',f_variant='normal',f_weight='normal', fig=None, ax=None, spnrows=1, spncols=1, spindex=1, man_sp_placement = False, spx0=0.1, spy0=0.1, sph0=0.4, spw0=0.4, color='#FDFEFC', cmap='viridis', facecolors=None, depthshade=True, linestyle='-', linewidth=1, marker='.', markersize=5, markerfacecolor=None, markeredgecolor=None, markeredgewidth=None, rstride=1,cstride=1,rcount=50,ccount=50, alpha=None, # Color map options x_meaning='mid',y_meaning='mid', cbar_fs=None,cbar_size=5,cbar_pad=0.1, # Legend settings legend_position='outside bottom', legend_anchor=None, legend_ncol=1, legend_alpha=None, ): ''' Description: Generate a 3D plot containing an arbitrary number of datasets. The z-axis of each of the datasets can either be a 1-D list (describing scatter points or a line) or a 2-D NumPy array (describing a surface); the x and y axes must be 1-D and match the correct dimension of the z-axis dataset. Dependencies: - import numpy as np - import matplotlib.pyplot as plt - from mpl_toolkits.mplot3d import Axes3D - import matplotlib.ticker as mticker - from mpl_toolkits.mplot3d.axis3d import Axis - import matplotlib.projections as proj - from matplotlib.colors import colorConverter Inputs: (Required) - xdata_lists = a list containing lists/arrays of 1-D x data (or single list of xdata applied to all zdata in z_data_lists) - ydata_lists = a list containing lists/arrays of 1-D y data (or single list of ydata applied to all zdata in z_data_lists) - zdata_lists = a list containing lists/arrays of z datasets (or a single list/array), - individual z datasets can be provided in either of two different formats: - 1) 1-D lists (of the same dimension of the corresponding x and y lists) - 2) 2-D NumPy arrays whose whidth and height match the dimensions of the x and y lists. Inputs: (Optional, basic, generic) - plot_styles = list of (or individual) strings denoting the plot style to be used for each dataset. Options include: + 1-D ['line','scatter','trisurface'] (D='line') + 2-D ['surface','wireframe','trisurface','contour','filledcontour'] (D='trisurface') + 2-D_colormaps ['map_pcolormesh','map_filledcontour','map_contour'] - data_labels = a list of strings to be used as data labels in the legend (D=[], no legend generated) (labels do not work for contours) - figi = figure index (D=1) - title_str = string to be used as the title of the plot (D='') - x_label_str = string to be used as x-axis title (D='x-axis') - y_label_str = string to be used as y-axis title (D='y-axis') - z_label_str = string to be used as z-axis title (D='z-axis') - x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=auto-calculated based on x_data_lists) - y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=auto-calculated based on y_data_lists) - z_limits = length 2 list specifying minimum and maximum z-axis bounds [zmin,zmax] (D=auto-calculated based on z_data_lists) - use_mpl_limits = boolean specifying if Matplotlib default (True) or specially calculated (False) log-scale axis limits are used when they aren't specified (D=True) - x_scale = x-axis scale, either "linear" or "log" - y_scale = y-axis scale, either "linear" or "log" - z_scale = z-axis scale, either "linear" or "log" - OoB_z_handling = string denoting how z values outside of z_limits, if provided, will be handled. (D='NaN', other option are 'limits' and 'None') By default, out of bounds (OoB) values are replaced with NaN. If this is set to 'limits', OoB values are set equal to the lower/upper limit provided. If 'None', no values are replaced (this may cause errors with 3-D plots). - fig_width_inch = figure width in inches (D=9.5) - fig_height_inch = figure height in inches (D=6.5) - title_fs = title font size (D=16) - axis_fs = axis label font size (D=14) - f_family = string specifying font family (D='sans-serif'); options include: ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] - f_style = string specifying font style (D='normal'); options include: ['normal', 'italic', 'oblique'] - f_variant = string specifying font variant (D='normal'); options include: ['normal', 'small-caps'] - f_weight = string specifying font weight (D='normal'); options include: ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] Inputs: (Optional, basic, 3D plot type specific) The below options are only applicable to specific plot_styles; this is denoted by the leftmost column. - L P S W T C F (L='line', P='scatter'(points), S='surface', W='wireframe', T='trisurface', C='contour', F='filledcontour'; 'o' indicates options used by each plot type) - o o o o o o o | alpha = list of (or individual) int/float of the alpha/opacity of each point/curve/surface/etc. (D=None) - o o o o o o o | color = list of color strings to be used of same length as z_data_lists (or individual color string) (D=Matplotlib default color cycle) - . . o . o o o | cmap = list of colormaps to be used of same length as z_data_lists, this will overwrite 'color' (or individual colormap) (D=None) - o . . o . o . | linestyle = list of (or individual) strings denoting linestyle: '', '-', '--', '-.', or ':' (D='-') - o . . o . o . | linewidth = list of (or individual) int/float of the width of line (D=1) - o o . . . . . | marker = list of (or individual) marker styles (D='.') For all options, see: https://matplotlib.org/3.1.0/api/markers_api.html - o o . . . . . | markersize = list of (or individual) int/float of marker size (D=5) - o o . . . . . | markerfacecolor = list of (or individual) marker face colors (D=None, use value of 'color') - o o . . . . . | markeredgecolor = list of (or individual) marker edge colors (D=None, use value of 'color') - o o . . . . . | markeredgewidth = list of (or individual) int/float of marker edge widths (D=None) - . o . . . . . | depthshade = list of (or individual) booleans to enable/disable marker shading for appearance of depth (D=True) - . . o o . . . | rstride = [DEPRECATED] list of (or individual) int/float of array row strides (D=1) - . . o o . . . | cstride = [DEPRECATED] list of (or individual) int/float of array column strides (D=1) - . . o o . . . | rcount = list of (or individual) int/float of maximum number of rows used (D=50) - . . o o . . . | ccount = list of (or individual) int/float of maximum number of columns used (D=50) - . . o . . . . | facecolors = list of (or individual) array mapping colors to each facet of the zdata (D=None) overwrites cmap Inputs: (Optional, 2D colormap type specific) - x_meaning = string specifying if x values describe x min, x max, or central x value for each corresponding z (D='mid'); options include: ['min','max','mid'] (think of like bin min/max/mid) - y_meaning = string specifying if y values describe y min, y max, or central y value for each corresponding z (D='mid'); options include: ['min','max','mid'] - cbar_fs = color bar label font size (D=axis_fs (D=14)) - cbar_size = color bar size expressed as an integer/float between 0 and 100 (D=5) (think of as percentage width) - cbar_pad = color bar padding (should be between 0 and 1) (D=0.1) Inputs: (Optional, advanced) Subplots - fig = figure handles from existing figure to draw on (D=None, fig=None should always be used for initial subplot unless a figure canvas has already been generated) - ax = axis handles from an existing figure to draw on (D=None, ax=None should always be used for initial subplot) - spnrows = number of rows in final subplot (D=1) - spncols = number of columns in final subplot (D=1) - spindex = index of current subplot (between 1 and spnrows*spncols) (D=1) - man_sp_placement = logical variable controlling manual sizing/placement of subplots using below variables (D=False, use automatic sizing) - spx0 = distance from canvas left edge where this plotting area should begin (D=0.1), generally a number around 0~1 - spy0 = distance from canvas bottom edge where this plotting area should begin (D=0.1), generally a number around 0~1 - spw0 = width of this plotting area on the canvas (D=0.4), generally a number around 0~1 - sph0 = height of this plotting area on the canvas (D=0.4), generally a number around 0~1 Legend settings - legend_position = one of the default matplotlib legend position strings ('best','upper right','lower center','lower left',etc.) to place the legend inside the plot or 'outside right' or 'outside bottom' to place the legend outside of the plot area (D='outside bottom', if legend is to be used) - legend_anchor = legend anchor position (x=left-right position, y=bottom-top position) only used when legend position is set to one of the "outside" options (D=None which becomes (1.0,0.75) if position is 'outside right' or (0.5,-0.17) if position is 'outside bottom') Note that only one coordinate usually should be adjusted. If using an 'outside right' legend, only the y-coordinate needs to be manipulated to scoot the legend up/down. Likewise, for 'outside bottom' legends only the x-coordinate needs adjusting for tuning left/right position - legend_ncol = number of columns in legend (D=1 in all cases except for legend_position='outside bottom' where D=len(ydata_lists)) - legend_alpha = alpha of legend background (D=None, auto determined by matplotlib) ''' ''' Notes: Leftover string not yet used for 2D colormap specific options: The below options are only applicable to specific plot styles; this is denoted by the leftmost column. P C F (P='map_pcolormesh', C='map_contour', F='map_filledcontour'; 'o' indicates options used by each plot type) ''' # This allows for implementation of hacked in minor gridlines class axis3d_custom(Axis): # https://stackoverflow.com/questions/31684448/how-to-color-a-specific-gridline-tickline-in-3d-matplotlib-scatter-plot-figure def __init__(self, adir, v_intervalx, d_intervalx, axes, *args, **kwargs): Axis.__init__(self, adir, v_intervalx, d_intervalx, axes, *args, **kwargs) self.gridline_colors = [] def set_gridline_color(self, *gridline_info): '''Gridline_info is a tuple containing the value of the gridline to change and the color to change it to. A list of tuples may be used with the * operator.''' self.gridline_colors.extend(gridline_info) def draw(self, renderer): # filter locations here so that no extra grid lines are drawn Axis.draw(self, renderer) which_gridlines = [] if self.gridline_colors: locmin, locmax = self.get_view_interval() if locmin > locmax: locmin, locmax = locmax, locmin # Rudimentary clipping majorLocs = [loc for loc in self.major.locator() if locmin <= loc <= locmax] for i, val in enumerate(majorLocs): for colored_val, color in self.gridline_colors: if val == colored_val: which_gridlines.append((i, color)) colors = self.gridlines.get_colors() for val, color in which_gridlines: colors[val] = colorConverter.to_rgba(color) self.gridlines.set_color(colors) self.gridlines.draw(renderer, project=True) class XAxis(axis3d_custom): def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.xy_dataLim.intervalx class YAxis(axis3d_custom): def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.xy_dataLim.intervaly class ZAxis(axis3d_custom): def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.zz_dataLim.intervalx class Axes3D_custom(Axes3D): """ 3D axes object. """ name = '3d_custom' def _init_axis(self): '''Init 3D axes; overrides creation of regular X/Y axes''' self.w_xaxis = XAxis('x', self.xy_viewLim.intervalx, self.xy_dataLim.intervalx, self) self.xaxis = self.w_xaxis self.w_yaxis = YAxis('y', self.xy_viewLim.intervaly, self.xy_dataLim.intervaly, self) self.yaxis = self.w_yaxis self.w_zaxis = ZAxis('z', self.zz_viewLim.intervalx, self.zz_dataLim.intervalx, self) self.zaxis = self.w_zaxis for ax in self.xaxis, self.yaxis, self.zaxis: ax.init3d() proj.projection_registry.register(Axes3D_custom) use_custom_3d_axis_class = False # custom axes broken in newer version of matplotlib? valid_plot_styles = ['line','scatter','surface','wireframe','trisurface','contour','filledcontour','map_pcolormesh','map_filledcontour','map_contour'] pls_by_dims = [['line','scatter','trisurface'],['surface','wireframe','trisurface','contour','filledcontour','map_pcolormesh','map_filledcontour','map_contour']] pls_maps = ['map_pcolormesh','map_filledcontour','map_contour'] if data_labels: include_legend = True # used to toggle legend on/off else: include_legend = False single_dataset = False # Assume multiple datasets entered, but this can be tested to see if it is the case or not. if (not xdata_lists) and (not ydata_lists): print('Warning: Both xdata and ydata lists are empty (figure index = {}, titled "{}")'.format(figi,title_str)) single_dataset = True include_legend = False xdata_lists = [[]] ydata_lists = [[]] elif (not xdata_lists): print('Warning: xdata list is empty (figure index = {}, titled "{}")'.format(figi,title_str)) elif (not ydata_lists): print('Warning: ydata list is empty (figure index = {}, titled "{}")'.format(figi,title_str)) # First, determine the number of datasets which have been provided # This is solely determined from the z axis entries. # A z list consisting of only floats/ints is interpreted only as being coordinates to corresponding # x and y lists of the same length. # If wanting to generate a plot with one axis whose values are unchanging in each dataset, please make that x or y. # The Z list can either be an individual an item or a list of supported structures. # Acceptable entries for Z are: # - list of values to plot a line or scatter # - 2D array (numpy, not a list of lists) whose shape matches the lengths of corresponding 1D arrays of x and y data if isinstance(zdata_lists, list): if (all(isinstance(el, (int, float)) for el in zdata_lists)): # just a single list of z coordinates provided ndatasets = 1 zdata_lists = [zdata_lists] elif len(zdata_lists)==1: # provided just a single dataset which could either be a list of values or 2D numpy array ndatasets = 1 else: # provided a number of datasets which could be composed of lists of values and/or 2D numpy arrays ndatasets = len(zdata_lists) elif isinstance(zdata_lists, np.ndarray): if len(np.shape(zdata_lists)) == 1: # single 1D array ndatasets = 1 zdata_lists = zdata_lists.tolist() elif len(np.shape(zdata_lists)) == 2: # single 2D array ndatasets = 1 zdata_lists = [zdata_lists] elif len(np.shape(zdata_lists)) == 3: # divide 3D array into multiple 2D slices ndatasets = np.shape(zdata_lists)[2] original_zdata_lists = zdata_lists zdata_lists = [] zdata_lists = [original_zdata_lists[:,:,i] for i in range(np.shape(original_zdata_lists)[2])] else: print('Dimensions of zdata_lists numpy array is incorrect') return 0 else: print('zdata_lists is invalid. Please enter either a list (of lists) or numpy array') return 0 if ndatasets>1: if isinstance(plot_styles, list): for i in plot_styles: if i in pls_maps: print('Only 1 dataset is allowed per call of fancy_3D_plot when a map plot style is selected.') return 0 else: if plot_styles in pls_maps: print('Only 1 dataset is allowed per call of fancy_3D_plot when a map plot style is selected.') return 0 # Determine if 2D color map or 3D plot plot_2D_map = False plot_pcolormesh = False if isinstance(plot_styles, list): if plot_styles[0] in pls_maps: plot_2D_map = True if plot_styles[0]=='map_pcolormesh': plot_pcolormesh = True else: if plot_styles in pls_maps: plot_2D_map = True if plot_styles=='map_pcolormesh': plot_pcolormesh = True zlen = ndatasets # At this point, zdata_lists if just a list containing either 1D lists or 2D numpy arrays # For each z dataset, determine if a list of z coords (1D) or xy map (2D) array nzdims = [] for i in range(len(zdata_lists)): nzdims.append(len(np.shape(np.array(zdata_lists[i])))) # Now determine how the provided x and y data map onto the provided z data # Check if either x or y data lists are lists of floats/ints rather than a list of lists xdata_only_vals = (all(isinstance(el, (int, float)) for el in xdata_lists)) ydata_only_vals = (all(isinstance(el, (int, float)) for el in ydata_lists)) if xdata_only_vals: xdata_lists = [xdata_lists for i in range(ndatasets)] if ydata_only_vals: ydata_lists = [ydata_lists for i in range(ndatasets)] xlen = len(xdata_lists) ylen = len(ydata_lists) # Check that all dimensions fit correctly and are consistent for i in range(ndatasets): xvals = xdata_lists[i] yvals = ydata_lists[i] zvals = zdata_lists[i] if nzdims[i]==1: # 1D list zlength = len(zvals) zwidth = 1 else: # 2D array zlength = np.shape(zvals)[0] zwidth = np.shape(zvals)[1] if zwidth==1: # points / line if not (len(xvals) == zlength and len(yvals) == zlength): print('Dimension mismatch of dataset i={} with x-length={}, y-length={}, and z-length={}, aborting.'.format(str(i),str(len(xvals)),str(len(yvals)),str(zlength))) return 0 else: # surface if not (len(xvals) == zlength and len(yvals) == zwidth): # if not fitting expected dimensions if ((len(yvals) == zlength and len(xvals) == zwidth) or (plot_pcolormesh and (len(yvals) == zlength+1 or len(xvals) == zwidth+1))): # z vals need to be transposed zdata_lists[i] = zdata_lists[i].T print('Warning: Transpozing Z dataset i={} with x-length={}, y-length={}, and original z-shape from {} to {}.'.format(str(i),str(len(xvals)),str(len(yvals)),str(np.shape(zvals)),str(np.shape(zdata_lists[i])))) elif plot_pcolormesh and (len(yvals) == zwidth+1 or len(xvals) == zlength+1): print('Note: For Z dataset i={} with x-length={}, y-length={}, and original z-shape from {} to {} can only be used with map_pcolormesh due to shape.'.format(str(i),str(len(xvals)),str(len(yvals)),str(np.shape(zvals)),str(np.shape(zdata_lists[i])))) else: print('Dimension mismatch of dataset i={} with x-length={}, y-length={}, and z-shape={}, aborting.'.format(str(i),str(len(xvals)),str(len(yvals)),str(zlength))) return 0 fst = title_fs #16 fs = axis_fs #14 z_min = 1.0e10 # later used to set z-axis minimum z_max = 1.0e-14 # later used to set z-axis maximum y_min = 1.0e10 # later used to set y-axis minimum y_max = 1.0e-14 # later used to set y-axis maximum x_min = 1.0e5 # later used to set x-axis minimum x_max = 1.0e1 # later used to set x-axis maximum if z_scale == 'log': z_min = np.log10(z_min) z_max = np.log10(z_max) if y_scale == 'log': y_min = np.log10(y_min) y_max = np.log10(y_max) if x_scale == 'log': x_min = np.log10(x_min) x_max = np.log10(x_max) plt.rc('font', family=f_family, style=f_style, variant=f_variant, weight=f_weight) if fig==None: fig = plt.figure(figi) if plot_2D_map: ax = fig.add_subplot(spnrows, spncols, spindex) elif use_custom_3d_axis_class: ax = fig.add_subplot(spnrows, spncols, spindex, projection='3d_custom') else: ax = fig.add_subplot(spnrows, spncols, spindex, projection='3d') #bg_color = '#FFFFFF' #'#E1E4E6' #fig.patch.set_facecolor(bg_color) #fig.patch.set_alpha(1.0) #ax = plt.subplot(spnrows, spncols, spindex) for i in range(ndatasets): if include_legend: label_str = data_labels[i] else: label_str = '' if isinstance(plot_styles, list): pls = plot_styles[i] elif plot_styles==None: if nzdims[i]==1: pls = 'line' else: pls = 'trisurface' else: pls = plot_styles if pls not in valid_plot_styles: print('Submitted plot style "{}" for index {} dataset is not a valid entry. Valid options include: '.format(pls,str(i)),valid_plot_styles,"Aborting.") return 0 elif pls not in pls_by_dims[int(nzdims[i]-1)]: print('Submitted plot style "{}" for index {} dataset is not a valid entry for a {}-D dataset. Valid options include: '.format(pls,str(i),str(nzdims[i])),pls_by_dims[int(nzdims[i]-1)],'Aborting.') return 0 # Get settings which may be constant or vary by dataset (lists) if isinstance(color, list): c = color[i] else: c = color if c=='#FDFEFC': c= None if isinstance(cmap, list): cmp = cmap[i] else: cmp = cmap if isinstance(cmap,str): cmp = plt.get_cmap(cmp) if isinstance(linestyle, list): ls = linestyle[i] else: ls = linestyle if isinstance(linewidth, list): lw = linewidth[i] else: lw = linewidth if isinstance(marker, list): mkr = marker[i] else: mkr = marker if isinstance(markersize, list): mks = markersize[i] else: mks = markersize if isinstance(markerfacecolor, list): mfc = markerfacecolor[i] else: mfc = markerfacecolor if isinstance(markeredgecolor, list): mec = markeredgecolor[i] else: mec = markeredgecolor if isinstance(markeredgewidth, list): mew = markeredgewidth[i] else: mew = markeredgewidth if isinstance(depthshade, list): depthshade_i = depthshade[i] else: depthshade_i = depthshade if isinstance(rstride, list): rstride_i = rstride[i] else: rstride_i = rstride if isinstance(cstride, list): cstride_i = cstride[i] else: cstride_i = cstride if isinstance(rcount, list): rcount_i = rcount[i] else: rcount_i = rcount if isinstance(ccount, list): ccount_i = ccount[i] else: ccount_i = ccount if isinstance(facecolors, list): facecolors_i = facecolors[i] else: facecolors_i = facecolors if isinstance(alpha, list): alpha_i = alpha[i] else: alpha_i = alpha # Make actual plot xvals = np.array(xdata_lists[i]).astype(float) yvals = np.array(ydata_lists[i]).astype(float) zvals = np.array(zdata_lists[i]).astype(float) # If user provided axis bounds, enforce them now if x_limits: if x_limits[0]: xvals[xvals<x_limits[0]] = np.NaN if x_limits[1]: xvals[xvals>x_limits[1]] = np.NaN if y_limits: if y_limits[0]: yvals[yvals<y_limits[0]] = np.NaN if y_limits[1]: yvals[yvals>y_limits[1]] = np.NaN if z_limits: if OoB_z_handling=='NaN': if z_limits[0]: zvals[zvals<z_limits[0]] = np.NaN if z_limits[1]: zvals[zvals>z_limits[1]] = np.NaN elif OoB_z_handling=='limits': if z_limits[0]: zvals[zvals<z_limits[0]] = z_limits[0] if z_limits[1]: zvals[zvals>z_limits[1]] = z_limits[1] if z_scale == 'log': zvals[(zvals<=0)] = np.NaN zvals = np.log10(zvals) if y_scale == 'log': yvals[yvals<=0] = np.NaN yvals = np.log10(yvals) if x_scale == 'log': xvals[xvals<=0] = np.NaN xvals = np.log10(xvals) if len(yvals) != 0: if len(yvals[np.nonzero(yvals)]) != 0: if min(yvals[np.nonzero(yvals)])<y_min: y_min = min(yvals[np.nonzero(yvals)]) #if min(yvals)<y_min: y_min = min(yvals) if np.nanmax(yvals)>y_max: y_max = np.nanmax(yvals) if np.nanmin(xvals)<x_min: x_min = np.nanmin(xvals) if np.nanmax(xvals)>x_max: x_max = np.nanmax(xvals) if np.nanmin(zvals)<z_min: z_min = np.nanmin(zvals) if np.nanmax(zvals)>z_max: z_max = np.nanmax(zvals) if nzdims[i]==1: # 1D list zlength = len(zvals) zwidth = 1 else: # 2D array zlength = np.shape(zvals)[0] zwidth = np.shape(zvals)[1] # Plotting functions if nzdims[i]==1: # line if pls=='line': ax.plot(xvals,yvals,zvals,label=label_str, color=c,linestyle=ls,linewidth=lw,alpha=alpha_i, marker=mkr,markersize=mks,markerfacecolor=mfc,markeredgecolor=mec,markeredgewidth=mew) # scatter elif pls=='scatter': if not c: c = mfc # if no color defined, check to see if marker face color was defined ax.scatter(xvals,yvals,zvals,label=label_str, color=c,depthshade=depthshade_i,alpha=alpha_i, marker=mkr,s=mks**2,linewidths=mew,edgecolors=mec) # trisurface elif pls=='trisurface': if cmp != None: c = None if facecolors != None: c = None cmap = None ps1 = ax.plot_trisurf(xvals,yvals,zvals,label=label_str, color=c,cmap=cmp,facecolors=facecolors_i,alpha=alpha_i) ps1._facecolors2d=ps1._facecolors3d ps1._edgecolors2d=ps1._edgecolors3d else: print('Encountered incompatability with plot style {} and data dimensionality {} for data index {}. Aborting.'.format(pls,str(nzdims[i]),str(i))) else: xvals_original,yvals_original = xvals,yvals xvals,yvals = np.meshgrid(xvals,yvals) # surface if pls=='surface': if cmp != None: c = None if facecolors != None: c = None cmap = None ps1 = ax.plot_surface(xvals,yvals, zvals.T,label=label_str, color=c,cmap=cmp,facecolors=facecolors_i,alpha=alpha_i, rcount=rcount_i,ccount=ccount_i, antialiased=False, vmin=z_min,vmax=z_max) # this line was once not needed ps1._facecolors2d=ps1._facecolors3d ps1._edgecolors2d=ps1._edgecolors3d # wireframe elif pls=='wireframe': ax.plot_wireframe(xvals,yvals, zvals.T,label=label_str, color=c,linestyle=ls,linewidth=lw,alpha=alpha_i, rcount=rcount_i,ccount=ccount_i) elif pls=='trisurface': xvals = np.reshape(xvals, -1) yvals = np.reshape(yvals, -1) xtri = [] ytri = [] ztri = [] for yi in range(np.shape(zvals)[1]): for xi in range(np.shape(zvals)[0]): ztri.append(zvals[xi,yi]) #xtri.append(xdata_lists[i][xi]) #ytri.append(ydata_lists[i][yi]) if cmp != None: c = None if facecolors != None: c = None cmap = None ps1 = ax.plot_trisurf(xvals,yvals,ztri,label=label_str, color=c,cmap=cmp,facecolors=facecolors_i,alpha=alpha_i) ps1._facecolors2d=ps1._facecolors3d ps1._edgecolors2d=ps1._edgecolors3d # contour elif pls=='contour': if cmp != None: c = None ax.contour(xvals,yvals, zvals.T, colors=c,cmap=cmp,linestyles=ls,linewidths=lw,alpha=alpha_i) # filled contour elif pls=='filledcontour': if cmp != None: c = None ax.contourf(xvals,yvals, zvals.T, colors=c,cmap=cmp,alpha=alpha_i) # map contour elif pls=='map_pcolormesh': if cmp != None: c = None # first, check if x and y dims are 1 larger than z dims expand_x, expand_y = False, False if np.shape(yvals)[0]==np.shape(zvals)[1]: expand_y = True if np.shape(xvals)[1]==np.shape(zvals)[0]: expand_x = True # make x any y bigger by 1 since pcolormesh takes all edges, not just midpoints if expand_x: dx = xvals[:,1:] - xvals[:,:-1] if x_meaning=='min': newx = (xvals[:,-1]+dx[:,-1]).reshape(len(xvals[:,0]),1) xvals = np.hstack(( xvals , newx )) yvals = np.hstack(( yvals, yvals[:,-1].reshape(len(yvals[:,-1]),1))) if x_meaning=='max': newx = (xvals[:,0]-dx[:,0]).reshape(len(xvals[:,0]),1) xvals = np.hstack(( newx , xvals )) yvals = np.hstack(( yvals[:,0].reshape(len(yvals[:,0]),1), yvals )) if x_meaning=='mid': newx = (xvals[:,0]-0.5*dx[:,0]).reshape(len(xvals[:,0]),1) dx =np.hstack((dx, np.tile(dx[:, [-1]], 1))) xvals = xvals + 0.5*dx xvals = np.hstack(( newx , xvals )) yvals = np.hstack(( yvals[:,0].reshape(len(yvals[:,0]),1), yvals )) if expand_y: dy = yvals[1:] - yvals[:-1] if y_meaning=='min': newy = yvals[-1,:]+dy[-1,:] yvals = np.vstack(( yvals , newy )) xvals = np.vstack(( xvals, xvals[-1,:] )) if y_meaning=='max': newy = yvals[0,:]-dy[0,:] yvals = np.vstack(( newy, yvals )) xvals = np.vstack(( xvals[0,:] , xvals )) if y_meaning=='mid': newy = yvals[0,:]-0.5*dy[0,:] dy =np.vstack((dy, np.tile(dy[[-1],:], 1))) yvals = yvals + 0.5*dy yvals = np.vstack(( newy , yvals )) xvals = np.vstack(( xvals[0,:], xvals )) pcm2d = ax.pcolormesh(xvals,yvals, zvals.T, cmap=cmp,linestyles=ls,linewidths=lw,alpha=alpha_i) # map contour (normal or filled) elif pls=='map_filledcontour' or pls=='map_contour': if cmp != None: c = None if x_meaning!='mid': # shift x values to be midpoints dx = xvals[:,1:] - xvals[:,:-1] if x_meaning=='min': dx =np.hstack((dx, np.tile(dx[:, [-1]], 1))) xvals = xvals + 0.5*dx if x_meaning=='max': dx = np.hstack((np.tile(dx[:, [0]], 1), dx)) xvals = xvals - 0.5*dx if y_meaning!='mid': # shift y values to be midpoints dy = yvals[1:] - yvals[:-1] if y_meaning=='min': dy =np.vstack((dy, np.tile(dy[[-1],:], 1))) yvals = yvals + 0.5*dy if y_meaning=='max': dy = np.vstack((np.tile(dy[[0],:], 1), dy)) yvals = yvals - 0.5*dy #print(yvals) if pls=='map_contour': pcm2d = ax.contour(xvals,yvals, zvals.T, cmap=cmp,linestyles=ls,alpha=alpha_i) else: pcm2d = ax.contourf(xvals,yvals, zvals.T, cmap=cmp,linestyles=ls,alpha=alpha_i) else: print('Encountered incompatability with plot style {} and data dimensionality {} for data index {}. Aborting.'.format(pls,str(nzdims[i]),str(i))) if title_str.strip() != '': window_title = slugify(title_str) # "comparison_fig" else: window_title = 'Figure ' + str(figi) #window_title = window_title.replace('b','',1) # remove leading 'b' character from slugify process fig.canvas.manager.set_window_title(window_title) # hangle figure/legend positioning/sizing # First, figure size default_fig_x_in = fig_width_inch default_fig_y_in = fig_height_inch fig_x_in = default_fig_x_in fig_y_in = default_fig_y_in fig.set_size_inches(fig_x_in,fig_y_in) mpl_leg_pos_names = ['best','upper right','upper left','lower left','lower right','right','center left','center right','lower center','upper center','center'] custom_leg_pos_names = ['outside right', 'outside bottom'] if include_legend and legend_position in custom_leg_pos_names: if legend_anchor==None: if legend_position=='outside right': legend_anchor = (1.0,0.75) elif legend_position=='outside bottom': legend_anchor = (0.5,-0.05) leg1_anchor = legend_anchor # varied items handles_l1, labels_l1 = ax.get_legend_handles_labels() if legend_position == 'outside right': legend1 = ax.legend(handles_l1, labels_l1,loc='upper left',bbox_to_anchor=leg1_anchor,ncol=legend_ncol,framealpha=legend_alpha) elif legend_position == 'outside bottom': if legend_ncol == 1 and len(data_labels) > 1: legend_ncol = len(data_labels) legend1 = ax.legend(handles_l1, labels_l1,loc='upper center',bbox_to_anchor=leg1_anchor,ncol=legend_ncol,framealpha=legend_alpha) ax.add_artist(legend1) fig.canvas.draw() f1 = legend1.get_frame() l1_w0_px, l1_h0_px = f1.get_width(), f1.get_height() l_w0_in, l_h0_in = l1_w0_px/fig.dpi, l1_h0_px/fig.dpi # width and height of legend, in inches else: l_w0_in, l_h0_in = 0.0, 0.0 if include_legend and legend_position not in custom_leg_pos_names: # use matplotlib default-style legend inside plot area ax.legend(loc=legend_position,ncol=legend_ncol,framealpha=legend_alpha) n_title_lines = 0 if title_str.strip() != '': n_title_lines = 1 + title_str.count('\n') n_xlabel_lines = 0 if x_label_str.strip() != '': n_xlabel_lines = 1 + x_label_str.count('\n') n_ylabel_lines = 0 if y_label_str.strip() != '': n_ylabel_lines = 1 + y_label_str.count('\n') n_zlabel_lines = 1 if z_label_str.strip() != '': n_zlabel_lines = 1 + z_label_str.count('\n') if plot_2D_map: # These values are good, do not change them. (derived while working on SHAEDIT project) # INCORPORATE WIDTH OF COLORBAR AND ITS LABEL? x0bar = 0.60 + 0.200*n_ylabel_lines # inches, horizontal space needed for ylabel y0bar = 0.45 + 0.200*n_xlabel_lines # inches, vertical space needed for xticks/numbers, xlabel and any extra lines it has t0bar = 0.10 + 0.300*n_title_lines # inches, vertical space needed for title del_l_in = 0.15 # inches, extra horizontal padding right of legend else: # These values are good, do not change them. (derived while working on on this function specifically for 3D plotting) x0bar = 0.00 + 0.200*(n_zlabel_lines-1) # inches, horizontal space needed for ylabel y0bar = 0.45 + 0.200*max(n_xlabel_lines,n_ylabel_lines) # inches, vertical space needed for xticks/numbers, xlabel and any extra lines it has t0bar = 0.10 + 0.300*n_title_lines # inches, vertical space needed for title del_l_in = 0.15 # inches, extra horizontal padding right of legend # adjust legend spacing depending on its position if legend_position=='outside right': l_h0_in = 0.0 elif legend_position=='outside bottom': l_w0_in = 0.0 # Plot window placement and sizing x0 = x0bar/fig_x_in # distance from left edge that plot area begins y0 = y0bar/fig_y_in + (l_h0_in/fig_y_in) # distance from bottom edge that plot area begins h0 = 1 - (y0bar+t0bar)/fig_y_in - (l_h0_in/fig_y_in) # height of plot area, set to be full height minus space needed for title, x-label, and potentially an outside bottom legend w0 = 1 - x0 - (l_w0_in/fig_x_in) - (del_l_in/fig_x_in) # width of plot area, set to be full width minus space needed for y-label and potentially an outside right legend if man_sp_placement: x0 = spx0 y0 = spy0 h0 = sph0 w0 = spw0 # Set size and location of the plot on the canvas box = ax.get_position() # all vals in [0,1]: left, bottom, width, height if not man_sp_placement and (spnrows != 1 or spncols != 1): pstr = 'Warning: It is highly encouraged that subplots be positioned manually.\n' pstr += ' This is done by setting man_sp_placement=True and then adjusting\n' pstr += ' the parameters spx0, spy0, sph0, and spw0 for each subplot.\n' pstr += ' The current plot was automatically sized by matplotlib.\n' print(pstr) else: ax.set_position([x0, y0, w0, h0]) if plot_2D_map: ax.set_title(title_str,fontsize=fst) plt.xlabel(x_label_str,fontsize=fs) plt.ylabel(y_label_str,fontsize=fs) plt.xscale(x_scale) plt.yscale(y_scale) zoom_mult = 1.0 x_log_buffer = 0.15*zoom_mult y_log_buffer = 0.2*zoom_mult min_x_decs = 2 min_y_decs = 2 x_scale='linear' if not x_limits: if x_scale == 'log': # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if (np.log10(x_max)-np.log10(x_min)+2*x_log_buffer) < min_x_decs: x_log_buffer = 0.5*(min_x_decs - (np.log10(x_max)-np.log10(x_min))) plt.xlim([10**(np.log10(x_min)-x_log_buffer),10**(np.log10(x_max)+x_log_buffer)]) else: plt.xlim(x_limits) if not y_limits: if y_scale == 'log': # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if (np.log10(y_max)-np.log10(y_min)+2*y_log_buffer) < min_y_decs: y_log_buffer = 0.5*(min_y_decs - (np.log10(y_max)-np.log10(y_min))) plt.ylim([10**(np.log10(y_min)-y_log_buffer),10**(np.log10(y_max)+y_log_buffer)]) else: plt.ylim(y_limits) if z_limits: if z_scale == 'log': zlogmin = None zlogmax = None if z_limits[0]: zlogmin=np.log10(z_limits[0]) if z_limits[1]: zlogmax=np.log10(z_limits[1]) pcm2d.set_clim(vmin=zlogmin, vmax=zlogmax) else: pcm2d.set_clim(vmin=z_limits[0], vmax=z_limits[1]) def fmt(x, pos): a, b = '{:.2e}'.format(x).split('e') b = int(b) if z_scale=='log': return r'$10^{{{:g}}}$'.format(x) else: if b < -2 or b > 3: return r'${:g} \times 10^{{{}}}$'.format(np.float(a), b) else: return '{:g}'.format(x) divider = make_axes_locatable(ax) cbar_size_str = '{:g}'.format(cbar_size) + '%' cax = divider.append_axes("right", size=cbar_size_str, pad=cbar_pad) cbar = plt.colorbar(pcm2d, cax=cax, format=ticker.FuncFormatter(fmt)) if cbar_fs==None: cbar_fs = fs cbar.set_label(z_label_str,fontsize=cbar_fs) #cbar.solids.set_rasterized(True) cbar.solids.set_edgecolor("face") #cbar.set_alpha(alpha_i) #cbar.draw_all() ax.set_position([x0, y0, w0, h0]) else: # if 3D-plot ax.set_title(title_str,fontsize=fst) plt.xlabel(x_label_str,fontsize=fs) plt.ylabel(y_label_str,fontsize=fs) ax.set_zlabel(z_label_str,fontsize=fs) # Current matplotlib set_scale commands for log scale are borked completely beyond use, manually add log support #ax.set_xscale(x_scale) #ax.set_yscale(y_scale) #ax.set_zscale(z_scale) #plt.grid(b=True, which='major', linestyle='-', alpha=0.25) # doesn't affect 3D axis #plt.grid(b=True, which='minor', linestyle='-', alpha=0.10) # ensure at least minimum number of decades are present on a plot by increasing padding if necessary zoom_mult = 1.0 x_log_buffer = 0.15*zoom_mult y_log_buffer = 0.2*zoom_mult z_log_buffer = 0.2*zoom_mult min_x_decs = 1 min_y_decs = 1 min_z_decs = 1 manually_calculate_axis_bounds = not use_mpl_limits # if False, use default Matplotlib axis bounds; if True, use specially calculated axis bounds if not x_limits: if x_scale == 'log' and manually_calculate_axis_bounds: # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if ((x_max)-(x_min)+2*x_log_buffer) < min_x_decs: x_log_buffer = 0.5*(min_x_decs - ((x_max)-(x_min))) ax.set_xlim([((x_min)-x_log_buffer),((x_max)+x_log_buffer)]) else: if x_scale=='log': xlimsnew = [] for limi in range(2): if x_limits[limi]: xlimsnew.append(np.log10(x_limits[limi])) else: xlimsnew.append(None) ax.set_xlim(xlimsnew) else: ax.set_xlim(x_limits) if not y_limits: if y_scale == 'log' and manually_calculate_axis_bounds: # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if ((y_max)-(y_min)+2*y_log_buffer) < min_y_decs: y_log_buffer = 0.5*(min_y_decs - ((y_max)-(y_min))) ax.set_ylim([((y_min)-y_log_buffer),((y_max)+y_log_buffer)]) else: if y_scale=='log': ylimsnew = [] for limi in range(2): if y_limits[limi]: ylimsnew.append(np.log10(y_limits[limi])) else: ylimsnew.append(None) ax.set_ylim(ylimsnew) else: ax.set_ylim(y_limits) if not z_limits: if z_scale == 'log' and manually_calculate_axis_bounds: # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if ((z_max)-(z_min)+2*z_log_buffer) < min_z_decs: z_log_buffer = 0.5*(min_z_decs - ((z_max)-(z_min))) ax.set_zlim([((z_min)-z_log_buffer),((z_max)+z_log_buffer)]) else: if z_scale=='log': zlimsnew = [] for limi in range(2): if z_limits[limi]: zlimsnew.append(np.log10(z_limits[limi])) else: zlimsnew.append(None) ax.set_zlim(zlimsnew) else: ax.set_zlim(z_limits) act_xmin, act_xmax = ax.get_xlim() act_ymin, act_ymax = ax.get_ylim() act_zmin, act_zmax = ax.get_zlim() def round_up_to_nearest_multiple(val,mult=1): round_val = np.ceil(val/mult)*mult if isinstance(mult,int) or (abs(round_val)%1<0.01): round_val = int(round_val) return round_val def round_down_to_nearest_multiple(val,mult=1): round_val = np.floor(val/mult)*mult if isinstance(mult,int) or (abs(round_val)%1<0.01): round_val = int(round_val) return round_val def get_ints_between_2_vals(vmin,vmax): stepval = 1 #vmini, vmaxi = int(np.ceil(vmin)),int(np.floor(vmax)) if (vmax-vmin) <=1: stepval = 0.25 elif (vmax-vmin) <=2: stepval = 0.5 vmini = round_up_to_nearest_multiple(vmin,stepval) vmaxi = round_down_to_nearest_multiple(vmax,stepval) tick_list = list(np.arange(vmini,vmaxi+stepval,stepval)) return tick_list def get_log_minor_ticks_between_bounds(vmin,vmax): minor_tick_list = [] # get powers of min and max minpower = np.sign(vmin)*divmod(abs(vmin),1)[0] # integer portion of vmin maxpower = np.sign(vmax)*divmod(abs(vmax),1)[0] # integer portion of vmax # determine leading number in base 10 min_lead_digit = divmod(((10**vmin)/(10**minpower)),1)[0] + 1 max_lead_digit = divmod(((10**vmax)/(10**maxpower)),1)[0] cdigit = min_lead_digit cpower = minpower cval = cdigit*(10**cpower) maxval = max_lead_digit*(10**maxpower) while cval < maxval: minor_tick_list.append(np.log10(cval)) cdigit += 1 if cdigit == 10: cdigit = 2 cpower += 1 cval = cdigit*(10**cpower) return minor_tick_list def log_tick_formatter(vals_list): tstr_list = [] for val in vals_list: tstr = r'10$^{{{:g}}}$'.format(val) tstr_list.append(tstr) return tstr_list if z_scale == 'log': zticks = get_ints_between_2_vals(act_zmin, act_zmax) ztick_labs = log_tick_formatter(zticks) ax.set_zticks(zticks) ax.set_zticklabels(ztick_labs) if y_scale == 'log': yticks = get_ints_between_2_vals(act_ymin, act_ymax) ytick_labs = log_tick_formatter(yticks) ax.set_yticks(yticks) ax.set_yticklabels(ytick_labs) if x_scale == 'log': xticks = get_ints_between_2_vals(act_xmin, act_xmax) xtick_labs = log_tick_formatter(xticks) ax.set_xticks(xticks) ax.set_xticklabels(xtick_labs) #ax.set_yticks(yticks+np.log10(np.array([2,3,4,5,8])).tolist()) #yticks = yticks + np.log10(np.array([2,3,4,5,8])).tolist() grid_alpha = 0.25 grid_alpha_minor = 0.05 if x_scale == 'log': ax.xaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha_minor) else: ax.xaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha) if y_scale == 'log': ax.yaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha_minor) else: ax.yaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha) if z_scale == 'log': ax.zaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha_minor) else: ax.zaxis._axinfo["grid"]['color'] = (0,0,0,grid_alpha) if use_custom_3d_axis_class: # If log scale, add minor grid lines if x_scale == 'log': xticks_minor = get_log_minor_ticks_between_bounds(act_xmin, act_xmax) xgridlines = [] for i in range(len(xticks)): xgridlines.append((xticks[i],(0,0,0,grid_alpha))) for i in range(len(xticks_minor)): xgridlines.append((xticks_minor[i],(1,1,1,grid_alpha_minor))) ax.set_xticks(xticks+xticks_minor) ax.xaxis.set_gridline_color(*xgridlines) if y_scale == 'log': yticks_minor = get_log_minor_ticks_between_bounds(act_ymin, act_ymax) ygridlines = [] for i in range(len(yticks)): ygridlines.append((yticks[i],(0,0,0,grid_alpha))) for i in range(len(yticks_minor)): ygridlines.append((yticks_minor[i],(0,0,0,grid_alpha_minor))) ax.set_yticks(yticks+yticks_minor) ax.yaxis.set_gridline_color(*ygridlines) if z_scale == 'log': zticks_minor = get_log_minor_ticks_between_bounds(act_zmin, act_zmax) zgridlines = [] for i in range(len(zticks)): zgridlines.append((zticks[i],(0,0,0,grid_alpha))) for i in range(len(zticks_minor)): zgridlines.append((zticks_minor[i],(0,0,0,grid_alpha_minor))) ax.set_zticks(zticks+zticks_minor) ax.zaxis.set_gridline_color(*zgridlines) # For some more info, see https://dawes.wordpress.com/2014/06/27/publication-ready-3d-figures-from-matplotlib/ # Tick positioning [t.set_va('center') for t in ax.get_yticklabels()] [t.set_ha('center') for t in ax.get_yticklabels()] [t.set_va('center') for t in ax.get_xticklabels()] [t.set_ha('center') for t in ax.get_xticklabels()] [t.set_va('center') for t in ax.get_zticklabels()] [t.set_ha('center') for t in ax.get_zticklabels()] '' tick_infactor = 0.0 tick_outfactor = 0.2 # adjusts length of ticks on inside/outside of plot ax.xaxis._axinfo['tick']['inward_factor'] = tick_infactor ax.xaxis._axinfo['tick']['outward_factor'] = tick_outfactor ax.yaxis._axinfo['tick']['inward_factor'] = tick_infactor ax.yaxis._axinfo['tick']['outward_factor'] = tick_outfactor ax.zaxis._axinfo['tick']['inward_factor'] = tick_infactor ax.zaxis._axinfo['tick']['outward_factor'] = tick_outfactor # Background ax.xaxis.pane.set_edgecolor('black') ax.yaxis.pane.set_edgecolor('black') #ax.zaxis.pane.set_edgecolor('black') ax.xaxis.pane.fill = False ax.yaxis.pane.fill = False ax.zaxis.pane.fill = False #ax.view_init(elev=10, azim=135) return fig, ax  def fancy_plot(xdata_lists, ydata_lists, dictionaries=None, data_labels=[], xerr_lists=[[]], yerr_lists=[[]], figi=1, title_str='title', x_label_str='x-axis', y_label_str='y-axis', x_limits=[], y_limits=[], x_scale='log', y_scale='log', color='#FDFEFC', alpha=1.0, linestyle='', linewidth=1, marker='.', markersize=5, markerfacecolor=None, markeredgecolor=None, markeredgewidth=None, errorstyle='bar-band', error_band_opacity=0.15, elinewidth=None, capsize=None, fig_width_inch=9.5, fig_height_inch=6.5, title_fs=16, axis_fs=14, f_family='sans-serif', f_style='normal', f_variant='normal', f_weight='normal', legend_position='outside right', legend_anchor=None, legend_ncol=1, legend_alpha=None, legend_columnspacing=None, errorbox_xdata_l=[[]], errorbox_xdata_r=[[]], errorbox_ydata_l=[[]], errorbox_ydata_u=[[]], errorbox_fc='k', errorbox_fa=0.1, errorbox_ec='k', errorbox_ea=1.0, errorbox_ew=0.5, fig=None, ax=None, spnrows=1, spncols=1, spindex=1, man_sp_placement=False, spx0=0.1, spy0=0.1, sph0=0.4, spw0=0.4)  ## Description Function which makes very customizable and beautiful plots. It is intended to be used when plotting multiple datasets at once with a legend but can also handle individual datasets ## Dependencies • import numpy as np • import matplotlib.pyplot as plt ## Inputs (Required) • xdata_lists = a list containing lists/arrays of x data (or single list of xdata applied to all ydata in y_data_lists) • ydata_lists = a list containing lists/arrays of y data (or single list of ydata) OR • dictionaries (see below) ## Dictionaries • dictionaries = a list containing dictionary objects for each dataset to be plotted (or single dictionary object). This provides an alternate way of providing this function with data to be plotted. If wanting to use exclusively dictionaries, set xdata_lists=None and ydata_lists=None; otherwise, the two modes may be used together. The dictionaries are converted to the "standard" list of lists/strings/etc format native to this function. Below are listed the input keywords for these dictionaries; where not the same as the normal variables for this function, the equivalent name is provided in parentheses. • Required: 'xdata' (xdata_lists), 'ydata' (ydata_lists) • Optional (basic): 'data_label' (data_labels), 'xerr' (xerr_lists), 'yerr' (yerr_lists), 'color', 'alpha', 'linestyle', 'linewidth', 'marker', 'markersize', 'markerfacecolor', 'markeredgecolor', 'markeredgewidth', 'errorstyle', 'error_band_opacity', 'elinewidth', 'capsize' • Optional (advanced): 'errorbox_xdata_l', 'errorbox_xdata_r', 'errorbox_ydata_l', 'errorbox_ydata_u', 'errorbox_fc', 'errorbox_fa', 'errorbox_ec', 'errorbox_ea', 'errorbox_ew' For any entry omitted from a dictionary, the value provided to the function is checked first; otherwise the default value is assumed. For example, for entries missing the 'color' keyword, the value provided to the color variable is used. If it has not been changed from its default value, then the default behavior is used. ## Inputs (Optional, basic) • data_labels = a list of strings to be used as data labels in the legend (D=[], no legend generated) • xerr_lists = a list containing lists/arrays of x data absolute uncertainties (or single list of xdata errors applied to all ydata in y_data_lists) (D=[[]], No error) • yerr_lists = a list containing lists/arrays of y data absolute uncertainties (or single list of ydata errors) (D=[[]], No error) • figi = figure index (D=1) • title_str = string to be used as the title of the plot (D='title') • x_label_str = string to be used as x-axis title (D='x-axis') • y_label_str = string to be used as y-axis title (D='y-axis') • x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=auto-calculated based on x_data_lists) • y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=auto-calculated based on y_data_lists) • x_scale = x-axis scale, either "linear", "log", "symlog", or "logit" • y_scale = y-axis scale, either "linear", "log", "symlog", or "logit" • color = list of color strings to be used of same length as y_data_lists (or individual color string) (D=Matplotlib default color cycle) • alpha = list of (or individual) alpha values (D=1.0) • linestyle = list of (or individual) strings denoting linestyle: '', '-', '--', '-.', or ':' (D='') • linewidth = list of (or individual) int/float of the width of line (D=1) • marker = list of (or individual) marker styles (D='.') For all options, see: https://matplotlib.org/3.1.0/api/markers_api.html • markersize = list of (or individual) int/float of marker size (D=5) • markerfacecolor = list of (or individual) marker face colors (D=None, use value of 'color') • markeredgecolor = list of (or individual) marker edge colors (D=None, use value of 'color') • markeredgewidth = list of (or individual) int/float of marker edge widths (D=None) • errorstyle = list of (or individual) strings specifying how y-error is represented (D='bar-band', ['bar-band','bar','band']) • error_band_opacity = list of (or individual) int/float of error band opacities (D=0.15) • elinewidth = list of (or individual) int/float line width of error bar lines (D=None, use current linewidth) • capsize = list of (or individual) int/float of length of the error bar caps in points (D=None) • fig_width_inch = figure width in inches (D=9.5) • fig_height_inch = figure height in inches (D=6.5) • title_fs = title font size (D=16) • axis_fs = axis label font size (D=14) • f_family = string specifying font family (D='sans-serif'); options include: ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] • f_style = string specifying font style (D='normal'); options include: ['normal', 'italic', 'oblique'] • f_variant = string specifying font variant (D='normal'); options include: ['normal', 'small-caps'] • f_weight = string specifying font weight (D='normal'); options include: ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] ## Inputs (Optional, advanced) Legend settings • legend_position = one of the default matplotlib legend position strings ('best','upper right','lower center','lower left',etc.) to place the legend inside the plot or 'outside right' or 'outside bottom' to place the legend outside of the plot area (D='outside right', if legend is to be used) • legend_anchor = legend anchor position (x=left-right position, y=bottom-top position) only used when legend position is set to one of the "outside" options (D=None which becomes (1.0,0.75) if position is 'outside right' or (0.5,-0.17) if position is 'outside bottom') Note that only one coordinate usually should be adjusted. If using an 'outside right' legend, only the y-coordinate needs to be manipulated to scoot the legend up/down. Likewise, for 'outside bottom' legends only the x-coordinate needs adjusting for tuning left/right position • legend_ncol = number of columns in legend (D=1 in all cases except for legend_position='outside bottom' where D=len(ydata_lists)) • legend_alpha = alpha of legend background (D=None, auto determined by matplotlib) • legend_columnspacing = column spacing of legend (D=None, auto determined by matplotlib) Error boxes (can be used in addition to or in lieu of normal error bars) • errorbox_xdata_l = a list containing lists/arrays of errorbox left widths from center (x-data lower error) • errorbox_xdata_r = a list containing lists/arrays of errorbox right widths from center (x-data upper error) • errorbox_ydata_l = a list containing lists/arrays of errorbox lower heights from center (y-data lower error) • errorbox_ydata_u = a list containing lists/arrays of errorbox upper heights from center (y-data upper error) Error boxes will only be drawn if at least one x list and one y list of the four above arrays is specified; unspecified lists will default to zero error. • errorbox_fc = list of (or individual) error box face color (D='k', black) • errorbox_fa = list of (or individual) error box face alpha (D=0.1) • errorbox_ec = list of (or individual) error box edge color (D='k', black) • errorbox_ea = list of (or individual) error box edge alpha (D=1.0) • errorbox_ew = list of (or individual) error box edge width (D=0.5) Subplots • fig = figure handles from existing figure to draw on (D=None, fig=None should always be used for initial subplot unless a figure canvas has already been generated) • ax = axis handles from an existing figure to draw on (D=None, ax=None should always be used for initial subplot) • spnrows = number of rows in final subplot (D=1) • spncols = number of columns in final subplot (D=1) • spindex = index of current subplot (between 1 and spnrows*spncols) (D=1) • man_sp_placement = logical variable controlling manual sizing/placement of subplots using below variables (D=False, use automatic sizing) • spx0 = distance from canvas left edge where this plotting area should begin (D=0.1), generally a number around 0~1 • spy0 = distance from canvas bottom edge where this plotting area should begin (D=0.1), generally a number around 0~1 • spw0 = width of this plotting area on the canvas (D=0.4), generally a number around 0~1 • sph0 = height of this plotting area on the canvas (D=0.4), generally a number around 0~1 ## Outputs • fig = pyplot figure • ax = pyplot figure plot/subplot axes handles Expand source code def fancy_plot( # Required data xdata_lists,ydata_lists, # Dictionaries dictionaries=None, # Optional data data_labels=[], xerr_lists=[[]], yerr_lists=[[]], # Standard basic settings (optional) figi=1, title_str='title', x_label_str='x-axis', y_label_str='y-axis', x_limits=[], y_limits=[], x_scale='log', y_scale='log', color='#FDFEFC', alpha=1.0, linestyle='', linewidth=1, marker='.', markersize=5, markerfacecolor=None, markeredgecolor=None, markeredgewidth=None, errorstyle='bar-band', error_band_opacity=0.15, elinewidth=None, capsize=None, fig_width_inch=9.5, fig_height_inch=6.5, title_fs=16, axis_fs=14, f_family='sans-serif',f_style='normal',f_variant='normal',f_weight='normal', # Advanced settings (optional) # Legend settings legend_position='outside right', legend_anchor=None, legend_ncol=1, legend_alpha=None, legend_columnspacing=None, # Errorbar settings errorbox_xdata_l=[[]], errorbox_xdata_r=[[]], errorbox_ydata_l=[[]], errorbox_ydata_u=[[]], errorbox_fc='k', errorbox_fa=0.1, errorbox_ec='k', errorbox_ea=1.0, errorbox_ew=0.5, # Subplot settings fig=None, ax=None, spnrows=1, spncols=1, spindex=1, man_sp_placement = False, spx0=0.1, spy0=0.1, sph0=0.4, spw0=0.4 ): ''' Description: Function which makes very customizable and beautiful plots. It is intended to be used when plotting multiple datasets at once with a legend but can also handle individual datasets Dependencies: - import numpy as np - import matplotlib.pyplot as plt Inputs: (Required) - xdata_lists = a list containing lists/arrays of x data (or single list of xdata applied to all ydata in y_data_lists) - ydata_lists = a list containing lists/arrays of y data (or single list of ydata) OR - dictionaries (see below) Dictionaries: - dictionaries = a list containing dictionary objects for each dataset to be plotted (or single dictionary object). This provides an alternate way of providing this function with data to be plotted. If wanting to use exclusively dictionaries, set xdata_lists=None and ydata_lists=None; otherwise, the two modes may be used together. The dictionaries are converted to the "standard" list of lists/strings/etc format native to this function. Below are listed the input keywords for these dictionaries; where not the same as the normal variables for this function, the equivalent name is provided in parentheses. - Required: 'xdata' (xdata_lists), 'ydata' (ydata_lists) - Optional (basic): 'data_label' (data_labels), 'xerr' (xerr_lists), 'yerr' (yerr_lists), 'color', 'alpha', 'linestyle', 'linewidth', 'marker', 'markersize', 'markerfacecolor', 'markeredgecolor', 'markeredgewidth', 'errorstyle', 'error_band_opacity', 'elinewidth', 'capsize' - Optional (advanced): 'errorbox_xdata_l', 'errorbox_xdata_r', 'errorbox_ydata_l', 'errorbox_ydata_u', 'errorbox_fc', 'errorbox_fa', 'errorbox_ec', 'errorbox_ea', 'errorbox_ew' For any entry omitted from a dictionary, the value provided to the function is checked first; otherwise the default value is assumed. For example, for entries missing the 'color' keyword, the value provided to the color variable is used. If it has not been changed from its default value, then the default behavior is used. Inputs: (Optional, basic) - data_labels = a list of strings to be used as data labels in the legend (D=[], no legend generated) - xerr_lists = a list containing lists/arrays of x data absolute uncertainties (or single list of xdata errors applied to all ydata in y_data_lists) (D=[[]], No error) - yerr_lists = a list containing lists/arrays of y data absolute uncertainties (or single list of ydata errors) (D=[[]], No error) - figi = figure index (D=1) - title_str = string to be used as the title of the plot (D='title') - x_label_str = string to be used as x-axis title (D='x-axis') - y_label_str = string to be used as y-axis title (D='y-axis') - x_limits = length 2 list specifying minimum and maximum x-axis bounds [xmin,xmax] (D=auto-calculated based on x_data_lists) - y_limits = length 2 list specifying minimum and maximum y-axis bounds [ymin,ymax] (D=auto-calculated based on y_data_lists) - x_scale = x-axis scale, either "linear", "log", "symlog", or "logit" - y_scale = y-axis scale, either "linear", "log", "symlog", or "logit" - color = list of color strings to be used of same length as y_data_lists (or individual color string) (D=Matplotlib default color cycle) - alpha = list of (or individual) alpha values (D=1.0) - linestyle = list of (or individual) strings denoting linestyle: '', '-', '--', '-.', or ':' (D='') - linewidth = list of (or individual) int/float of the width of line (D=1) - marker = list of (or individual) marker styles (D='.') For all options, see: https://matplotlib.org/3.1.0/api/markers_api.html - markersize = list of (or individual) int/float of marker size (D=5) - markerfacecolor = list of (or individual) marker face colors (D=None, use value of 'color') - markeredgecolor = list of (or individual) marker edge colors (D=None, use value of 'color') - markeredgewidth = list of (or individual) int/float of marker edge widths (D=None) - errorstyle = list of (or individual) strings specifying how y-error is represented (D='bar-band', ['bar-band','bar','band']) - error_band_opacity = list of (or individual) int/float of error band opacities (D=0.15) - elinewidth = list of (or individual) int/float line width of error bar lines (D=None, use current linewidth) - capsize = list of (or individual) int/float of length of the error bar caps in points (D=None) - fig_width_inch = figure width in inches (D=9.5) - fig_height_inch = figure height in inches (D=6.5) - title_fs = title font size (D=16) - axis_fs = axis label font size (D=14) - f_family = string specifying font family (D='sans-serif'); options include: ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace'] - f_style = string specifying font style (D='normal'); options include: ['normal', 'italic', 'oblique'] - f_variant = string specifying font variant (D='normal'); options include: ['normal', 'small-caps'] - f_weight = string specifying font weight (D='normal'); options include: ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black'] Inputs: (Optional, advanced) Legend settings - legend_position = one of the default matplotlib legend position strings ('best','upper right','lower center','lower left',etc.) to place the legend inside the plot or 'outside right' or 'outside bottom' to place the legend outside of the plot area (D='outside right', if legend is to be used) - legend_anchor = legend anchor position (x=left-right position, y=bottom-top position) only used when legend position is set to one of the "outside" options (D=None which becomes (1.0,0.75) if position is 'outside right' or (0.5,-0.17) if position is 'outside bottom') Note that only one coordinate usually should be adjusted. If using an 'outside right' legend, only the y-coordinate needs to be manipulated to scoot the legend up/down. Likewise, for 'outside bottom' legends only the x-coordinate needs adjusting for tuning left/right position - legend_ncol = number of columns in legend (D=1 in all cases except for legend_position='outside bottom' where D=len(ydata_lists)) - legend_alpha = alpha of legend background (D=None, auto determined by matplotlib) - legend_columnspacing = column spacing of legend (D=None, auto determined by matplotlib) Error boxes (can be used in addition to or in lieu of normal error bars) - errorbox_xdata_l = a list containing lists/arrays of errorbox left widths from center (x-data lower error) - errorbox_xdata_r = a list containing lists/arrays of errorbox right widths from center (x-data upper error) - errorbox_ydata_l = a list containing lists/arrays of errorbox lower heights from center (y-data lower error) - errorbox_ydata_u = a list containing lists/arrays of errorbox upper heights from center (y-data upper error) *Error boxes will only be drawn if at least one x list and one y list of the four above arrays is specified; unspecified lists will default to zero error.* - errorbox_fc = list of (or individual) error box face color (D='k', black) - errorbox_fa = list of (or individual) error box face alpha (D=0.1) - errorbox_ec = list of (or individual) error box edge color (D='k', black) - errorbox_ea = list of (or individual) error box edge alpha (D=1.0) - errorbox_ew = list of (or individual) error box edge width (D=0.5) Subplots - fig = figure handles from existing figure to draw on (D=None, fig=None should always be used for initial subplot unless a figure canvas has already been generated) - ax = axis handles from an existing figure to draw on (D=None, ax=None should always be used for initial subplot) - spnrows = number of rows in final subplot (D=1) - spncols = number of columns in final subplot (D=1) - spindex = index of current subplot (between 1 and spnrows*spncols) (D=1) - man_sp_placement = logical variable controlling manual sizing/placement of subplots using below variables (D=False, use automatic sizing) - spx0 = distance from canvas left edge where this plotting area should begin (D=0.1), generally a number around 0~1 - spy0 = distance from canvas bottom edge where this plotting area should begin (D=0.1), generally a number around 0~1 - spw0 = width of this plotting area on the canvas (D=0.4), generally a number around 0~1 - sph0 = height of this plotting area on the canvas (D=0.4), generally a number around 0~1 Outputs: - fig = pyplot figure - ax = pyplot figure plot/subplot axes handles ''' include_legend = True # used to toggle legend on/off single_dataset = False # Assume multiple datasets entered, but this can be tested to see if it is the case or not. # At the very start, check for dictionaries if dictionaries != None: # determine if single entry or list of entries if isinstance(dictionaries, dict): dictionaries = [dictionaries] num_dict = len(dictionaries) dxdata_lists = [] dydata_lists = [] ddata_labels = [] dxerr_lists = [] dyerr_lists = [] dcolor, dalpha, dlinestyle, dlinewidth, dmarker, dmarkersize, dmarkerfacecolor, dmarkeredgecolor, dmarkeredgewidth = [], [], [], [], [], [], [], [], [] derrorstyle, derror_band_opacity, delinewidth, dcapsize = [], [], [], [] derrorbox_xdata_l, derrorbox_xdata_r, derrorbox_ydata_l, derrorbox_ydata_u = [], [], [], [] derrorbox_fc, derrorbox_fa, derrorbox_ec, derrorbox_ea, derrorbox_ew = [], [], [], [], [] keylist = ['xdata','xdata_lists','ydata','ydata_lists','data_label','data_labels','xerr','xerr_lists','yerr','yerr_lists', 'color','alpha', 'linestyle', 'linewidth', 'marker', 'markersize', 'markerfacecolor', 'markeredgecolor','markeredgewidth', 'errorstyle', 'error_band_opacity', 'elinewidth, capsize', 'errorbox_xdata_l', 'errorbox_xdata_r', 'errorbox_ydata_l', 'errorbox_ydata_u', 'errorbox_fc', 'errorbox_fa', 'errorbox_ec', 'errorbox_ea', 'errorbox_ew'] settings_realvars=[color , alpha, linestyle, linewidth, marker, markersize, markerfacecolor, markeredgecolor, markeredgewidth, errorstyle, error_band_opacity, elinewidth, capsize, errorbox_xdata_l, errorbox_xdata_r, errorbox_ydata_l, errorbox_ydata_u, errorbox_fc, errorbox_fa, errorbox_ec, errorbox_ea, errorbox_ew] settings_keys = ['color' ,'alpha','linestyle','linewidth','marker','markersize','markerfacecolor','markeredgecolor','markeredgewidth','errorstyle','error_band_opacity','elinewidth','capsize','errorbox_xdata_l','errorbox_xdata_r','errorbox_ydata_l','errorbox_ydata_u','errorbox_fc','errorbox_fa','errorbox_ec','errorbox_ea','errorbox_ew'] settings_vars = [dcolor , dalpha, dlinestyle, dlinewidth, dmarker, dmarkersize, dmarkerfacecolor, dmarkeredgecolor, dmarkeredgewidth, derrorstyle, derror_band_opacity, delinewidth, dcapsize, derrorbox_xdata_l, derrorbox_xdata_r, derrorbox_ydata_l, derrorbox_ydata_u, derrorbox_fc, derrorbox_fa, derrorbox_ec, derrorbox_ea, derrorbox_ew] settings_defalts=['#FDFEFC', 1.0 , '' , 1 , '.' , 5 , None , None , None , 'bar-band' , 0.15 , None , None , [] , [] , [] , [] , 'k' , 0.1 , 'k' , 1.0 , 0.5 ] for i in range(num_dict): d = dictionaries[i] if not isinstance(d, dict): print('Index {} of dictionaries list is not a dictionary! Quitting...'.format(i)) return None # Check for any unrecognizable keys dkeys = list(d.keys()) for dkey in dkeys: if dkey not in keylist: print('Encountered unknown keyword {} in dictionary entry at index {}. Ignoring it...'.format(dkey,i)) # Check for each key that will be used if 'xdata' in d: dxdata_lists.append(d['xdata']) elif 'xdata_lists' in d: dxdata_lists.append(d['xdata_lists']) else: print('Dictionary at index {} is missing xdata. Quitting...'.format(i)) return None if 'ydata' in d: dydata_lists.append(d['ydata']) elif 'ydata_lists' in d: dydata_lists.append(d['ydata_lists']) else: print('Dictionary at index {} is missing ydata. Quitting...'.format(i)) return None if 'data_label' in d: ddata_labels.append(d['data_label']) elif 'data_labels' in d: ddata_labels.append(d['data_labels']) else: ddata_labels.append(None) if 'xerr' in d: dxerr_lists.append(d['xerr']) elif 'xerr_lists' in d: dxerr_lists.append(d['xerr_lists']) else: dxerr_lists.append([]) if 'yerr' in d: dyerr_lists.append(d['yerr']) elif 'yerr_lists' in d: dyerr_lists.append(d['yerr_lists']) else: dyerr_lists.append([]) for ski in range(len(settings_keys)): if settings_keys[ski] in d: settings_vars[ski].append(d[settings_keys[ski]]) elif not isinstance(settings_realvars[ski],(list,np.ndarray)): # if main entry is not a list, use it instead of default value settings_vars[ski].append(settings_realvars[ski]) else: settings_vars[ski].append(settings_defalts[ski]) # Now combine with data entered normally, if applicable if xdata_lists != None and ydata_lists != None: # combine if not isinstance(xdata_lists[0],(list,np.ndarray)): xdata_lists = [xdata_lists] xdata_lists = xdata_lists + dxdata_lists if not isinstance(ydata_lists[0],(list,np.ndarray)): ydata_lists = [ydata_lists] num_normal_datasets = len(ydata_lists) num_dict_datasets = len(dydata_lists) ydata_lists = ydata_lists + dydata_lists if data_labels==[]: if not all(x==None for x in ddata_labels): data_labels = num_normal_datasets*[None] + ddata_labels else: data_labels = data_labels + ddata_labels if xerr_lists == [[]]: if not all(x==[] for x in dxerr_lists): xerr_lists = num_normal_datasets*[[]] + dxerr_lists else: xerr_lists = xerr_lists + dxerr_lists if yerr_lists == [[]]: if not all(x==[] for x in dyerr_lists): yerr_lists = num_normal_datasets*[[]] + dyerr_lists else: yerr_lists = yerr_lists + dyerr_lists for ski in range(len(settings_keys)): if settings_keys[ski] in ['errorbox_xdata_l','errorbox_xdata_r','errorbox_ydata_l','errorbox_ydata_u']: # the special exceptions which can be lists if settings_realvars[ski] == [[]]: if not all(x==[] for x in settings_vars[ski]): settings_realvars[ski] = num_normal_datasets*[[]] + settings_vars[ski] else: settings_realvars[ski] = settings_realvars[ski] + settings_vars[ski] else: # for each possible setting option which could be a single value or list if not isinstance(settings_realvars[ski],(list,np.ndarray)): # if main entry isn't a list if not all(x==settings_realvars[ski] for x in settings_vars[ski]): # if not all dict entries are same as main entry settings_realvars[ski] = num_normal_datasets*[settings_realvars[ski]] + settings_vars[ski] else: # just combine the two lists #print(settings_vars[ski]) settings_realvars[ski] = settings_realvars[ski] + settings_vars[ski] color, alpha, linestyle, linewidth, marker, markersize, markerfacecolor, markeredgecolor, markeredgewidth, errorstyle, error_band_opacity, elinewidth, capsize, errorbox_xdata_l, errorbox_xdata_r, errorbox_ydata_l, errorbox_ydata_u, errorbox_fc, errorbox_fa, errorbox_ec, errorbox_ea, errorbox_ew = settings_realvars else: # the only data present are in dictionary form xdata_lists = dxdata_lists ydata_lists = dydata_lists if all([x == None for x in data_labels]): data_labels = None else: data_labels = ddata_labels xerr_lists = dxerr_lists yerr_lists = dyerr_lists for ski in range(len(settings_keys)): if settings_keys[ski] in ['errorbox_xdata_l','errorbox_xdata_r','errorbox_ydata_l','errorbox_ydata_u']: # the special exceptions which can be lists if all(x==[] for x in settings_vars[ski]): settings_vars[ski] = [[]] # set the error box parameters to appear as expected if empty color, alpha, linestyle, linewidth, marker, markersize, markerfacecolor, markeredgecolor, markeredgewidth, errorstyle, error_band_opacity, elinewidth, capsize, errorbox_xdata_l, errorbox_xdata_r, errorbox_ydata_l, errorbox_ydata_u, errorbox_fc, errorbox_fa, errorbox_ec, errorbox_ea, errorbox_ew = settings_vars # End of dictionary entry handling # Determine if error boxes are to be drawn draw_error_boxes = False if (errorbox_xdata_l!=[[]] or errorbox_xdata_r!=[[]]) and (errorbox_ydata_l!=[[]] or errorbox_ydata_u!=[[]]): draw_error_boxes = True if (not xdata_lists) and (not ydata_lists): print('Warning: Both xdata and ydata lists are empty (figure index = {}, titled "{}")'.format(figi,title_str)) single_dataset = True include_legend = False xdata_lists = [[]] ydata_lists = [] xerr_lists = [[]] yerr_lists = [] elif (not xdata_lists): print('Warning: xdata list is empty (figure index = {}, titled "{}")'.format(figi,title_str)) elif (not ydata_lists): print('Warning: ydata list is empty (figure index = {}, titled "{}")'.format(figi,title_str)) # If using a single dataset (user inputs a single list, not a list of list(s) #if len(np.shape(ydata_lists)) != 1: # not just a simple list if (all(isinstance(el, (int, float)) for el in ydata_lists)): # input ydata is a single dataset, not a list of lists/arrays, convert to a list containing a single list for compatability with remainder of code single_dataset = True ydata_lists = [ydata_lists] yerr_lists = [yerr_lists] include_legend = False if draw_error_boxes: errorbox_xdata_l = [errorbox_xdata_l] errorbox_xdata_r = [errorbox_xdata_r] errorbox_ydata_l = [errorbox_ydata_l] errorbox_ydata_u = [errorbox_ydata_u] if not data_labels: include_legend = False nds = len(ydata_lists) # Allow use of single set of xdata for multiple sets of ydata if (not single_dataset) and (all(isinstance(el, (int, float)) for el in xdata_lists)): # ydata is list of lists, xdata is a single list. Assume same xdata for each set of ydata xdata2 = [] for i in range(nds): xdata2.append(xdata_lists) xdata_lists = xdata2 if (all(isinstance(el, (int, float)) for el in xerr_lists)): # ydata is list of lists, xerr_data is a single list. Assume same xerr_data for each set of ydata xerr2 = [] for i in range(nds): xerr2.append(xerr_lists) xerr_lists = xerr2 if draw_error_boxes: errorbox_xdata_l2 = [] errorbox_xdata_r2 = [] for i in range(nds): errorbox_xdata_l2.append(errorbox_xdata_l) errorbox_xdata_r2.append(errorbox_xdata_r) errorbox_xdata_l = errorbox_xdata_l2 errorbox_xdata_r = errorbox_xdata_r2 fst = title_fs #16 fs = axis_fs #14 y_min = 1.0e10 # later used to set y-axis minimum y_max = 1.0e-14 # later used to set y-axis maximum x_min = 1.0e5 # later used to set x-axis minimum x_max = 1.0e1 # later used to set x-axis maximum plt.rc('font', family=f_family, style=f_style, variant=f_variant, weight=f_weight) if fig==None: fig = plt.figure(figi) #bg_color = '#FFFFFF' #'#E1E4E6' #fig.patch.set_facecolor(bg_color) #fig.patch.set_alpha(1.0) ax = plt.subplot(int(spnrows), int(spncols), int(spindex)) for i in range(nds): xdata = xdata_lists[i] ydata = np.array(ydata_lists[i]) xerr=None yerr=None xerr_present = False yerr_present = False if len(xerr_lists[0])>0: xerr = xerr_lists[i] if np.sum(xerr)==0: xerr = None else: xerr_present = True if len(yerr_lists[0])>0: yerr = yerr_lists[i] if np.sum(yerr)==0: yerr = None else: yerr_present = True if include_legend: label_str = data_labels[i] else: label_str = '' # Get settings which may be constant or vary by dataset (lists) if isinstance(color, (list,np.ndarray)): c = color[i] else: c = color if isinstance(alpha, (list,np.ndarray)): alp = alpha[i] else: alp = alpha if isinstance(linestyle, (list,np.ndarray)): ls = linestyle[i] else: ls = linestyle if isinstance(linewidth, (list,np.ndarray)): lw = linewidth[i] else: lw = linewidth if isinstance(marker, (list,np.ndarray)): mkr = marker[i] else: mkr = marker if isinstance(markersize, (list,np.ndarray)): mks = markersize[i] else: mks = markersize if isinstance(errorstyle, (list,np.ndarray)): ers = errorstyle[i] else: ers = errorstyle if isinstance(error_band_opacity, (list,np.ndarray)): ebo = error_band_opacity[i] else: ebo = error_band_opacity if isinstance(elinewidth, (list,np.ndarray)): elw = elinewidth[i] else: elw = elinewidth if isinstance(capsize, (list,np.ndarray)): ecs = capsize[i] else: ecs = capsize if isinstance(markerfacecolor, (list,np.ndarray)): mfc = markerfacecolor[i] else: mfc = markerfacecolor if isinstance(markeredgecolor, (list,np.ndarray)): mec = markeredgecolor[i] else: mec = markeredgecolor if isinstance(markeredgewidth, (list,np.ndarray)): mew = markeredgewidth[i] else: mew = markeredgewidth # Make actual plot if (not xerr_present and not yerr_present) or (ers=='band' and not xerr_present): if color=='#FDFEFC' or color[0]=='#FDFEFC': # assume user will never actually want/input this specific white color p = ax.plot(xdata,ydata,label=label_str,ls=ls,lw=lw,marker=mkr,ms=mks,mfc=mfc,mec=mec,mew=mew,alpha=alp) else: p = ax.plot(xdata,ydata,label=label_str,c=c,ls=ls,lw=lw,marker=mkr,ms=mks,mfc=mfc,mec=mec,mew=mew,alpha=alp) else: if color=='#FDFEFC' or color[0]=='#FDFEFC': # assume user will never actually want/input this specific white color p = ax.errorbar(xdata,ydata,xerr=xerr,yerr=yerr,label=label_str,ls=ls,lw=lw,marker=mkr,ms=mks,elinewidth=elw,capsize=ecs,mfc=mfc,mec=mec,mew=mew,alpha=alp) else: p = ax.errorbar(xdata,ydata,xerr=xerr,yerr=yerr,label=label_str,c=c,ls=ls,lw=lw,marker=mkr,ms=mks,elinewidth=elw,capsize=ecs,mfc=mfc,mec=mec,mew=mew,alpha=alp) if (ers=='bar-band' or ers=='band') and yerr_present: if color=='#FDFEFC' or color[0]=='#FDFEFC': # assume user will never actually want/input this specific white color c = p[0].get_color() # need to grab whatever color was just used if len(np.shape(yerr))==1: ax.fill_between(xdata, np.array(ydata)-np.array(yerr), np.array(ydata)+np.array(yerr),color=c,alpha=ebo) else: ax.fill_between(xdata, np.array(ydata)-np.array(yerr[0,:]), np.array(ydata)+np.array(yerr[1,:]),color=c,alpha=ebo) if draw_error_boxes: draw_error_box_for_this_dataset = True # Ensure x and y error arrays are correctly sized, accounting for possible datasets without error boxes # determine which, if either, x errors are 'None' if (errorbox_xdata_l!=[[]] and errorbox_xdata_r==[[]]): erb_x_l = errorbox_xdata_l[i] if len(erb_x_l) != len(ydata): draw_error_box_for_this_dataset = False else: erb_x_r = 0*np.array(erb_x_l) elif (errorbox_xdata_l==[[]] and errorbox_xdata_r!=[[]]): erb_x_r = errorbox_xdata_r[i] if len(erb_x_r) != len(ydata): draw_error_box_for_this_dataset = False else: erb_x_l = 0*np.array(erb_x_r) else: # both datasets possibly present erb_x_l = errorbox_xdata_l[i] erb_x_r = errorbox_xdata_r[i] if len(erb_x_l) != len(ydata) and len(erb_x_r) != len(ydata): draw_error_box_for_this_dataset = False elif len(erb_x_l) != len(ydata) or len(erb_x_r) != len(ydata): if len(erb_x_l) != len(ydata): erb_x_l = 0*np.array(erb_x_r) elif len(erb_x_r) != len(ydata): erb_x_r = 0*np.array(erb_x_l) # determine which, if either, y errors are 'None' if (errorbox_ydata_l!=[[]] and errorbox_ydata_u==[[]]): erb_y_l = errorbox_ydata_l[i] if len(erb_y_l) != len(ydata): draw_error_box_for_this_dataset = False else: erb_y_u = 0*np.array(erb_y_l) elif (errorbox_ydata_l==[[]] and errorbox_ydata_u!=[[]]): erb_y_u = errorbox_ydata_u[i] if len(erb_y_u) != len(ydata): draw_error_box_for_this_dataset = False else: erb_y_l = 0*np.array(erb_y_u) else: # both datasets possibly present erb_y_l = errorbox_ydata_l[i] erb_y_u = errorbox_ydata_u[i] if len(erb_y_l) != len(ydata) and len(erb_y_u) != len(ydata): draw_error_box_for_this_dataset = False elif len(erb_y_l) != len(ydata) or len(erb_y_u) != len(ydata): if len(erb_y_l) != len(ydata): erb_y_l = 0*np.array(erb_y_u) elif len(erb_y_u) != len(ydata): erb_y_u = 0*np.array(erb_y_l) if draw_error_box_for_this_dataset: xerrbox = [erb_x_l,erb_x_r] yerrbox = [erb_y_l,erb_y_u] # Get settings which may be constant or vary by dataset (lists) if isinstance(errorbox_fc, (list,np.ndarray)): efc = errorbox_fc[i] else: efc = errorbox_fc if isinstance(errorbox_ec, (list,np.ndarray)): eec = errorbox_ec[i] else: eec = errorbox_ec if isinstance(errorbox_ea, (list,np.ndarray)): eea = errorbox_ea[i] else: eea = errorbox_ea if isinstance(errorbox_fa, (list,np.ndarray)): efa = errorbox_fa[i] else: efa = errorbox_fa if isinstance(errorbox_ew, (list,np.ndarray)): eew = errorbox_ew[i] else: eew = errorbox_ew makeErrorBoxes(ax,xdata,ydata,xerrbox,yerrbox,fc='None',ec=eec ,alpha=eea,lw=eew) # outline makeErrorBoxes(ax,xdata,ydata,xerrbox,yerrbox,fc=efc ,ec='None',alpha=efa,lw=eew) # fill face if len(ydata) != 0: if all([yi==None for yi in ydata]): print("\t\tfancy_plot warning: Encountered set of only 'None' at index {}".format(i)) elif len(ydata[np.nonzero(ydata)]) != 0: if min(ydata[np.nonzero(ydata)])<y_min: y_min = min(ydata[np.nonzero(ydata)]) #if min(ydata)<y_min: y_min = min(ydata) if max(ydata[ydata!=None])>y_max: y_max = max(ydata[ydata!=None]) if min(xdata)<x_min: x_min = min(xdata) if max(xdata)>x_max: x_max = max(xdata) if title_str.strip() != '': window_title = slugify(title_str) # "comparison_fig" else: window_title = 'Figure ' + str(figi) #window_title = window_title.replace('b','',1) # remove leading 'b' character from slugify process fig.canvas.manager.set_window_title(window_title) # hangle figure/legend positioning/sizing # First, figure size default_fig_x_in = fig_width_inch default_fig_y_in = fig_height_inch fig_x_in = default_fig_x_in fig_y_in = default_fig_y_in fig.set_size_inches(fig_x_in,fig_y_in) mpl_leg_pos_names = ['best','upper right','upper left','lower left','lower right','right','center left','center right','lower center','upper center','center'] custom_leg_pos_names = ['outside right', 'outside bottom'] if include_legend and legend_position in custom_leg_pos_names: if legend_anchor==None: if legend_position=='outside right': legend_anchor = (1.0,0.75) elif legend_position=='outside bottom': legend_anchor = (0.5,-0.17) leg1_anchor = legend_anchor # varied items handles_l1, labels_l1 = ax.get_legend_handles_labels() if legend_position == 'outside right': legend1 = ax.legend(handles_l1, labels_l1,loc='upper left',bbox_to_anchor=leg1_anchor,ncol=legend_ncol,framealpha=legend_alpha,columnspacing=legend_columnspacing) elif legend_position == 'outside bottom': if legend_ncol == 1 and len(data_labels) > 1: legend_ncol = len(data_labels) legend1 = ax.legend(handles_l1, labels_l1,loc='upper center',bbox_to_anchor=leg1_anchor,ncol=legend_ncol,framealpha=legend_alpha,columnspacing=legend_columnspacing) ax.add_artist(legend1) fig.canvas.draw() f1 = legend1.get_frame() l1_w0_px, l1_h0_px = f1.get_width(), f1.get_height() l_w0_in, l_h0_in = l1_w0_px/fig.dpi, l1_h0_px/fig.dpi # width and height of legend, in inches else: l_w0_in, l_h0_in = 0.0, 0.0 if include_legend and legend_position not in custom_leg_pos_names: # use matplotlib default-style legend inside plot area ax.legend(loc=legend_position,ncol=legend_ncol,framealpha=legend_alpha,columnspacing=legend_columnspacing) n_title_lines = 0 if title_str.strip() != '': n_title_lines = 1 + title_str.count('\n') n_xlabel_lines = 0 if x_label_str.strip() != '': n_xlabel_lines = 1 + x_label_str.count('\n') n_ylabel_lines = 0 if y_label_str.strip() != '': n_ylabel_lines = 1 + y_label_str.count('\n') # These values are good, do not change them. (derived while working on SHAEDIT project) x0bar = 0.60 + 0.200*n_ylabel_lines # inches, horizontal space needed for ylabel y0bar = 0.45 + 0.200*n_xlabel_lines # inches, vertical space needed for xticks/numbers, xlabel and any extra lines it has t0bar = 0.10 + 0.300*n_title_lines # inches, vertical space needed for title del_l_in = 0.15 # inches, extra horizontal padding right of legend # adjust legend spacing depending on its position if legend_position=='outside right': l_h0_in = 0.0 elif legend_position=='outside bottom': l_w0_in = 0.0 # Plot window placement and sizing x0 = x0bar/fig_x_in # distance from left edge that plot area begins y0 = y0bar/fig_y_in + (l_h0_in/fig_y_in) # distance from bottom edge that plot area begins h0 = 1 - (y0bar+t0bar)/fig_y_in - (l_h0_in/fig_y_in) # height of plot area, set to be full height minus space needed for title, x-label, and potentially an outside bottom legend w0 = 1 - x0 - (l_w0_in/fig_x_in) - (del_l_in/fig_x_in) # width of plot area, set to be full width minus space needed for y-label and potentially an outside right legend if man_sp_placement: if spx0!=None: x0 = spx0 if spy0!=None: y0 = spy0 if sph0!=None: h0 = sph0 if spw0!=None: w0 = spw0 # Set size and location of the plot on the canvas box = ax.get_position() # all vals in [0,1]: left, bottom, width, height if not man_sp_placement and (spnrows != 1 or spncols != 1): pstr = 'Warning: It is highly encouraged that subplots be positioned manually.\n' pstr += ' This is done by setting man_sp_placement=True and then adjusting\n' pstr += ' the parameters spx0, spy0, sph0, and spw0 for each subplot.\n' pstr += ' The current plot was automatically sized by matplotlib.\n' print(pstr) else: ax.set_position([x0, y0, w0, h0]) plt.title(title_str,fontsize=fst) plt.xlabel(x_label_str,fontsize=fs) plt.ylabel(y_label_str,fontsize=fs) plt.xscale(x_scale) plt.yscale(y_scale) plt.grid(b=True, which='major', linestyle='-', alpha=0.25) plt.grid(b=True, which='minor', linestyle='-', alpha=0.10) # ensure at least minimum number of decades are present on a plot by increasing padding if necessary zoom_mult = 1.0 x_log_buffer = 0.15*zoom_mult y_log_buffer = 0.2*zoom_mult min_x_decs = 2 min_y_decs = 2 x_scale='linear' if not x_limits: if x_scale == 'log': # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if (np.log10(x_max)-np.log10(x_min)+2*x_log_buffer) < min_x_decs: x_log_buffer = 0.5*(min_x_decs - (np.log10(x_max)-np.log10(x_min))) plt.xlim([10**(np.log10(x_min)-x_log_buffer),10**(np.log10(x_max)+x_log_buffer)]) else: plt.xlim(x_limits) if not y_limits: if y_scale == 'log': # use fancy code to determine bounds, otherwise, let matplotlib automatically generate boundaries if (np.log10(y_max)-np.log10(y_min)+2*y_log_buffer) < min_y_decs: y_log_buffer = 0.5*(min_y_decs - (np.log10(y_max)-np.log10(y_min))) plt.ylim([10**(np.log10(y_min)-y_log_buffer),10**(np.log10(y_max)+y_log_buffer)]) else: plt.ylim(y_limits) return fig, ax  def fetch_MC_material(matid=None, matname=None, matsource=None, concentration_type=None, particle=None)  ## Description Returns a materials definition string formatted for use in MCNP or PHITS ## Dependencies • import os • import pickle • PYTHONPATH environmental variable must be set and one entry must contain the directory which contains the vital "MC_tools/materials/Hunters_MC_materials.pkl" file. ## Inputs (required to enter matid OR matname, with matid taking priority if conflicting) • matid = ID number in the "Hunters_MC_materials" file • matname = exact name of material in "Hunters_MC_materials" file • matsource = exact source of material in "Hunters_MC_materials" file, only used when multiple materials have identical names • concentration_type = selection between 'weight fraction' (default if no formula) and 'atom fraction' (default if formula present) to be returned • particle = selection of whether natural ('photons', default) or isotopic ('neutrons') elements are used Note that if "enriched" or "depleted" appears in the material's name, particle='neutrons' is set automatically. ## Outputs • mat_str = string containing the material's information, ready to be inserted directly into a MCNP/PHITS input deck Expand source code def fetch_MC_material(matid=None,matname=None,matsource=None,concentration_type=None,particle=None): ''' Description: Returns a materials definition string formatted for use in MCNP or PHITS Dependencies: - import os - import pickle - PYTHONPATH environmental variable must be set and one entry must contain the directory which contains the vital "MC_tools/materials/Hunters_MC_materials.pkl" file. Inputs: (required to enter matid OR matname, with matid taking priority if conflicting) - matid = ID number in the "Hunters_MC_materials" file - matname = exact name of material in "Hunters_MC_materials" file - matsource = exact source of material in "Hunters_MC_materials" file, only used when multiple materials have identical names - concentration_type = selection between 'weight fraction' (default if no formula) and 'atom fraction' (default if formula present) to be returned - particle = selection of whether natural ('photons', default) or isotopic ('neutrons') elements are used Note that if "enriched" or "depleted" appears in the material's name, particle='neutrons' is set automatically. Outputs: - mat_str = string containing the material's information, ready to be inserted directly into a MCNP/PHITS input deck ''' if not matid and not matname: print('Either "matid" or "matname" MUST be defined') return None # First, locate and open materials library try: user_paths = os.environ['PYTHONPATH'].split(os.pathsep) lib_file = None for i in user_paths: if 'personal Python scripts' in i: lib_file = i + r"\MC_tools\materials\Hunters_MC_materials" if not lib_file: print('Could not find "personal Python scripts" folder in PYTHONPATH; this folder contains the vital "MC_tools/materials/Hunters_MC_materials.pkl" file.') except KeyError: print('The PYTHONPATH environmental variable must be defined and contain the path to the directory holding "MC_tools/materials/Hunters_MC_materials.pkl"') return None # Load materials library def load_obj(name ): with open(name + '.pkl', 'rb') as f: return pickle.load(f) all_mats_list = load_obj(lib_file) if matid: # use mat ID number mi = int(matid)-1 matname = all_mats_list[mi]['name'] else: # use material name and possibly source too # determine material mi = None # first check for exact matches matching_mi = [] for i in range(len(all_mats_list)): if all_mats_list[i]['name'].lower()==matname.lower(): matching_mi.append(i) if len(matching_mi)==1: mi = matching_mi[0] elif len(matching_mi)>1: print('Found multiple materials with this identical matname value:') for mmi in matching_mi: print('\tmatid={} matname="{}" source="{}"'.format(str(mmi+1),all_mats_list[mmi]['name'],all_mats_list[mmi]['source'])) if all_mats_list[mmi]['source'] and all_mats_list[mmi]['source']==matsource: mi = mmi print('\t\t^ matches inputed "matsource" and will be used') if mi==None: print('Please enter a "matsource" value identical to one of these two (or the matid).') return None else: # Exact material name not found # search for similar entries similar_mi = [] for i in range(len(all_mats_list)): if matname.lower() in all_mats_list[i]['name'].lower(): similar_mi.append(i) if len(similar_mi)==0: print('No materials with that exact name or names containing "matname" were found.') return None elif len(similar_mi)==1: mi = similar_mi[0] print('Found one similar material (matid={} matname="{}" source="{}"); using it.'.format(str(mi+1),all_mats_list[mi]['name'],all_mats_list[mi]['source'])) else: print('Found no material with exact "matname" but {} with similar names:'.format(len(similar_mi))) for smi in similar_mi: print('\tmatid={} matname="{}" source="{}"'.format(str(smi+1),all_mats_list[smi]['name'],all_mats_list[smi]['source'])) print('The first of these will be used. If another material was desired, please enter its "matid" or exact "matname".') mi = similar_mi[0] # Now that material ID has been found, generate text entry mat = all_mats_list[mi] banner_width = 60 cc = '$'  # comment character

entry_text  = '\n'+cc+'*'*banner_width + '\n'
entry_text += cc+'  {:<3d} : {} \n'.format(mi+1,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'])

if concentration_type==None: # user did not select this, determine which is more appropriate automatically
if mat['formula'] and mat['formula']!='-':
concentration_type = 'atom fraction'
else:
concentration_type = 'weight fraction'

entry_text += cc+'  Composition by {} \n'.format(concentration_type)

# Determine if neutron or photon entry will be used
neutron_keyword_list = ['depleted','enriched',' heu',' leu','uranium','plutonium','uranyl']
if particle==None: # user did not select this, determine which is more appropriate automatically
neutron_kw_found_in_name = False
for nki in neutron_keyword_list:
if nki in matname.lower():
neutron_kw_found_in_name = True
if neutron_kw_found_in_name:
particle = 'neutrons'
else:
particle = 'photons'

for j in range(len(mat[particle][concentration_type]['ZA'])):

if isinstance(mat[particle][concentration_type]['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(mi+1)
else:
mstr = ' '*4

ZZZAAA = mat[particle][concentration_type]['ZA'][j]
if ZZZAAA == '-':
ZZZAAA = mat['photons'][concentration_type]['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[particle][concentration_type]['value'][j],isotope)
entry_text  += cc+'*'*banner_width + '\n'

return entry_text
 def find(target, myList) 

## Description

Search for and return the index of the first occurance of a value in a list.

## Inputs

• target = value to be searched for
• myList = list of values

## Output

• index of first instance of target in myList
Expand source code
def find(target, myList):
'''
Description:
Search for and return the index of the first occurance of a value in a list.

Inputs:
- target = value to be searched for
- myList = list of values

Output:
- index of first instance of target in myList
'''
for i in range(len(myList)):
if myList[i] == target:
return i
 def fit_distribution(x, y, dist_name='gauss', a0=None, mu0=None, sigma0=None) 

## Description

Determine best fit parameters and quality of fit provided test x and y values, the name of the ditribution to be fit, and initial guesses of its parameters.
If initial guesses are omitted, they will try to be automatically assessed (your mileage may vary).

## Dependencies

from munch import * from scipy.optimize import curve_fit

## Inputs

• x = list/array of x values to be fit
• y = list/array of y values to be fit
• dist_name = string denoting the distribution used (D='Gaussian'), options include: ['gauss','normal','Logistic','sech'], Read more here on the Gaussian/normal, Logistic, and Hyperbolic secant distributions.
• a0 = initial guess of the distribution amplitude/height parameter
• mu0 = initial guess of the distribution mean/center parameter
• sigma0 = initial guess of the distribution width parameter

## Notes

At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to amplitude/height, x position/centering, and width.

## Outputs

• y_fit = list/array of evaluated y values using the optimally found fit parameters
• dist_info = dictionary object containing the distribution short_name and full_name; optimized fit parameters a, mu, and sigma; calculated FWHM, r2/r_squared() R^2, and chi2/chi_squared() values; Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str
Expand source code
def fit_distribution(x,y,dist_name='gauss',a0=None,mu0=None,sigma0=None):
'''
Description:
Determine best fit parameters and quality of fit provided test x and y values, the name of the ditribution to be fit, and initial guesses of its parameters.
If initial guesses are omitted, they will try to be automatically assessed (your mileage may vary).

Dependencies:
from munch import *
from scipy.optimize import curve_fit

Inputs:
- x = list/array of x values to be fit
- y = list/array of y values to be fit
- dist_name = string denoting the distribution used (D='Gaussian'), options include:
['gauss','normal','Logistic','sech'], Read more here on the [Gaussian/normal](https://en.wikipedia.org/wiki/Normal_distribution),
[Logistic](https://en.wikipedia.org/wiki/Logistic_distribution), and
[Hyperbolic secant](https://en.wikipedia.org/wiki/Hyperbolic_secant_distribution) distributions.
- a0 = initial guess of the distribution amplitude/height parameter
- mu0 = initial guess of the distribution mean/center parameter
- sigma0 = initial guess of the distribution width parameter

Notes:
At present, this function is only designed with "bell-shaped" distributions describable with three parameters related to
amplitude/height, x position/centering, and width.

Outputs:
- y_fit = list/array of evaluated y values using the optimally found fit parameters
- dist_info = dictionary object containing the distribution short_name and full_name; optimized fit parameters a, mu, and sigma;
calculated FWHM, r2/r_squared R^2, and chi2/chi_squared values; Python string of the function fcn_py_str; and LaTex string of the function fcn_tex_str
'''
dist_names_list = ['gauss','normal','Logistic','sech']
dist_name = dist_name.lower()
if dist_name not in dist_names_list:
print('Selected distribution name, ',dist_name,' is not in the list of allowed distribution names: ',dist_names_list,'\n exiting function... Please pick from this list and try again.')
return None
if type(x) is list: x = np.array(x)
if type(y) is list: y = np.array(y)

def gaus(x,a,mu,sigma):
f = a*np.exp(-(x-mu)**2/(2*sigma**2))
return f
def logistic_dist(x,a,mu,sigma):
f = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))**2
return f
def hyperbolic_secant_dist(x,a,mu,sigma):
f = (a/(4*sigma))*(1/np.cosh((x-mu)/(2*sigma)))
return f

if dist_name=='gauss' or dist_name=='normal':
fit_fcn = gaus
elif dist_name=='logistic':
fit_fcn = logistic_dist
else: #if dist_name=='sech'
fit_fcn = hyperbolic_secant_dist

n = len(x)
ymax=max(y)
mean = sum(x*y/ymax)/n
sigma = sum((y/ymax)*(x-mean)**2)/n

if a0==None: a0 = ymax
if mu0==None: mu0 = mean
if sigma0==None: sigma0 = sigma
popt,pcov = curve_fit(fit_fcn,x,y,p0=[a0,mu0,sigma0])
a, mu, sigma = popt[0], popt[1], popt[2]
FWHM_fit = 2*np.sqrt(2*np.log(2))*sigma

y_fit, dist_info = eval_distribution(x, dist_name=dist_name, a=a, mu=mu, sigma=sigma)
r2 = r_squared(y,y_fit)

#chi2,red_chi2 = chi_squared(y,y_fit,num_fit_params=3)
num_fit_params=3
chi2,p_chi2,ndf_chi2 = chi_squared(y,y_fit,num_fit_params=num_fit_params)
# How to write chi2 results: https://www.socscistatistics.com/tutorials/chisquare/default.aspx
if p_chi2<0.001:
chi_p_str = 'p<0.001'
else:
chi_p_str = 'p = {:4g}'
chi2_tex_str = r'$\chi^2$'+'({:},N={:})={:.3g}, {}'.format(num_fit_params,n,chi2,chi_p_str)

dist_info.update({
'FWHM':FWHM_fit,
'r2':r2,
'r_squared':r2,
'chi2':chi2,
'chi_squared':chi2,
'p_chi2':p_chi2,
'ndf_chi2':ndf_chi2,
'chi2_tex_str':chi2_tex_str
#'red_chi2':red_chi2,
#'red_chi_squared':red_chi2,
})

return y_fit, dist_info
 def fractional_difference(v1, v2) 

## Description

Calculate fractional difference between two values (neither of more merit than the other)

## Inputs

• v1 = first value (float ot int)
• v2 = second value (float ot int)

## Outputs

• fdiff = fractional difference between the two values
Expand source code
def fractional_difference(v1,v2):
'''
Description:
Calculate fractional difference between two values (neither of more merit than the other)
Inputs:
- v1 = first value (float ot int)
- v2 = second value (float ot int)
Outputs:
- fdiff = fractional difference between the two values
'''
if v1 != 0 and v2 != 0:
fdiff = abs(v1-v2)/(0.5*(v1+v2))
elif (v1+v2) != 0: # only one is nonzero, 100% difference
fdiff = 1
else: # both are zero
fdiff = 0
return fdiff
 def fractional_error(test_val, ref_val) 

## Description

Calculate fractional error of an experimental test value relative to a reference value

## Inputs

• test_val = new/tested/experimental value (float ot int)
• ref_val = old/established/reference value (float ot int)

## Outputs

• ferr = fractional error of the new value from the old one
Expand source code
def fractional_error(test_val,ref_val):
'''
Description:
Calculate fractional error of an experimental test value relative to a reference value
Inputs:
- test_val = new/tested/experimental value (float ot int)
- ref_val = old/established/reference value (float ot int)
Outputs:
- ferr = fractional error of the new value from the old one
'''
if ref_val != 0:
ferr = (test_val-ref_val)/ref_val
elif test_val == 0: # both are zero
ferr = 0
else: # only one is zero, 100% error
ferr = 1
#ferr = 0 # changed to zero because I'd rather reject points where no comparison can be made
return ferr
 def generate_line_bar_coordinates(xbins, yvals, yerrs=[]) 

## Description

Converts a set of bin boundaries and bin contents to coordinates mapping a bar plot if drawn with a line

## Inputs

• xbins = list of length N+1 bin boundary values
• yvals = list of length N bin content values
• yerrs = (optional) list of length N absolute uncertainties of bin content values

## Outputs

• newx = list of length 2N + 2 of x-coordinates mapping a 'bar plot' of the input histogram data
• newy = list of length 2N + 2 of y-coordinates mapping a 'bar plot' of the input histogram data
• newyerr = (optional) list of length 2N + 2 of y-coordinates mapping a 'bar plot' of the input histogram data
Expand source code
def generate_line_bar_coordinates(xbins,yvals,yerrs=[]):
"""
Description:
Converts a set of bin boundaries and bin contents to coordinates mapping a bar plot if drawn with a line

Inputs:
- xbins = list of length N+1 bin boundary values
- yvals = list of length N bin content values
- yerrs = (optional) list of length N absolute uncertainties of bin content values

Outputs:
- newx = list of length 2N + 2 of x-coordinates mapping a 'bar plot' of the input histogram data
- newy = list of length 2N + 2 of y-coordinates mapping a 'bar plot' of the input histogram data
- newyerr = (optional) list of length 2N + 2 of y-coordinates mapping a 'bar plot' of the input histogram data
"""
if len(yvals) != (len(xbins)-1):
pstr = 'xbins should be a list of bin edges of length one more than yvals, the values associated with the contents of each bin' + '\n'
pstr += 'provided input arrays had lengths of {} for xbins and {} for yvals'.format(str(len(xbins)),str(len(yvals)))
print(pstr)
return 0
newx = [xbins[0],xbins[0]]
newy = [0,yvals[0]]
if len(yerrs)!=0: newyerr = [0,yvals[0]]
for i in range(len(xbins)-2):
newx.append(xbins[i+1])
newx.append(xbins[i+1])
newy.append(yvals[i])
newy.append(yvals[i+1])
if len(yerrs)!=0:
newyerr.append(yerrs[i])
newyerr.append(yerrs[i+1])
newx.append(xbins[-1])
newx.append(xbins[-1])
newy.append(yvals[-1])
newy.append(0)
if len(yerrs)!=0:
newyerr.append(yerrs[-1])
newyerr.append(0)
return newx, newy, newyerr
else:
return newx, newy
 def get_colormap(cmap_str) 

## Description

Retrieve a matplotlib colormap using just its string name
See available colormaps: https://matplotlib.org/3.3.1/tutorials/colors/colormaps.html

## Dependencies

• from matplotlib import cm

## Inputs

• cmap_str = string of name of colormap

## Outputs

• callable colormap object which can be provided to plotting functions or evaluated for any value from 0 to 1
Expand source code
def get_colormap(cmap_str):
'''
Description:
Retrieve a matplotlib colormap using just its string name
See available colormaps: https://matplotlib.org/3.3.1/tutorials/colors/colormaps.html

Dependencies:
- from matplotlib import cm

Inputs:
- cmap_str = string of name of colormap

Outputs:
- callable colormap object which can be provided to plotting functions or evaluated for any value from 0 to 1
'''
return cm.get_cmap(cmap_str)
 def hex_to_rgb(hexcol, opacity=1.0, out_of_one=False) 

## Description

Return (red, green, blue, opacity) for the color given as #rrggbb and optional opacity

## Inputs

• hexcol = string of hexidecimal color formatted as #rrggbb
• opacity = float between 0 and 1 specifying color opacity (D=1.0)
• out_of_one = bool which toggles whether output will be out of 255 (default, =False) or 1 (=True) useful if you'd rather have white be (255,255,255) (default) or (1,1,1)

## Outputs

• tuple containing RGB values and opacity, (red, green, blue, opacity)
Expand source code
def hex_to_rgb(hexcol,opacity=1.0,out_of_one=False):
'''
Description:
Return (red, green, blue, opacity) for the color given as #rrggbb and optional opacity

Inputs:
- hexcol = string of hexidecimal color formatted as #rrggbb
- opacity = float between 0 and 1 specifying color opacity (D=1.0)
- out_of_one = bool which toggles whether output will be out of 255 (default, =False) or 1 (=True)
useful if you'd rather have white be (255,255,255) (default) or (1,1,1)

Outputs:
- tuple containing RGB values and opacity,  (red, green, blue, opacity)
'''
divisor = 1
if out_of_one: divisor = 255
value = hexcol.lstrip('#')
lv = len(value)
rgb = (int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
rgb = tuple(rgb)
rgbo = [rgb[0]/divisor,rgb[1]/divisor,rgb[2]/divisor,opacity]
return tuple(rgbo)
 def humansize(nbytes) 

## Description

Provided an integer describing a number of bytes, return a string in more human-readable units

## Dependencies

import numpy as np

## Input

• nbytes = integer number of bytes (B)

## Output

• string containing the number converted to a human-readable number in *B
Expand source code
def humansize(nbytes):
'''
Description:
Provided an integer describing a number of bytes, return a string in more human-readable units

Dependencies:
import numpy as np

Input:
- nbytes = integer number of bytes (B)

Output:
- string containing the number converted to a human-readable number in *B
'''
suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
i = 0
while nbytes >= 1024 and i < len(suffixes)-1:
nbytes /= 1024.
i += 1
f = ('%.2f' % nbytes).rstrip('0').rstrip('.')
return '%s %s' % (f, suffixes[i])
 def makeErrorBoxes(ax, xdata, ydata, xerror, yerror, fc='None', ec='k', alpha=1.0, lw=0.5) 

## Description

Generate uncertainty/error "boxes" which are overlaid on points

## Dependencies

• import numpy as np
• import matplotlib.pyplot as plt
• from matplotlib.collections import PatchCollection
• from matplotlib.patches import Rectangle

## Inputs

(required)

• ax = axis handles onto which error boxes will be drawn
• xdata = a list/array of x data
• ydata = a list/array of y data
• xerror = a list/array of 2 lists/arrays of x absolute uncertainties as [x_lower_errors, x_upper_errors]
• yerror = a list/array of 2 lists/arrays of y absolute uncertainties as [y_lower_errors, y_upper_errors]

## Inputs

(optional)

• fc = face color of boxes (D=None)
• ec = edge color of boxes (D='k', black)
• alpha = opacity of box filling (D=1.0)
• lw = line width of box edge (D=0.5)

## Notes

For best results, repeat this function twice, first rendering the edges and then a second time for the filling as shown below: makeErrorBoxes(xdata,ydata,xerrbox,yerrbox,fc='None',ec=nx_color,alpha=1.0,lw=0.5) makeErrorBoxes(xdata,ydata,xerrbox,yerrbox,fc=nx_color,ec='None',alpha=0.1,lw=0.5)

Expand source code
def makeErrorBoxes(ax,xdata,ydata,xerror,yerror,fc='None',ec='k',alpha=1.0,lw=0.5):
'''
Description:
Generate uncertainty/error "boxes" which are overlaid on points

Dependencies:
- import numpy as np
- import matplotlib.pyplot as plt
- from matplotlib.collections import PatchCollection
- from matplotlib.patches import Rectangle

Inputs:
(required)

- ax = axis handles onto which error boxes will be drawn
- xdata = a list/array of x data
- ydata = a list/array of y data
- xerror = a list/array of 2 lists/arrays of x absolute uncertainties as [x_lower_errors, x_upper_errors]
- yerror = a list/array of 2 lists/arrays of y absolute uncertainties as [y_lower_errors, y_upper_errors]

Inputs:
(optional)

- fc = face color of boxes (D=None)
- ec = edge color of boxes (D='k', black)
- alpha = opacity of box filling (D=1.0)
- lw = line width of box edge (D=0.5)

Notes:
For best results, repeat this function twice, first rendering the edges and then a second time for the filling as shown below:
makeErrorBoxes(xdata,ydata,xerrbox,yerrbox,fc='None',ec=nx_color,alpha=1.0,lw=0.5)
makeErrorBoxes(xdata,ydata,xerrbox,yerrbox,fc=nx_color,ec='None',alpha=0.1,lw=0.5)
'''
xdata,ydata,xerror,yerror = np.array(xdata),np.array(ydata),np.array(xerror),np.array(yerror)
# Create list for all the error patches
errorboxes = []

# Loop over data points; create box from errors at each point
for xc,yc,xe,ye in zip(xdata,ydata,xerror.T,yerror.T):
rect = Rectangle((xc-xe[0],yc-ye[0]),xe.sum(),ye.sum())
errorboxes.append(rect)

# Create patch collection with specified colour/alpha
pc = PatchCollection(errorboxes,facecolor=fc,alpha=alpha,edgecolor=ec,linewidth=lw)

# Add collection to axes
ax.add_collection(pc)
 def nuclide_plain_str_ZZZAAAM(nuc_str) 

## Description

Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000*Z + 10*A + M

## Dependencies

Element_Z_to_Sym() (function within the "Hunter's tools" package)

## Input

• nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
• Isomeric/metastable state characters must always immediately follow the atomic mass characters. Isomeric state labels MUST either:
• (1) be a single lower-case character OR
• (2) begin with any non-numeric character and end with a number
• Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
• Elemental symbols MUST begin with an upper-case character

## Output

• ZZZAAAM integer
Expand source code
def nuclide_plain_str_to_ZZZAAAM(nuc_str):
'''
Description:
Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000\*Z + 10\*A + M

Dependencies:
Element_Z_to_Sym (function within the "Hunter's tools" package)

Input:
- nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
+ Isomeric/metastable state characters must always immediately follow the atomic mass characters.
Isomeric state labels MUST either:
- (1) be a single lower-case character OR
- (2) begin with any non-numeric character and end with a number
+ Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
+ Elemental symbols MUST begin with an upper-case character

Output:
- ZZZAAAM integer
'''

# remove unwanted characters from provided string
delete_characters_list = [' ', '-', '_']
for dc in delete_characters_list:
nuc_str = nuc_str.replace(dc,'')

# determine which characters are letters versus numbers
isalpha_list = []
isdigit_list = []
for c in nuc_str:
isalpha_list.append(c.isalpha())
isdigit_list.append(c.isdigit())

symbol = ''
mass = ''
isost = ''

if 'nat' in nuc_str:
print('Must specify a specific nuclide, not natural abundances')
return None

# string MUST begin with either mass number or elemental symbol
if isdigit_list[0]: # mass first
mass_first = True
else:
mass_first = False

if mass_first:
ci = 0
while isdigit_list[ci]:
mass += nuc_str[ci]
ci += 1
mass = str(int(mass)) # eliminate any extra leading zeros
# encountered a non-numeric character, end of mass
# now, determine if metastable state is listed or if element is listed next
# first, check to see if any other numerals are in string
lni = 0 # last numeral index
for i in range(ci,len(nuc_str)):
if isdigit_list[i]:
lni = i
if lni != 0:
# grab all characters between ci and last numeral as metastable state
isost = nuc_str[ci:lni+1]
ci = lni + 1
else: # no more numerals in string, now check for single lower-case letter
if isalpha_list[ci] and nuc_str[ci].islower():
isost = nuc_str[ci]
ci += 1

# Now extract elemental symbol
for i in range(ci,len(nuc_str)):
if isalpha_list[i]:
symbol += nuc_str[i]

else: # if elemental symbol is listed first
ci = 0
# Extract all characters before first number as the elemental symbol
while nuc_str[ci].isalpha():
symbol += nuc_str[ci]
ci += 1

# now, extract mass
while nuc_str[ci].isdigit():
mass += nuc_str[ci]
ci += 1
if ci == len(nuc_str):
break

# lastly, extract isomeric state, if present
if ci != len(nuc_str):
isost = nuc_str[ci:]

# treating the cases of lowercase-specified particles (n, d, t, etc.)
if symbol == '' and isost != '':
symbol = isost
isost = ''

if symbol == 'n':
Z = 0
elif symbol == 'p' or symbol == 'd' or symbol == 't':
Z = 1
else:
Z = Element_Sym_to_Z(symbol)

A = int(mass)

if isost.strip()=='' or isost=='g':
M = 0
elif isost=='m' or isost=='m1':
M = 1
elif isost=='n' or isost=='m2':
M = 2
elif isost=='o' or isost=='m3':
M = 3
elif isost=='p' or isost=='m4':
M = 4
elif isost=='q' or isost=='m5':
M = 5
else:
print("Unknown isomeric state {}, assumed ground state".format(isost))
M = 0

ZZZAAAM = 10000*Z + 10*A + M

return ZZZAAAM
 def nuclide_plain_str_to_ZZZAAAM(nuc_str) 

## Description

Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000*Z + 10*A + M

## Dependencies

Element_Z_to_Sym() (function within the "Hunter's tools" package)

## Input

• nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
• Isomeric/metastable state characters must always immediately follow the atomic mass characters. Isomeric state labels MUST either:
• (1) be a single lower-case character OR
• (2) begin with any non-numeric character and end with a number
• Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
• Elemental symbols MUST begin with an upper-case character

## Output

• ZZZAAAM integer
Expand source code
def nuclide_plain_str_to_ZZZAAAM(nuc_str):
'''
Description:
Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000\*Z + 10\*A + M

Dependencies:
Element_Z_to_Sym (function within the "Hunter's tools" package)

Input:
- nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
+ Isomeric/metastable state characters must always immediately follow the atomic mass characters.
Isomeric state labels MUST either:
- (1) be a single lower-case character OR
- (2) begin with any non-numeric character and end with a number
+ Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
+ Elemental symbols MUST begin with an upper-case character

Output:
- ZZZAAAM integer
'''

# remove unwanted characters from provided string
delete_characters_list = [' ', '-', '_']
for dc in delete_characters_list:
nuc_str = nuc_str.replace(dc,'')

# determine which characters are letters versus numbers
isalpha_list = []
isdigit_list = []
for c in nuc_str:
isalpha_list.append(c.isalpha())
isdigit_list.append(c.isdigit())

symbol = ''
mass = ''
isost = ''

if 'nat' in nuc_str:
print('Must specify a specific nuclide, not natural abundances')
return None

# string MUST begin with either mass number or elemental symbol
if isdigit_list[0]: # mass first
mass_first = True
else:
mass_first = False

if mass_first:
ci = 0
while isdigit_list[ci]:
mass += nuc_str[ci]
ci += 1
mass = str(int(mass)) # eliminate any extra leading zeros
# encountered a non-numeric character, end of mass
# now, determine if metastable state is listed or if element is listed next
# first, check to see if any other numerals are in string
lni = 0 # last numeral index
for i in range(ci,len(nuc_str)):
if isdigit_list[i]:
lni = i
if lni != 0:
# grab all characters between ci and last numeral as metastable state
isost = nuc_str[ci:lni+1]
ci = lni + 1
else: # no more numerals in string, now check for single lower-case letter
if isalpha_list[ci] and nuc_str[ci].islower():
isost = nuc_str[ci]
ci += 1

# Now extract elemental symbol
for i in range(ci,len(nuc_str)):
if isalpha_list[i]:
symbol += nuc_str[i]

else: # if elemental symbol is listed first
ci = 0
# Extract all characters before first number as the elemental symbol
while nuc_str[ci].isalpha():
symbol += nuc_str[ci]
ci += 1

# now, extract mass
while nuc_str[ci].isdigit():
mass += nuc_str[ci]
ci += 1
if ci == len(nuc_str):
break

# lastly, extract isomeric state, if present
if ci != len(nuc_str):
isost = nuc_str[ci:]

# treating the cases of lowercase-specified particles (n, d, t, etc.)
if symbol == '' and isost != '':
symbol = isost
isost = ''

if symbol == 'n':
Z = 0
elif symbol == 'p' or symbol == 'd' or symbol == 't':
Z = 1
else:
Z = Element_Sym_to_Z(symbol)

A = int(mass)

if isost.strip()=='' or isost=='g':
M = 0
elif isost=='m' or isost=='m1':
M = 1
elif isost=='n' or isost=='m2':
M = 2
elif isost=='o' or isost=='m3':
M = 3
elif isost=='p' or isost=='m4':
M = 4
elif isost=='q' or isost=='m5':
M = 5
else:
print("Unknown isomeric state {}, assumed ground state".format(isost))
M = 0

ZZZAAAM = 10000*Z + 10*A + M

return ZZZAAAM
 def nuclide_plain_str_to_latex_str(nuc_str, include_Z=False) 

## Description

Converts a plaintext string of a nuclide to a LaTeX-formatted raw string Note: if you already have the Z, A, and isomeric state information determined, the "nuclide_to_Latex_form" function can be used instead

## Dependencies

• Element_Z_to_Sym() (function within the "Hunter's tools" package) (only required if include_Z = True)

## Input

(required)

• nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
• Isomeric/metastable state characters must always immediately follow the atomic mass characters. Isomeric state labels MUST either:
• (1) be a single lower-case character OR
• (2) begin with any non-numeric character and end with a number
• Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
• Elemental symbols MUST begin with an upper-case character

## Input

(optional)

• include_Z = True/False determining whether the nuclide's atomic number Z will be printed as a subscript beneath the atomic mass

## Output

• LaTeX-formatted raw string of nuclide
Expand source code
def nuclide_plain_str_to_latex_str(nuc_str,include_Z=False):
'''
Description:
Converts a plaintext string of a nuclide to a LaTeX-formatted raw string
Note: if you already have the Z, A, and isomeric state information determined, the "nuclide_to_Latex_form" function can be used instead

Dependencies:
- Element_Z_to_Sym (function within the "Hunter's tools" package) (only required if include_Z = True)

Input:
(required)

- nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
+ Isomeric/metastable state characters must always immediately follow the atomic mass characters.
Isomeric state labels MUST either:
- (1) be a single lower-case character OR
- (2) begin with any non-numeric character and end with a number
+ Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
+ Elemental symbols MUST begin with an upper-case character

Input:
(optional)

- include_Z = True/False determining whether the nuclide's atomic number Z will be printed as a subscript beneath the atomic mass

Output:
- LaTeX-formatted raw string of nuclide
'''
tex_str = r''

# remove unwanted characters from provided string
delete_characters_list = [' ', '-', '_']
for dc in delete_characters_list:
nuc_str = nuc_str.replace(dc,'')

# determine which characters are letters versus numbers
isalpha_list = []
isdigit_list = []
for c in nuc_str:
isalpha_list.append(c.isalpha())
isdigit_list.append(c.isdigit())

symbol = ''
mass = ''
isost = ''

# string MUST begin with either mass number or elemental symbol
if isdigit_list[0] or nuc_str[0:3]=='nat': # mass first
mass_first = True
else:
mass_first = False

if mass_first:
if nuc_str[0:3]=='nat':
mass = 'nat'
ci = 3
else:
ci = 0
while isdigit_list[ci]:
mass += nuc_str[ci]
ci += 1
mass = str(int(mass)) # eliminate any extra leading zeros
# encountered a non-numeric character, end of mass
# now, determine if metastable state is listed or if element is listed next
# first, check to see if any other numerals are in string
lni = 0 # last numeral index
for i in range(ci,len(nuc_str)):
if isdigit_list[i]:
lni = i
if lni != 0:
# grab all characters between ci and last numeral as metastable state
isost = nuc_str[ci:lni+1]
ci = lni + 1
else: # no more numerals in string, now check for single lower-case letter
if isalpha_list[ci] and nuc_str[ci].islower():
isost = nuc_str[ci]
ci += 1

# Now extract elemental symbol
for i in range(ci,len(nuc_str)):
if isalpha_list[i]:
symbol += nuc_str[i]

else: # if elemental symbol is listed first
if 'nat' in nuc_str:
mass = 'nat'
nuc_str = nuc_str.replace('nat','')

ci = 0
# Extract all characters before first number as the elemental symbol
while nuc_str[ci].isalpha():
symbol += nuc_str[ci]
ci += 1

# now, extract mass
if mass != 'nat':
while nuc_str[ci].isdigit():
mass += nuc_str[ci]
ci += 1
if ci == len(nuc_str):
break

# lastly, extract isomeric state, if present
if ci != len(nuc_str):
isost = nuc_str[ci:]

# treating the cases of lowercase-specified particles (n, d, t, etc.)
if symbol == '' and isost != '':
symbol = isost
isost = ''

# Now assemble LaTeX string for nuclides
if include_Z:
if symbol == 'n':
Z = 0
elif symbol == 'p' or symbol == 'd' or symbol == 't':
Z = 1
else:
Z = Element_Sym_to_Z(symbol)
Z = str(int(Z))
tex_str = r"$^{{{}{}}}_{{{}}}$".format(mass,isost,Z) + "{}".format(symbol)
else:
tex_str = r"$^{{{}{}}}$".format(mass,isost) + "{}".format(symbol)

return tex_str
 def nuclide_to_Latex_form(Z, A, m='') 

## Description

Form a LaTeX-formatted string of a nuclide provided its information

## Dependencies

Element_Z_to_Sym() (function within the "Hunter's tools" package) (only required if inputed Z is not already an elemental symbol)

## Inputs

• Z = atomic number of nuclide (int, float, or string) or elemental symbol (string)
• A = atomic mass of nuclide (int, float, or string) or string to go in place of A (ex. 'nat')
• m = metastable state (D='', ground state); this will be appended to the end of A if not a string already, it will be converted into one and appended to 'm' (ex. 1 -> 'm1')

## Outputs

• LaTeX-formatted raw string of a nuclide, excellent for plot titles, labels, and auto-generated LaTeX documents
Expand source code
def nuclide_to_Latex_form(Z,A,m=''):
'''
Description:
Form a LaTeX-formatted string of a nuclide provided its information

Dependencies:
Element_Z_to_Sym (function within the "Hunter's tools" package)
(only required if inputed Z is not already an elemental symbol)

Inputs:
- Z = atomic number of nuclide (int, float, or string) or elemental symbol (string)
- A = atomic mass of nuclide (int, float, or string) or string to go in place of A (ex. 'nat')
- m = metastable state (D='', ground state); this will be appended to the end of A
if not a string already, it will be converted into one and appended to 'm' (ex. 1 -> 'm1')

Outputs:
- LaTeX-formatted raw string of a nuclide, excellent for plot titles, labels, and auto-generated LaTeX documents
'''
if isinstance(A,(int,float)): A = str(int(A))
if not isinstance(Z,str): symbol = Element_Z_to_Sym(int(Z))
if isinstance(m,float): m = int(m)
if isinstance(m,int): m = 'm' + str(m)
latex_str = r"$^{{{}{}}}$".format(A,m) + "{}".format(symbol)
return latex_str
 def parse_dyld_files(path_to_dyld_file, iredufmt=None) 

## Description

Parses the output files of a T-Yield tally generated by PHITS with axis=dchain. This function assumes that the T-Yield tally was one automatically generated by and corresponding to a T-Dchain tally (axis=dchain). This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.

## Inputs

• path_to_dyld_file = path to the T-Yield tally output file to be parsed (the *_err.dyld file of the same name is automatically searched for and read, if present)
• iredufmt = (DEPRICATED; this is now determined automatically) integer 1 or 0 specifying how the xyz meshes are ordered relative to the internal region numbers. In the new format (1), region indices are incremented as x->y->z (x=innermost loop); this is reversed in the old format (0). Ultimately, this corresponds to the same iredufmt parameter in PHITS/DCHAIN, 1='new' and 0='old'. This variable is only used for xyz meshes where this ordering matters.

## Outputs

• yields = a RxNx2 array containing regionwise yields (and their absolute uncertainties) for all nuclides produced in T-Yield
• nuclide_names_yld = a length N list of all nuclide names in order
Expand source code
def parse_dyld_files(path_to_dyld_file,iredufmt=None):
'''
Description:
Parses the output files of a T-Yield tally generated by PHITS with axis=dchain.  This function assumes
that the T-Yield tally was one automatically generated by and corresponding to a T-Dchain tally (axis=dchain).
This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.

Inputs:
- path_to_dyld_file = path to the T-Yield tally output file to be parsed
(the *_err.dyld file of the same name is automatically searched for and read, if present)
- iredufmt = (DEPRICATED; this is now determined automatically)
integer 1 or 0 specifying how the xyz meshes are ordered relative to the internal region numbers.
In the new format (1), region indices are incremented as x->y->z (x=innermost loop); this is reversed in the old format (0).
Ultimately, this corresponds to the same iredufmt parameter in PHITS/DCHAIN, 1='new' and 0='old'.
This variable is only used for xyz meshes where this ordering matters.

Outputs:
- yields = a RxNx2 array containing regionwise yields (and their absolute uncertainties) for all nuclides produced in T-Yield
- nuclide_names_yld = a length N list of all nuclide names in order
'''

def ZAM_to_Dname(ZAM):
'''
Description:
Converts a ZZZAAAM number to a DCHAIN-formatted nuclide name

Inputs:
- ZZZAAAM = nuclide identification ineger, calculated as 10000\*Z + 10\*A + m

Outputs:
- Dname = nuclide identification string in DCHAIN format
'''
elms = ["n ",\
"H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
"Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
"Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
"Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
"Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
"Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
"Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
"Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
"Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
"Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
"Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
"Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
m = int(str(ZAM)[-1])
A = int(str(ZAM)[-4:-1])
Z = int(str(ZAM)[:-4])
sym = elms[Z]
A_str = '{:>3}'.format(A)
m_str_list = [' ','m','n']
m_str = m_str_list[m]
Dname = sym + A_str + m_str
return Dname

# Extract text from file
f = open(path_to_dyld_file)
file_text = f.read()
lines = file_text.split('\n')
f.close()

# determine if in reduced format
iredufmt=0
for li, line in enumerate(lines):
if "# num nucleusID yield r.err" in line:
iredufmt=1
break
if "isotope production #" in line:
iredufmt=0
break

# Get error data if available
if iredufmt==0:
try:
f_err = open(path_to_dyld_file.replace('.dyld','_err.dyld'))
file_text_err = f_err.read()
lines_err = file_text_err.split('\n')
f_err.close()
err_dyld_found = True
except:
file_text_err = None
lines_err = None
err_dyld_found = False

# Determine geometry type (mesh = reg, xyz, or tet)
for line in lines:
if 'mesh =' in line:
meshtype = line.replace('mesh =','').strip().split()[0]
break

# If xyz mesh, need to find mesh dimensions:
if meshtype=='xyz':
for line in lines:
if 'nx =' in line:
nx = int(line.replace('nx =','').strip().split()[0])
elif 'ny =' in line:
ny = int(line.replace('ny =','').strip().split()[0])
elif 'nz =' in line:
nz = int(line.replace('nz =','').strip().split()[0])
break

# Find starting line
for li, line in enumerate(lines):
if 'nuclear yield (or production)' in line:
li_start = li
break

if iredufmt==1:
# Count number of nuclides present in whole file
nreg = 0
nuc_id_list = []
for li, line in enumerate(lines):
if li <= li_start+3: continue # in header
vals = line.strip().split()
if int(vals[0])==0: break # reached end
if int(vals[0])>nreg: nreg = int(vals[0])
zzzaaam = int(vals[1])
if zzzaaam not in nuc_id_list: nuc_id_list.append(zzzaaam)

nnuc = len(nuc_id_list)
yields = np.zeros((nreg,nnuc,2))
nuclide_names_yld = []

# Get names
nuc_id_list = sorted(nuc_id_list)
for id in nuc_id_list:
nuclide_names_yld.append(ZAM_to_Dname(id))

# Get values
for li, line in enumerate(lines):
if li <= li_start+3: continue # in header
vals = line.strip().split()
if int(vals[0])==0: break # reached end
ri = int(vals[0]) - 1
zzzaaam = int(vals[1])
ni = nuc_id_list.index(zzzaaam)
yields[ri,ni,0] = np.float(vals[2])
yields[ri,ni,1] = np.float(vals[3])*np.float(vals[2])

else: # old ''traditional'' format
# Count number of nuclides present in whole file
nnuc = 0
for li, line in enumerate(lines):
if li <= li_start+2: continue # skip header lines
if 'isotope production' in line:
N_bounds = line.strip().split('=')[-1].split()
N_bounds = [int(i) for i in N_bounds]
nnuc += N_bounds[1] - N_bounds[0] + 1

# Determine number of regions
nreg = 0
for li, line in enumerate(lines):
if li <= li_start+4: continue # skip header lines
if len(line) < 2: break # reached end of first element block
nreg += 1

yields = np.zeros((nreg,nnuc,2))
nuclide_names_yld = []

# Extract yield data
ni = 0 # nuclide index
ni_newstart = 0
ri = 0 # region index
for li, line in enumerate(lines):
if li <= li_start+2: continue # skip header lines
if len(line) < 2: continue # skip line breaks

if 'isotope production' in line:
# extract Z and A info
Z = int(line.strip().split('-')[0])
N_bounds = line.strip().split('=')[-1].split()
N_bounds = [int(i) for i in N_bounds]
N_list = []
for i in range(N_bounds[1]-N_bounds[0]+1):
N_list.append(N_bounds[0]+i)
nisotopes = len(N_list)
A_list = [N+Z for N in N_list]
ni_newstart = len(nuclide_names_yld)
for A in A_list:
ZAM = 10*A + 10000*Z
nuclide_names_yld.append(ZAM_to_Dname(ZAM))
on_buffer_line = True
continue

if on_buffer_line:
ri = 0
on_buffer_line = False
continue

if '# Information for Restart Calculation' in line: break # reached end of useful info

# Only lines making it to this point will be ones with region and yield data
vals = line.strip().split()
if meshtype=='xyz':
yvals = vals[3:]
if err_dyld_found: yvals_rerr = lines_err[li].strip().split()[3:]
jx,jy,jz = int(vals[0]),int(vals[1]),int(vals[2])
rii = jz + (jy-1)*nz + (jx-1)*(nz*ny)
#if iredufmt==1:
#    rii = jx + (jy-1)*nx + (jz-1)*(nx*ny)
#else:
#    rii = jz + (jy-1)*nz + (jx-1)*(nz*ny)
else:
yvals = vals[1:]
if err_dyld_found: yvals_rerr = lines_err[li].split()[1:]
rii = ri

for i in range(nisotopes):
yields[rii,ni_newstart+i,0] = np.float(yvals[i])
if err_dyld_found: yields[rii,ni_newstart+i,1] = np.float(yvals_rerr[i])*yields[rii,ni_newstart+i,0]

ri += 1

return yields, nuclide_names_yld
 def parse_tdeposit_file(path_to_tdeposit_file, return_metadata=False) 

## Description

Parses the output file of a T-Deposit tally generated by PHITS. This works for region, xyz, and tetrahedral mesh geometries. It has only been tested for output=deposit, axis=eng. It is currently only designed for tallies tracking a single particle (or "all").

## Inputs

• path_to_tdeposit_file = path to the T-Deposit tally output file to be parsed
• return_metadata = Boolean indicating whether additional information is outputted with the flux (D=False)

## Outputs

• deposit = a RxEx4 array containing regionwise T-Deposit tally output [Elower/Eupper/deposit/abs_error]
• deposit_metadata (only returned if return_metadata=True) = list of length two
• deposit_metadata[0] = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format)
• deposit_metadata[1] = string denoting mesh type as either 'reg', 'xyz', or 'tet'
Expand source code
def parse_tdeposit_file(path_to_tdeposit_file,return_metadata=False):
'''
Description:
Parses the output file of a T-Deposit tally generated by PHITS.  This works for region, xyz, and tetrahedral mesh geometries.
It has only been tested for output=deposit, axis=eng.  It is currently only designed for tallies tracking a single particle (or "all").

Inputs:
- path_to_tdeposit_file = path to the T-Deposit tally output file to be parsed
- return_metadata = Boolean indicating whether additional information is outputted with the flux (D=False)

Outputs:
- deposit = a RxEx4 array containing regionwise T-Deposit tally output [Elower/Eupper/deposit/abs_error]
- deposit_metadata (only returned if return_metadata=True) = list of length two
- deposit_metadata[0] = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format)
- deposit_metadata[1] = string denoting mesh type as either 'reg', 'xyz', or 'tet'
'''

# Extract text from file
f = open(path_to_tdeposit_file)
file_text = f.read()
lines = file_text.split('\n')
f.close()

# Determine geometry type (mesh = reg, xyz, or tet)
for line in lines:
if 'mesh =' in line:
meshtype = line.replace('mesh =','').strip().split()[0]
break

# Determine if original or reduced format (axis = eng or axis = dchain)
for line in lines:
if 'axis =' in line:
axistype = line.replace('axis =','').strip().split()[0]
break
# Double check
for li, line in enumerate(lines):
if li>500: break
if '#  e-lower      e-upper      neutron     r.err ' in line:
axistype='eng'
break

deposit_metadata = [axistype,meshtype]

# Determine number of regions
nreg = file_text.count('#   no. =')

for line in lines:
if 'ne =' in line:
nEbins = int(line.replace('ne =','').strip().split()[0])
break

deposit = np.zeros((nreg,nEbins,4))

in_deposit_lines = False
ei = 0
ri = -1

for line in lines:
if '#   no. =' in line:
ri += 1
if '#  e-lower      e-upper  ' in line:
in_deposit_lines = True
continue
if in_deposit_lines:
deposit[ri,ei,:] = [float(x) for x in line.split()]
deposit[ri,ei,3] = deposit[ri,ei,3]*deposit[ri,ei,2] # convert relative error to absolute error
ei += 1
if ei == nEbins:
in_deposit_lines = False
ei = 0

if return_metadata:
return deposit, deposit_metadata
else:
return deposit
 def parse_ttrack_file(path_to_dtrk_file, return_metadata=False) 

## Description

Parses the output file of a T-Track tally generated by PHITS. Note that this specific function assumes that the T-Track tally was one automatically generated by and corresponding to a T-Dchain tally but in principle works with any T-Track tally. This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format. It is currently only designed for tallies tracking a single particle (or "all").

## Inputs

• path_to_dtrk_file = path to the T-Track tally output file to be parsed
• return_metadata = Boolean indicating whether additional information is outputted with the flux (D=False)

## Outputs

• flux = a RxEx4 array containing regionwise fluxes [x-lower/x-upper/flux/abs_error]
• dtrk_metadata (only returned if return_metadata=True) = list of length two
• dtrk_metadata[0] = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format)
• dtrk_metadata[1] = string denoting mesh type as either 'reg', 'xyz', or 'tet'
Expand source code
def parse_ttrack_file(path_to_dtrk_file,return_metadata=False):
'''
Description:
Parses the output file of a T-Track tally generated by PHITS.  Note that this specific function assumes that the T-Track
tally was one automatically generated by and corresponding to a T-Dchain tally but in principle works with any T-Track tally.
This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.
It is currently only designed for tallies tracking a single particle (or "all").

Inputs:
- path_to_dtrk_file = path to the T-Track tally output file to be parsed
- return_metadata = Boolean indicating whether additional information is outputted with the flux (D=False)

Outputs:
- flux = a RxEx4 array containing regionwise fluxes [x-lower/x-upper/flux/abs_error]
- dtrk_metadata (only returned if return_metadata=True) = list of length two
- dtrk_metadata[0] = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format)
- dtrk_metadata[1] = string denoting mesh type as either 'reg', 'xyz', or 'tet'
'''

# Extract text from file
f = open(path_to_dtrk_file)
file_text = f.read()
lines = file_text.split('\n')
f.close()

# Determine geometry type (mesh = reg, xyz, or tet)
for line in lines:
if 'mesh =' in line:
meshtype = line.replace('mesh =','').strip().split()[0]
break

# Determine if original or reduced format (axis = eng or axis = dchain or axis=x/y/z)
for line in lines:
if 'axis =' in line:
axistype = line.replace('axis =','').strip().split()[0]
break

if meshtype=='xyz':
nx, ny, nz = 0, 0, 0
for line in lines:
if 'nx =' in line:
nx = int(line.replace('nx =','').strip().split()[0])
break
for line in lines:
if 'ny =' in line:
ny = int(line.replace('ny =','').strip().split()[0])
break
for line in lines:
if 'nz =' in line:
nz = int(line.replace('nz =','').strip().split()[0])
break
nreg = nx*ny*nz

dtrk_metadata = [axistype,meshtype]

# Determine number of regions
if axistype=='eng':
nreg = file_text.count('#   no. =')
elif axistype in ['x','y','z']:
#nreg = nx*ny*nz
if axistype=='x': nbins = nx
if axistype=='y': nbins = ny
if axistype=='z': nbins = nz
elif axistype=='dchain':
for li, line in reversed(list(enumerate(lines))):
#print(line)
if '0    0   0.0000E+00  0.0000' in line:
nreg = int(lines[li-1].split()[0])
break

if axistype=='dchain':
nEbins = 1968
else:
for line in lines:
if 'ne =' in line:
nEbins = int(line.replace('ne =','').strip().split()[0])
break

if meshtype=='xyz':
flux = np.zeros((nx,ny,nz,nEbins,4))
else:
flux = np.zeros((nreg,nEbins,4))

if axistype=='eng':
in_flux_lines = False
ei = 0
ri = -1

for line in lines:
if '#   no. =' in line:
ri += 1
if '#  e-lower      e-upper ' in line:
in_flux_lines = True
continue
if in_flux_lines:
flux[ri,ei,:] = [float(x) for x in line.split()]
flux[ri,ei,3] = flux[ri,ei,3]*flux[ri,ei,2] # convert relative error to absolute error
ei += 1
if ei == nEbins:
in_flux_lines = False
ei = 0

elif axistype in ['x','y','z']:
xi, yi, zi, ei = 0, 0, 0, 0
ie_edges, ix_edges, iy_edges, iz_edges = [],[],[],[]
in_flux_lines = False
for line in lines:
if '#   no. =' in line:
parts = line.split()
if 'ie' in parts: ei = int(parts[2+parts.index('ie')])-1
if 'ix' in parts: xi = int(parts[2+parts.index('ix')])-1
if 'iy' in parts: yi = int(parts[2+parts.index('iy')])-1
if 'iz' in parts: zi = int(parts[2+parts.index('iz')])-1
if '#   e =' in line:
parts = line.split()
ie_edges.append([float(parts[4]),float(parts[6])])
if '#   x =' in line:
parts = line.split()
ix_edges.append([float(parts[4]),float(parts[6])])
if '#   y =' in line:
parts = line.split()
iy_edges.append([float(parts[4]),float(parts[6])])
if '#   z =' in line:
parts = line.split()
iz_edges.append([float(parts[4]),float(parts[6])])

if '#  {}-lower      {}-upper '.format(axistype,axistype) in line:
in_flux_lines = True
continue

if in_flux_lines:
flux[xi,yi,zi,ei,:] = [float(x) for x in line.split()]
flux[xi,yi,zi,ei,3] = flux[xi,yi,zi,ei,3]*flux[xi,yi,zi,ei,2] # convert relative error to absolute error
if axistype=='x':
xi += 1
if xi==nbins:
in_flux_lines = False
xi=0
if axistype=='y':
yi += 1
if yi==nbins:
in_flux_lines = False
yi=0
if axistype=='z':
zi += 1
if zi==nbins:
in_flux_lines = False
zi=0

elif axistype=='dchain':
ebins = [20.0,
1.964033E+01,1.947734E+01,1.931570E+01,1.915541E+01,1.899644E+01,1.883880E+01,1.868246E+01,1.852742E+01,1.837367E+01,1.822119E+01,1.806998E+01,1.792002E+01,1.777131E+01,1.762383E+01,1.747757E+01,1.733253E+01,1.718869E+01,1.704605E+01,1.690459E+01,1.676430E+01,1.662518E+01,
1.648721E+01,1.635039E+01,1.621470E+01,1.608014E+01,1.594670E+01,1.581436E+01,1.568312E+01,1.555297E+01,1.542390E+01,1.529590E+01,1.516897E+01,1.504309E+01,1.491825E+01,1.479444E+01,1.467167E+01,1.454991E+01,1.442917E+01,1.430943E+01,1.419068E+01,1.407291E+01,
1.395612E+01,1.384031E+01,1.372545E+01,1.361155E+01,1.349859E+01,1.338657E+01,1.327548E+01,1.316531E+01,1.305605E+01,1.294770E+01,1.284025E+01,1.273370E+01,1.262802E+01,1.252323E+01,1.241930E+01,1.231624E+01,1.221403E+01,1.211267E+01,1.201215E+01,1.191246E+01,
1.181360E+01,1.171557E+01,1.161834E+01,1.152193E+01,1.142631E+01,1.133148E+01,1.123745E+01,1.114419E+01,1.105171E+01,1.095999E+01,1.086904E+01,1.077884E+01,1.068939E+01,1.060068E+01,1.051271E+01,1.042547E+01,1.033895E+01,1.025315E+01,1.016806E+01,1.008368E+01,
1.000000E+01,9.917013E+00,9.834715E+00,9.753099E+00,9.672161E+00,9.591895E+00,9.512294E+00,9.433354E+00,9.355070E+00,9.277435E+00,9.200444E+00,9.124092E+00,9.048374E+00,8.973284E+00,8.898818E+00,8.824969E+00,8.751733E+00,8.679105E+00,8.607080E+00,8.535652E+00,
8.464817E+00,8.394570E+00,8.324906E+00,8.255820E+00,8.187308E+00,8.119363E+00,8.051983E+00,7.985162E+00,7.918896E+00,7.853179E+00,7.788008E+00,7.723377E+00,7.659283E+00,7.595721E+00,7.532687E+00,7.470175E+00,7.408182E+00,7.346704E+00,7.285736E+00,7.225274E+00,
7.165313E+00,7.105850E+00,7.046881E+00,6.988401E+00,6.930406E+00,6.872893E+00,6.815857E+00,6.759294E+00,6.703200E+00,6.647573E+00,6.592406E+00,6.537698E+00,6.483443E+00,6.429639E+00,6.376282E+00,6.323367E+00,6.270891E+00,6.218851E+00,6.167242E+00,6.116062E+00,
6.065307E+00,6.014972E+00,5.965056E+00,5.915554E+00,5.866462E+00,5.817778E+00,5.769498E+00,5.721619E+00,5.674137E+00,5.627049E+00,5.580351E+00,5.534042E+00,5.488116E+00,5.442572E+00,5.397406E+00,5.352614E+00,5.308195E+00,5.264143E+00,5.220458E+00,5.177135E+00,
5.134171E+00,5.091564E+00,5.049311E+00,5.007408E+00,4.965853E+00,4.924643E+00,4.883775E+00,4.843246E+00,4.803053E+00,4.763194E+00,4.723666E+00,4.684465E+00,4.645590E+00,4.607038E+00,4.568805E+00,4.530890E+00,4.493290E+00,4.456001E+00,4.419022E+00,4.382350E+00,
4.345982E+00,4.309916E+00,4.274149E+00,4.238679E+00,4.203504E+00,4.168620E+00,4.134026E+00,4.099719E+00,4.065697E+00,4.031957E+00,3.998497E+00,3.965314E+00,3.932407E+00,3.899773E+00,3.867410E+00,3.835316E+00,3.803488E+00,3.771924E+00,3.740621E+00,3.709579E+00,
3.678794E+00,3.648265E+00,3.617989E+00,3.587965E+00,3.558189E+00,3.528661E+00,3.499377E+00,3.470337E+00,3.441538E+00,3.412978E+00,3.384654E+00,3.356566E+00,3.328711E+00,3.301087E+00,3.273692E+00,3.246525E+00,3.219583E+00,3.192864E+00,3.166368E+00,3.140091E+00,
3.114032E+00,3.088190E+00,3.062562E+00,3.037147E+00,3.011942E+00,2.986947E+00,2.962159E+00,2.937577E+00,2.913199E+00,2.889023E+00,2.865048E+00,2.841272E+00,2.817693E+00,2.794310E+00,2.771121E+00,2.748124E+00,2.725318E+00,2.702701E+00,2.680272E+00,2.658030E+00,
2.635971E+00,2.614096E+00,2.592403E+00,2.570889E+00,2.549554E+00,2.528396E+00,2.507414E+00,2.486605E+00,2.465970E+00,2.445505E+00,2.425211E+00,2.405085E+00,2.385126E+00,2.365332E+00,2.345703E+00,2.326237E+00,2.306932E+00,2.287787E+00,2.268802E+00,2.249973E+00,
2.231302E+00,2.212785E+00,2.194421E+00,2.176211E+00,2.158151E+00,2.140241E+00,2.122480E+00,2.104866E+00,2.087398E+00,2.070076E+00,2.052897E+00,2.035860E+00,2.018965E+00,2.002210E+00,1.985595E+00,1.969117E+00,1.952776E+00,1.936570E+00,1.920499E+00,1.904561E+00,
1.888756E+00,1.873082E+00,1.857538E+00,1.842122E+00,1.826835E+00,1.811675E+00,1.796640E+00,1.781731E+00,1.766944E+00,1.752281E+00,1.737739E+00,1.723318E+00,1.709017E+00,1.694834E+00,1.680770E+00,1.666821E+00,1.652989E+00,1.639271E+00,1.625667E+00,1.612176E+00,
1.598797E+00,1.585530E+00,1.572372E+00,1.559323E+00,1.546383E+00,1.533550E+00,1.520823E+00,1.508202E+00,1.495686E+00,1.483274E+00,1.470965E+00,1.458758E+00,1.446652E+00,1.434646E+00,1.422741E+00,1.410934E+00,1.399225E+00,1.387613E+00,1.376098E+00,1.364678E+00,
1.353353E+00,1.342122E+00,1.330984E+00,1.319938E+00,1.308985E+00,1.298122E+00,1.287349E+00,1.276666E+00,1.266071E+00,1.255564E+00,1.245145E+00,1.234812E+00,1.224564E+00,1.214402E+00,1.204324E+00,1.194330E+00,1.184418E+00,1.174589E+00,1.164842E+00,1.155175E+00,
1.145588E+00,1.136082E+00,1.126654E+00,1.117304E+00,1.108032E+00,1.098836E+00,1.089717E+00,1.080674E+00,1.071706E+00,1.062812E+00,1.053992E+00,1.045245E+00,1.036571E+00,1.027969E+00,1.019438E+00,1.010978E+00,1.002588E+00,9.942682E-01,9.860171E-01,9.778344E-01,
9.697197E-01,9.616723E-01,9.536916E-01,9.457772E-01,9.379285E-01,9.301449E-01,9.224259E-01,9.147709E-01,9.071795E-01,8.996511E-01,8.921852E-01,8.847812E-01,8.774387E-01,8.701570E-01,8.629359E-01,8.557746E-01,8.486728E-01,8.416299E-01,8.346455E-01,8.277190E-01,
8.208500E-01,8.140380E-01,8.072825E-01,8.005831E-01,7.939393E-01,7.873507E-01,7.808167E-01,7.743369E-01,7.679109E-01,7.615382E-01,7.552184E-01,7.489511E-01,7.427358E-01,7.365720E-01,7.304594E-01,7.243976E-01,7.183860E-01,7.124243E-01,7.065121E-01,7.006490E-01,
6.948345E-01,6.890683E-01,6.833499E-01,6.776790E-01,6.720551E-01,6.664779E-01,6.609470E-01,6.554620E-01,6.500225E-01,6.446282E-01,6.392786E-01,6.339734E-01,6.287123E-01,6.234948E-01,6.183206E-01,6.131893E-01,6.081006E-01,6.030542E-01,5.980496E-01,5.930866E-01,
5.881647E-01,5.832837E-01,5.784432E-01,5.736429E-01,5.688824E-01,5.641614E-01,5.594796E-01,5.548366E-01,5.502322E-01,5.456660E-01,5.411377E-01,5.366469E-01,5.321934E-01,5.277769E-01,5.233971E-01,5.190535E-01,5.147461E-01,5.104743E-01,5.062381E-01,5.020369E-01,
4.978707E-01,4.937390E-01,4.896416E-01,4.855782E-01,4.815485E-01,4.775523E-01,4.735892E-01,4.696591E-01,4.657615E-01,4.618963E-01,4.580631E-01,4.542618E-01,4.504920E-01,4.467535E-01,4.430460E-01,4.393693E-01,4.357231E-01,4.321072E-01,4.285213E-01,4.249651E-01,
4.214384E-01,4.179410E-01,4.144727E-01,4.110331E-01,4.076220E-01,4.042393E-01,4.008846E-01,3.975578E-01,3.942586E-01,3.909868E-01,3.877421E-01,3.845243E-01,3.813333E-01,3.781687E-01,3.750304E-01,3.719181E-01,3.688317E-01,3.657708E-01,3.627354E-01,3.597252E-01,
3.567399E-01,3.537795E-01,3.508435E-01,3.479320E-01,3.450446E-01,3.421812E-01,3.393415E-01,3.365254E-01,3.337327E-01,3.309631E-01,3.282166E-01,3.254928E-01,3.227916E-01,3.201129E-01,3.174564E-01,3.148219E-01,3.122093E-01,3.096183E-01,3.070489E-01,3.045008E-01,
3.019738E-01,2.994678E-01,2.985000E-01,2.972000E-01,2.969826E-01,2.945181E-01,2.920740E-01,2.896501E-01,2.872464E-01,2.848626E-01,2.824986E-01,2.801543E-01,2.778293E-01,2.755237E-01,2.732372E-01,2.709697E-01,2.687210E-01,2.664910E-01,2.642794E-01,2.620863E-01,
2.599113E-01,2.577544E-01,2.556153E-01,2.534941E-01,2.513904E-01,2.493042E-01,2.472353E-01,2.451835E-01,2.431488E-01,2.411310E-01,2.391299E-01,2.371455E-01,2.351775E-01,2.332258E-01,2.312903E-01,2.293709E-01,2.274674E-01,2.255797E-01,2.237077E-01,2.218512E-01,
2.200102E-01,2.181844E-01,2.163737E-01,2.145781E-01,2.127974E-01,2.110314E-01,2.092801E-01,2.075434E-01,2.058210E-01,2.041130E-01,2.024191E-01,2.007393E-01,1.990734E-01,1.974214E-01,1.957830E-01,1.941583E-01,1.925470E-01,1.909491E-01,1.893645E-01,1.877930E-01,
1.862346E-01,1.846891E-01,1.831564E-01,1.816364E-01,1.801291E-01,1.786342E-01,1.771518E-01,1.756817E-01,1.742237E-01,1.727779E-01,1.713441E-01,1.699221E-01,1.685120E-01,1.671136E-01,1.657268E-01,1.643514E-01,1.629875E-01,1.616349E-01,1.602936E-01,1.589634E-01,
1.576442E-01,1.563359E-01,1.550385E-01,1.537519E-01,1.524760E-01,1.512106E-01,1.499558E-01,1.487113E-01,1.474772E-01,1.462533E-01,1.450396E-01,1.438360E-01,1.426423E-01,1.414586E-01,1.402847E-01,1.391205E-01,1.379660E-01,1.368210E-01,1.356856E-01,1.345596E-01,
1.334429E-01,1.323355E-01,1.312373E-01,1.301482E-01,1.290681E-01,1.279970E-01,1.269348E-01,1.258814E-01,1.248368E-01,1.238008E-01,1.227734E-01,1.217545E-01,1.207441E-01,1.197421E-01,1.187484E-01,1.177629E-01,1.167857E-01,1.158165E-01,1.148554E-01,1.139022E-01,
1.129570E-01,1.120196E-01,1.110900E-01,1.101681E-01,1.092538E-01,1.083471E-01,1.074480E-01,1.065563E-01,1.056720E-01,1.047951E-01,1.039254E-01,1.030630E-01,1.022077E-01,1.013595E-01,1.005184E-01,9.968419E-02,9.885694E-02,9.803655E-02,9.722297E-02,9.641615E-02,
9.561602E-02,9.482253E-02,9.403563E-02,9.325525E-02,9.248135E-02,9.171388E-02,9.095277E-02,9.019798E-02,8.944945E-02,8.870714E-02,8.797098E-02,8.724094E-02,8.651695E-02,8.579897E-02,8.508695E-02,8.438084E-02,8.368059E-02,8.298615E-02,8.250000E-02,8.229747E-02,
8.161451E-02,8.093721E-02,8.026554E-02,7.959944E-02,7.950000E-02,7.893887E-02,7.828378E-02,7.763412E-02,7.698986E-02,7.635094E-02,7.571733E-02,7.508897E-02,7.446583E-02,7.384786E-02,7.323502E-02,7.262726E-02,7.202455E-02,7.142684E-02,7.083409E-02,7.024626E-02,
6.966330E-02,6.908519E-02,6.851187E-02,6.794331E-02,6.737947E-02,6.682031E-02,6.626579E-02,6.571586E-02,6.517051E-02,6.462968E-02,6.409333E-02,6.356144E-02,6.303396E-02,6.251086E-02,6.199211E-02,6.147765E-02,6.096747E-02,6.046151E-02,5.995976E-02,5.946217E-02,
5.896871E-02,5.847935E-02,5.799405E-02,5.751277E-02,5.703549E-02,5.656217E-02,5.609278E-02,5.562728E-02,5.516564E-02,5.470784E-02,5.425384E-02,5.380360E-02,5.335710E-02,5.291430E-02,5.247518E-02,5.203971E-02,5.160785E-02,5.117957E-02,5.075484E-02,5.033364E-02,
4.991594E-02,4.950170E-02,4.909090E-02,4.868351E-02,4.827950E-02,4.787884E-02,4.748151E-02,4.708747E-02,4.669671E-02,4.630919E-02,4.592488E-02,4.554376E-02,4.516581E-02,4.479099E-02,4.441928E-02,4.405066E-02,4.368510E-02,4.332257E-02,4.296305E-02,4.260651E-02,
4.225293E-02,4.190229E-02,4.155455E-02,4.120970E-02,4.086771E-02,4.052857E-02,4.019223E-02,3.985869E-02,3.952791E-02,3.919988E-02,3.887457E-02,3.855196E-02,3.823203E-02,3.791476E-02,3.760011E-02,3.728808E-02,3.697864E-02,3.667176E-02,3.636743E-02,3.606563E-02,
3.576633E-02,3.546952E-02,3.517517E-02,3.488326E-02,3.459377E-02,3.430669E-02,3.402199E-02,3.373965E-02,3.345965E-02,3.318198E-02,3.290662E-02,3.263353E-02,3.236272E-02,3.209415E-02,3.182781E-02,3.156368E-02,3.130174E-02,3.104198E-02,3.078437E-02,3.052890E-02,
3.027555E-02,3.002430E-02,2.977514E-02,2.952804E-02,2.928300E-02,2.903999E-02,2.879899E-02,2.856000E-02,2.850000E-02,2.832299E-02,2.808794E-02,2.785485E-02,2.762369E-02,2.739445E-02,2.716711E-02,2.700000E-02,2.694166E-02,2.671808E-02,2.649635E-02,2.627647E-02,
2.605841E-02,2.584215E-02,2.562770E-02,2.541502E-02,2.520411E-02,2.499495E-02,2.478752E-02,2.458182E-02,2.437782E-02,2.417552E-02,2.397489E-02,2.377593E-02,2.357862E-02,2.338295E-02,2.318890E-02,2.299646E-02,2.280562E-02,2.261636E-02,2.242868E-02,2.224255E-02,
2.205796E-02,2.187491E-02,2.169338E-02,2.151335E-02,2.133482E-02,2.115777E-02,2.098218E-02,2.080806E-02,2.063538E-02,2.046413E-02,2.029431E-02,2.012589E-02,1.995887E-02,1.979324E-02,1.962898E-02,1.946608E-02,1.930454E-02,1.914434E-02,1.898547E-02,1.882791E-02,
1.867166E-02,1.851671E-02,1.836305E-02,1.821066E-02,1.805953E-02,1.790966E-02,1.776104E-02,1.761364E-02,1.746747E-02,1.732251E-02,1.717876E-02,1.703620E-02,1.689482E-02,1.675461E-02,1.661557E-02,1.647768E-02,1.634094E-02,1.620533E-02,1.607085E-02,1.593748E-02,
1.580522E-02,1.567406E-02,1.554398E-02,1.541499E-02,1.528706E-02,1.516020E-02,1.503439E-02,1.490963E-02,1.478590E-02,1.466319E-02,1.454151E-02,1.442083E-02,1.430116E-02,1.418247E-02,1.406478E-02,1.394806E-02,1.383231E-02,1.371752E-02,1.360368E-02,1.349079E-02,
1.337883E-02,1.326780E-02,1.315770E-02,1.304851E-02,1.294022E-02,1.283283E-02,1.272634E-02,1.262073E-02,1.251599E-02,1.241212E-02,1.230912E-02,1.220697E-02,1.210567E-02,1.200521E-02,1.190558E-02,1.180678E-02,1.170880E-02,1.161163E-02,1.151527E-02,1.141970E-02,
1.132494E-02,1.123095E-02,1.113775E-02,1.104532E-02,1.095366E-02,1.086276E-02,1.077261E-02,1.068321E-02,1.059456E-02,1.050664E-02,1.041944E-02,1.033298E-02,1.024723E-02,1.016219E-02,1.007785E-02,9.994221E-03,9.911282E-03,9.829031E-03,9.747463E-03,9.666572E-03,
9.586352E-03,9.506797E-03,9.427903E-03,9.349664E-03,9.272074E-03,9.195127E-03,9.118820E-03,9.043145E-03,8.968099E-03,8.893675E-03,8.819869E-03,8.746676E-03,8.674090E-03,8.602106E-03,8.530719E-03,8.459926E-03,8.389719E-03,8.320095E-03,8.251049E-03,8.182576E-03,
8.114671E-03,8.047330E-03,7.980548E-03,7.914319E-03,7.848641E-03,7.783507E-03,7.718914E-03,7.654857E-03,7.591332E-03,7.528334E-03,7.465858E-03,7.403901E-03,7.342458E-03,7.281525E-03,7.221098E-03,7.161172E-03,7.101744E-03,7.042809E-03,6.984362E-03,6.926401E-03,
6.868921E-03,6.811918E-03,6.755388E-03,6.699327E-03,6.643731E-03,6.588597E-03,6.533920E-03,6.479697E-03,6.425924E-03,6.372597E-03,6.319712E-03,6.267267E-03,6.215257E-03,6.163678E-03,6.112528E-03,6.061802E-03,6.011496E-03,5.961609E-03,5.912135E-03,5.863072E-03,
5.814416E-03,5.766164E-03,5.718312E-03,5.670858E-03,5.623797E-03,5.577127E-03,5.530844E-03,5.484945E-03,5.439427E-03,5.394287E-03,5.349521E-03,5.305127E-03,5.261101E-03,5.217441E-03,5.174143E-03,5.131204E-03,5.088622E-03,5.046393E-03,5.004514E-03,4.962983E-03,
4.921797E-03,4.880952E-03,4.840447E-03,4.800277E-03,4.760441E-03,4.720936E-03,4.681758E-03,4.642906E-03,4.604375E-03,4.566165E-03,4.528272E-03,4.490693E-03,4.453426E-03,4.416468E-03,4.379817E-03,4.343471E-03,4.307425E-03,4.271679E-03,4.236230E-03,4.201075E-03,
4.166211E-03,4.131637E-03,4.097350E-03,4.063347E-03,4.029627E-03,3.996186E-03,3.963023E-03,3.930135E-03,3.897520E-03,3.865175E-03,3.833099E-03,3.801290E-03,3.769744E-03,3.738460E-03,3.707435E-03,3.676668E-03,3.646157E-03,3.615898E-03,3.585891E-03,3.556133E-03,
3.526622E-03,3.497355E-03,3.468332E-03,3.439549E-03,3.411005E-03,3.382698E-03,3.354626E-03,3.326787E-03,3.299179E-03,3.271800E-03,3.244649E-03,3.217722E-03,3.191019E-03,3.164538E-03,3.138276E-03,3.112233E-03,3.086405E-03,3.060792E-03,3.035391E-03,3.010202E-03,
2.985221E-03,2.960447E-03,2.935879E-03,2.911515E-03,2.887354E-03,2.863392E-03,2.839630E-03,2.816065E-03,2.792695E-03,2.769519E-03,2.746536E-03,2.723743E-03,2.701139E-03,2.678723E-03,2.656494E-03,2.634448E-03,2.612586E-03,2.590904E-03,2.569403E-03,2.548081E-03,
2.526935E-03,2.505965E-03,2.485168E-03,2.464545E-03,2.444092E-03,2.423809E-03,2.403695E-03,2.383747E-03,2.363965E-03,2.344347E-03,2.324892E-03,2.305599E-03,2.286465E-03,2.267490E-03,2.248673E-03,2.230012E-03,2.211506E-03,2.193153E-03,2.174953E-03,2.156904E-03,
2.139004E-03,2.121253E-03,2.103650E-03,2.086192E-03,2.068879E-03,2.051710E-03,2.034684E-03,2.017798E-03,2.001053E-03,1.984447E-03,1.967979E-03,1.951647E-03,1.935451E-03,1.919389E-03,1.903461E-03,1.887665E-03,1.871999E-03,1.856464E-03,1.841058E-03,1.825780E-03,
1.810628E-03,1.795602E-03,1.780701E-03,1.765923E-03,1.751268E-03,1.736735E-03,1.722323E-03,1.708030E-03,1.693855E-03,1.679798E-03,1.665858E-03,1.652034E-03,1.638324E-03,1.624728E-03,1.611245E-03,1.597874E-03,1.584613E-03,1.571463E-03,1.558422E-03,1.545489E-03,
1.532663E-03,1.519944E-03,1.507331E-03,1.494822E-03,1.482417E-03,1.470115E-03,1.457915E-03,1.445816E-03,1.433817E-03,1.421919E-03,1.410118E-03,1.398416E-03,1.386811E-03,1.375303E-03,1.363889E-03,1.352571E-03,1.341346E-03,1.330215E-03,1.319176E-03,1.308228E-03,
1.297372E-03,1.286605E-03,1.275928E-03,1.265339E-03,1.254839E-03,1.244425E-03,1.234098E-03,1.223857E-03,1.213700E-03,1.203628E-03,1.193639E-03,1.183734E-03,1.173910E-03,1.164168E-03,1.154507E-03,1.144926E-03,1.135425E-03,1.126002E-03,1.116658E-03,1.107391E-03,
1.098201E-03,1.089088E-03,1.080050E-03,1.071087E-03,1.062198E-03,1.053383E-03,1.044641E-03,1.035972E-03,1.027375E-03,1.018849E-03,1.010394E-03,1.002009E-03,9.936937E-04,9.854473E-04,9.772694E-04,9.691593E-04,9.611165E-04,9.531405E-04,9.452307E-04,9.373865E-04,
9.296074E-04,9.218928E-04,9.142423E-04,9.066553E-04,8.991312E-04,8.916696E-04,8.842699E-04,8.769316E-04,8.696542E-04,8.624372E-04,8.552801E-04,8.481824E-04,8.411435E-04,8.341631E-04,8.272407E-04,8.203756E-04,8.135676E-04,8.068160E-04,8.001205E-04,7.934805E-04,
7.868957E-04,7.803654E-04,7.738894E-04,7.674671E-04,7.610981E-04,7.547820E-04,7.485183E-04,7.423066E-04,7.361464E-04,7.300373E-04,7.239790E-04,7.179709E-04,7.120126E-04,7.061038E-04,7.002441E-04,6.944330E-04,6.886701E-04,6.829550E-04,6.772874E-04,6.716668E-04,
6.660928E-04,6.605651E-04,6.550832E-04,6.496469E-04,6.442557E-04,6.389092E-04,6.336071E-04,6.283489E-04,6.231345E-04,6.179633E-04,6.128350E-04,6.077492E-04,6.027057E-04,5.977040E-04,5.927438E-04,5.878248E-04,5.829466E-04,5.781089E-04,5.733114E-04,5.685536E-04,
5.638354E-04,5.591563E-04,5.545160E-04,5.499142E-04,5.453506E-04,5.408249E-04,5.363368E-04,5.318859E-04,5.274719E-04,5.230946E-04,5.187536E-04,5.144486E-04,5.101793E-04,5.059455E-04,5.017468E-04,4.975830E-04,4.934537E-04,4.893587E-04,4.852976E-04,4.812703E-04,
4.772763E-04,4.733156E-04,4.693877E-04,4.654923E-04,4.616294E-04,4.577984E-04,4.539993E-04,4.502317E-04,4.464953E-04,4.427900E-04,4.391154E-04,4.354713E-04,4.318575E-04,4.282736E-04,4.247195E-04,4.211949E-04,4.176995E-04,4.142332E-04,4.107955E-04,4.073865E-04,
4.040057E-04,4.006530E-04,3.973281E-04,3.940308E-04,3.907608E-04,3.875180E-04,3.843021E-04,3.811129E-04,3.779502E-04,3.748137E-04,3.717032E-04,3.686185E-04,3.655595E-04,3.625258E-04,3.595173E-04,3.565338E-04,3.535750E-04,3.506408E-04,3.477309E-04,3.448452E-04,
3.419834E-04,3.391454E-04,3.363309E-04,3.335398E-04,3.307719E-04,3.280269E-04,3.253047E-04,3.226051E-04,3.199279E-04,3.172729E-04,3.146399E-04,3.120288E-04,3.094394E-04,3.068715E-04,3.043248E-04,3.017993E-04,2.992948E-04,2.968110E-04,2.943479E-04,2.919052E-04,
2.894827E-04,2.870804E-04,2.846980E-04,2.823354E-04,2.799924E-04,2.776688E-04,2.753645E-04,2.730793E-04,2.708131E-04,2.685657E-04,2.663370E-04,2.641267E-04,2.619348E-04,2.597611E-04,2.576054E-04,2.554676E-04,2.533476E-04,2.512451E-04,2.491601E-04,2.470924E-04,
2.450418E-04,2.430083E-04,2.409917E-04,2.389917E-04,2.370084E-04,2.350416E-04,2.330910E-04,2.311567E-04,2.292384E-04,2.273360E-04,2.254494E-04,2.235784E-04,2.217230E-04,2.198830E-04,2.180583E-04,2.162487E-04,2.144541E-04,2.126744E-04,2.109095E-04,2.091592E-04,
2.074234E-04,2.057021E-04,2.039950E-04,2.023021E-04,2.006233E-04,1.989584E-04,1.973073E-04,1.956699E-04,1.940461E-04,1.924358E-04,1.908388E-04,1.892551E-04,1.876845E-04,1.861269E-04,1.845823E-04,1.830505E-04,1.815315E-04,1.800250E-04,1.785310E-04,1.770494E-04,
1.755802E-04,1.741231E-04,1.726781E-04,1.712451E-04,1.698239E-04,1.684146E-04,1.670170E-04,1.656310E-04,1.642565E-04,1.628933E-04,1.615415E-04,1.602010E-04,1.588715E-04,1.575531E-04,1.562456E-04,1.549489E-04,1.536631E-04,1.523879E-04,1.511232E-04,1.498691E-04,
1.486254E-04,1.473920E-04,1.461688E-04,1.449558E-04,1.437529E-04,1.425599E-04,1.413768E-04,1.402036E-04,1.390401E-04,1.378862E-04,1.367420E-04,1.356072E-04,1.344818E-04,1.333658E-04,1.322590E-04,1.311615E-04,1.300730E-04,1.289935E-04,1.279231E-04,1.268615E-04,
1.258087E-04,1.247646E-04,1.237292E-04,1.227024E-04,1.216842E-04,1.206744E-04,1.196729E-04,1.186798E-04,1.176949E-04,1.167182E-04,1.157496E-04,1.147890E-04,1.138364E-04,1.128917E-04,1.119548E-04,1.110258E-04,1.101044E-04,1.091907E-04,1.082845E-04,1.073859E-04,
1.064947E-04,1.056110E-04,1.047345E-04,1.038654E-04,1.030034E-04,1.021486E-04,1.013009E-04,1.004603E-04,9.962658E-05,9.879981E-05,9.797990E-05,9.716679E-05,9.636043E-05,9.556076E-05,9.476773E-05,9.398128E-05,9.320136E-05,9.242791E-05,9.166088E-05,9.090021E-05,
9.014586E-05,8.939776E-05,8.865588E-05,8.792015E-05,8.719052E-05,8.646695E-05,8.574939E-05,8.503778E-05,8.433208E-05,8.363223E-05,8.293819E-05,8.224991E-05,8.156734E-05,8.089044E-05,8.021915E-05,7.955344E-05,7.889325E-05,7.823854E-05,7.758926E-05,7.694537E-05,
7.630682E-05,7.567357E-05,7.504558E-05,7.442280E-05,7.380518E-05,7.319270E-05,7.258529E-05,7.198293E-05,7.138556E-05,7.079316E-05,7.020566E-05,6.962305E-05,6.904527E-05,6.847228E-05,6.790405E-05,6.734053E-05,6.678169E-05,6.622749E-05,6.567789E-05,6.513285E-05,
6.459233E-05,6.405630E-05,6.352471E-05,6.299754E-05,6.247474E-05,6.195628E-05,6.144212E-05,6.093223E-05,6.042657E-05,5.992511E-05,5.942781E-05,5.893464E-05,5.844556E-05,5.796053E-05,5.747954E-05,5.700253E-05,5.652948E-05,5.606036E-05,5.559513E-05,5.513376E-05,
5.467623E-05,5.422248E-05,5.377251E-05,5.332626E-05,5.288373E-05,5.244486E-05,5.200963E-05,5.157802E-05,5.114999E-05,5.072551E-05,5.030456E-05,4.988709E-05,4.947309E-05,4.906253E-05,4.865538E-05,4.825160E-05,4.785117E-05,4.745407E-05,4.706026E-05,4.666972E-05,
4.628243E-05,4.589834E-05,4.551744E-05,4.513971E-05,4.476511E-05,4.439361E-05,4.402521E-05,4.365985E-05,4.329753E-05,4.293822E-05,4.258189E-05,4.222851E-05,4.187807E-05,4.153054E-05,4.118589E-05,4.084410E-05,4.050514E-05,4.016900E-05,3.983565E-05,3.950507E-05,
3.917723E-05,3.885211E-05,3.852969E-05,3.820994E-05,3.789285E-05,3.757838E-05,3.726653E-05,3.695727E-05,3.665057E-05,3.634642E-05,3.604479E-05,3.574566E-05,3.544902E-05,3.515484E-05,3.486310E-05,3.457378E-05,3.428686E-05,3.400233E-05,3.372015E-05,3.344032E-05,
3.316281E-05,3.288760E-05,3.261467E-05,3.234401E-05,3.207560E-05,3.180942E-05,3.154544E-05,3.128365E-05,3.102404E-05,3.076658E-05,3.051126E-05,3.025805E-05,3.000695E-05,2.975793E-05,2.951098E-05,2.926607E-05,2.902320E-05,2.878235E-05,2.854349E-05,2.830662E-05,
2.807171E-05,2.783875E-05,2.760773E-05,2.737862E-05,2.715141E-05,2.692609E-05,2.670264E-05,2.648104E-05,2.626128E-05,2.604335E-05,2.582722E-05,2.561289E-05,2.540033E-05,2.518954E-05,2.498050E-05,2.477320E-05,2.456761E-05,2.436373E-05,2.416154E-05,2.396104E-05,
2.376219E-05,2.356499E-05,2.336944E-05,2.317550E-05,2.298317E-05,2.279244E-05,2.260329E-05,2.241572E-05,2.222969E-05,2.204522E-05,2.186227E-05,2.168084E-05,2.150092E-05,2.132249E-05,2.114554E-05,2.097006E-05,2.079603E-05,2.062345E-05,2.045231E-05,2.028258E-05,
2.011426E-05,1.994734E-05,1.978180E-05,1.961764E-05,1.945484E-05,1.929339E-05,1.913328E-05,1.897449E-05,1.881703E-05,1.866087E-05,1.850601E-05,1.835244E-05,1.820013E-05,1.804910E-05,1.789931E-05,1.775077E-05,1.760346E-05,1.745738E-05,1.731250E-05,1.716883E-05,
1.702635E-05,1.688506E-05,1.674493E-05,1.660597E-05,1.646816E-05,1.633150E-05,1.619597E-05,1.606156E-05,1.592827E-05,1.579609E-05,1.566500E-05,1.553500E-05,1.540608E-05,1.527823E-05,1.515144E-05,1.502570E-05,1.490101E-05,1.477735E-05,1.465472E-05,1.453310E-05,
1.441250E-05,1.429289E-05,1.417428E-05,1.405665E-05,1.394000E-05,1.382431E-05,1.370959E-05,1.359582E-05,1.348299E-05,1.337110E-05,1.326014E-05,1.315010E-05,1.304097E-05,1.293274E-05,1.282542E-05,1.271898E-05,1.261343E-05,1.250876E-05,1.240495E-05,1.230201E-05,
1.219991E-05,1.209867E-05,1.199827E-05,1.189870E-05,1.179995E-05,1.170203E-05,1.160492E-05,1.150861E-05,1.141311E-05,1.131839E-05,1.122446E-05,1.113132E-05,1.103894E-05,1.094733E-05,1.085648E-05,1.076639E-05,1.067704E-05,1.058843E-05,1.050056E-05,1.041342E-05,
1.032701E-05,1.024130E-05,1.015631E-05,1.007203E-05,9.988446E-06,9.905554E-06,9.823351E-06,9.741830E-06,9.660985E-06,9.580812E-06,9.501303E-06,9.422455E-06,9.344261E-06,9.266715E-06,9.189814E-06,9.113550E-06,9.037919E-06,8.962916E-06,8.888536E-06,8.814772E-06,
8.741621E-06,8.669077E-06,8.597135E-06,8.525790E-06,8.455037E-06,8.384871E-06,8.315287E-06,8.246281E-06,8.177848E-06,8.109982E-06,8.042680E-06,7.975936E-06,7.909746E-06,7.844105E-06,7.779009E-06,7.714454E-06,7.650434E-06,7.586945E-06,7.523983E-06,7.461544E-06,
7.399622E-06,7.338215E-06,7.277317E-06,7.216925E-06,7.157034E-06,7.097640E-06,7.038739E-06,6.980326E-06,6.922399E-06,6.864952E-06,6.807981E-06,6.751484E-06,6.695455E-06,6.639892E-06,6.584789E-06,6.530144E-06,6.475952E-06,6.422210E-06,6.368914E-06,6.316060E-06,
6.263645E-06,6.211665E-06,6.160116E-06,6.108995E-06,6.058298E-06,6.008022E-06,5.958164E-06,5.908719E-06,5.859684E-06,5.811056E-06,5.762832E-06,5.715008E-06,5.667581E-06,5.620547E-06,5.573904E-06,5.527647E-06,5.481775E-06,5.436284E-06,5.391169E-06,5.346430E-06,
5.302061E-06,5.258061E-06,5.214426E-06,5.171153E-06,5.128239E-06,5.085681E-06,5.043477E-06,4.918953E-06,4.797503E-06,4.679053E-06,4.563526E-06,4.450853E-06,4.340961E-06,4.233782E-06,4.129250E-06,4.000000E-06,3.927860E-06,3.830880E-06,3.736300E-06,3.644050E-06,
3.554080E-06,3.466330E-06,3.380750E-06,3.300000E-06,3.217630E-06,3.137330E-06,3.059020E-06,2.983490E-06,2.909830E-06,2.837990E-06,2.767920E-06,2.720000E-06,2.659320E-06,2.600000E-06,2.550000E-06,2.485030E-06,2.421710E-06,2.382370E-06,2.360000E-06,2.300270E-06,
2.242050E-06,2.185310E-06,2.130000E-06,2.100000E-06,2.059610E-06,2.020000E-06,1.974490E-06,1.930000E-06,1.884460E-06,1.855390E-06,1.840000E-06,1.797000E-06,1.755000E-06,1.711970E-06,1.670000E-06,1.629510E-06,1.590000E-06,1.544340E-06,1.500000E-06,1.475000E-06,
1.440000E-06,1.404560E-06,1.370000E-06,1.337500E-06,1.300000E-06,1.267080E-06,1.235000E-06,1.202060E-06,1.170000E-06,1.150000E-06,1.123000E-06,1.110000E-06,1.097000E-06,1.080000E-06,1.071000E-06,1.045000E-06,1.035000E-06,1.020000E-06,9.960000E-07,9.860000E-07,
9.720000E-07,9.500000E-07,9.300000E-07,9.100000E-07,8.764250E-07,8.600000E-07,8.500000E-07,8.194500E-07,7.900000E-07,7.800000E-07,7.415500E-07,7.050000E-07,6.825600E-07,6.531500E-07,6.250000E-07,5.952800E-07,5.669600E-07,5.400000E-07,5.315800E-07,5.196200E-07,
5.000000E-07,4.850000E-07,4.670100E-07,4.496800E-07,4.330000E-07,4.139900E-07,4.000000E-07,3.910000E-07,3.699300E-07,3.500000E-07,3.346600E-07,3.200000E-07,3.145000E-07,3.000000E-07,2.800000E-07,2.635100E-07,2.480000E-07,2.335800E-07,2.200000E-07,2.091400E-07,
1.988100E-07,1.890000E-07,1.800000E-07,1.697100E-07,1.600000E-07,1.530300E-07,1.463700E-07,1.400000E-07,1.340000E-07,1.150000E-07,1.000000E-07,9.500000E-08,8.000000E-08,7.700000E-08,6.700000E-08,5.800000E-08,5.000000E-08,4.200000E-08,3.500000E-08,3.000000E-08,
2.500000E-08,2.000000E-08,1.500000E-08,1.000000E-08,6.900000E-09,5.000000E-09,3.000000E-09,1.000010E-11
]
ebins = ebins[::-1]
in_flux_lines = False
for line in lines:
if '# num ie flux r.err' in line:
in_flux_lines = True
continue
if '0    0   0.0000E+00  0.0000' in line:
in_flux_lines = False
break
if in_flux_lines:
vals = line.split()
ri = int(vals[0])-1
ei = int(vals[1])-1
fval = np.float(vals[2])
ferr = np.float(vals[3])

flux[ri,ei,0] = ebins[ei]
flux[ri,ei,1] = ebins[ei+1]
flux[ri,ei,2] = fval
flux[ri,ei,3] = fval*ferr

if return_metadata:
return flux, dtrk_metadata
else:
return flux
 def quotient(n, d) 

## Description

Calculate the quotient of two value, returning 0 if either the numerator or demoninator equal zero

## Inputs

• n = numerator (float ot int)
• d = denominator (float ot int)

## Outputs

• q = quotient of n and d (q=n/d)
Expand source code
def quotient(n,d):
'''
Description:
Calculate the quotient of two value, returning 0 if either the numerator or demoninator equal zero
Inputs:
- n = numerator (float ot int)
- d = denominator (float ot int)
Outputs:
- q = quotient of n and d (q=n/d)
'''
if n != 0 and d != 0:
q = n/d
else:
q = 0
return q
 def r_squared(y, y_fit) 

## Description

Calculate R^2 (R-squared) value between two sets of data, an experimental "y" and fitted "y_fit"

## Inputs

• y = list/array of y values (experimental)
• y_fit = list/array of fitted y values to be compared against y

## Outputs

• r_squared() = calculated R-squared value
Expand source code
def r_squared(y,y_fit):
'''
Description:
Calculate R^2 (R-squared) value between two sets of data, an experimental "y" and fitted "y_fit"

Inputs:
- y = list/array of y values (experimental)
- y_fit = list/array of fitted y values to be compared against y

Outputs:
- r_squared = calculated R-squared value
'''
# Calculate R^2
# residual sum of squares
ss_res = np.sum((y-y_fit)**2)
# total sum of squares
ss_tot = np.sum((y-np.mean(y))**2)
# r-squared
r2 = 1-(ss_res/ss_tot)
return r2
 def rebinner(output_xbins, input_xbins, input_ybins) 

## Description

The purpose of this function is to rebin a set of y values corresponding to a set of x bins to a new set of x bins. The function seeks to be as generalized as possible, meaning bin sizes do not need to be consistent.

## Dependencies

import numpy as np

## Inputs

• output_xbins = output array containing bounds of x bins of length N; first entry is leftmost bin boundary
• input_xbins = input array containing bounds of x bins of length M; first entry is leftmost bin boundary
• input_ybins = input array containing y values of length M-1

## Outputs

• output_ybins = output array containing y values of length N-1
Expand source code
def rebinner(output_xbins,input_xbins,input_ybins):
"""
Description:
The purpose of this function is to rebin a set of y values corresponding to a set of x bins to a new set of x bins.
The function seeks to be as generalized as possible, meaning bin sizes do not need to be consistent.

Dependencies:
import numpy as np

Inputs:
- output_xbins = output array containing bounds of x bins of length N; first entry is leftmost bin boundary
- input_xbins  = input array containing bounds of x bins of length M; first entry is leftmost bin boundary
- input_ybins  = input array containing y values of length M-1

Outputs:
- output_ybins = output array containing y values of length N-1
"""

N = len(output_xbins)
M = len(input_xbins)
output_ybins = np.zeros(N-1)

for i in range(0,N-1):
# For each output bin
lxo = output_xbins[i]   # lower x value of output bin
uxo = output_xbins[i+1] # upper x value of output bin
dxo = uxo - lxo         # width of current x output bin

# Scan input x bins to see if any fit in this output bin
for j in range(0,M-1):
lxi = input_xbins[j]    # lower x value of input bin
uxi = input_xbins[j+1]  # upper x value of input bin
dxi = uxi - lxi         # width of current x input bin

if uxi<lxo or lxi>uxo:
# no bins are aligned
continue
elif lxi >= lxo and lxi < uxo:
# start of an input bin occurs in this output bin
if lxi >= lxo and uxi <= uxo:
# input bin completely encompassed by output bin
output_ybins[i] = output_ybins[i] + input_ybins[j]
else:
# input bin spans over at least one output bin
# count fraction in current output x bin
f_in_dxo = (uxo-lxi)/dxi
output_ybins[i] = output_ybins[i] + f_in_dxo*input_ybins[j]
elif lxi < lxo and uxi > uxo:
# output bin is completely encompassed by input bin
f_in_dxo = (uxo-lxo)/dxi
output_ybins[i] = output_ybins[i] + f_in_dxo*input_ybins[j]
elif lxi < lxo and uxi > lxo and uxi <= uxo:
# tail of input bin is located in this output bin
f_in_dxo = (uxi-lxo)/dxi
output_ybins[i] = output_ybins[i] + f_in_dxo*input_ybins[j]

return output_ybins
 def relative_error_to_N(relerr) 

## Description

Convert a relative uncertainty to a "N" value

## Inputs

• relerr = a relative uncertainty

## Outputs

• N = "number of counts"-like number
Expand source code
def relative_error_to_N(relerr):
'''
Description:
Convert a relative uncertainty to a "N" value
Inputs:
- relerr = a relative uncertainty
Outputs:
- N = "number of counts"-like number
'''
if relerr == 0:
N = 0
else:
N = (1/(relerr**2))
return N
 def rgb_to_hex(rgb) 

## Description

Return hexidecimal color string #rrggbb for the color given as a tuple (red, green, blue, opacity)

## Inputs

• rgb = tuple containing RGB values and (optionally) opacity, (red, green, blue, opacity); note that the opacity value is not preserved when converted to hex

## Outputs

• string of hexidecimal color formatted as #rrggbb

## Notes

Each RGB tuple value needs to be either between 0 and 1 or between 0 and 255. If no values greater than 1 are provided in the rgb tuple, they will be automatically renormalized to 255. Additionally, any values outside of the bounds will be reassigned to the nearest bound value.

Expand source code
def rgb_to_hex(rgb):
'''
Description:
Return hexidecimal color string #rrggbb for the color given as a tuple (red, green, blue, opacity)

Inputs:
- rgb = tuple containing RGB values and (optionally) opacity,  (red, green, blue, opacity); note that
the opacity value is not preserved when converted to hex

Outputs:
- string of hexidecimal color formatted as #rrggbb

Notes:
Each RGB tuple value needs to be either between 0 and 1 or between 0 and 255.  If no values greater
than 1 are provided in the rgb tuple, they will be automatically renormalized to 255.
Additionally, any values outside of the bounds will be reassigned to the nearest bound value.
'''
if any(rgb[i]>255 or rgb[i]<0 for i in range(3)):
print('Warning: Check that rgb values of color tuple are within bounds [0,255].')
if any(rgb[i]>1 for i in range(3)):
nrm_fac = 1
else:
nrm_fac = 255
hex_col_str = '#%02x%02x%02x' % (max(0, min(int(nrm_fac*rgb[0]), 255)),max(0, min(int(nrm_fac*rgb[1]), 255)),max(0, min(int(nrm_fac*rgb[2]), 255)))
return hex_col_str
 def round_down_to_next_multiple(value, mult=1) 

## Description

Round a number down to its nearest multiple of some value

## Dependencies

import numpy as np

## Input

• value = number to be rounded
• mult = multiple to be rounded to (D=1, next lower integer)

## Output

• value rounded down to its nearest multiple of mult
Expand source code
def round_down_to_next_multiple(value,mult=1):
'''
Description:
Round a number down to its nearest multiple of some value

Dependencies:
import numpy as np

Input:
- value = number to be rounded
- mult = multiple to be rounded to (D=1, next lower integer)

Output:
- value rounded down to its nearest multiple of mult
'''
round_val = np.floor(value/mult)*mult
if isinstance(mult,int) or (abs(round_val)%1<0.01): round_val = int(round_val)
return round_val
 def round_up_to_next_multiple(value, mult=1) 

## Description

Round a number up to its nearest multiple of some value

## Dependencies

import numpy as np

## Input

• value = number to be rounded
• mult = multiple to be rounded to (D=1, next higher integer)

## Output

• value rounded up to its nearest multiple of mult
Expand source code
def round_up_to_next_multiple(value,mult=1):
'''
Description:
Round a number up to its nearest multiple of some value

Dependencies:
import numpy as np

Input:
- value = number to be rounded
- mult = multiple to be rounded to (D=1, next higher integer)

Output:
- value rounded up to its nearest multiple of mult
'''
round_val = np.ceil(value/mult)*mult
if isinstance(mult,int) or (abs(round_val)%1<0.01): round_val = int(round_val)
return round_val
 def scan_folder_for_file(path, filename) 

## Description

Provided a path to a folder and a filename, determine if the file is present in the folder or any of its subfolders

## Dependencies

import os

## Inputs

• path = string, path to folder (containing files, folders, and/or nested subfolders)
• filename = string, filename to search for in folders

## Outputs

• logical variable True or False stating whether the filename exists anywhere in the specified path and it's subdirectories
Expand source code
def scan_folder_for_file(path,filename):
'''
Description:
Provided a path to a folder and a filename, determine if the file is present in the folder or any of its subfolders

Dependencies:
import os

Inputs:
- path = string, path to folder (containing files, folders, and/or nested subfolders)
- filename = string, filename to search for in folders

Outputs:
- logical variable True or False stating whether the filename exists anywhere in the specified path and it's subdirectories
'''
file_found = False
#for entry in os.scandir(final_exp_folder_path):
for root, dirs, files in os.walk(path):
for name in files:
if name == filename:
file_found = True
return file_found
return file_found
 def seconds_to_dhms(t_sec) 

## Description

Provide a time in seconds and obtain a string with the time in days, hours, minutes, and seconds

## Inputs

• t_sec = a time in seconds (float or int)

## Outputs

• time_str = string containing the time prettily formatted in d/h/m/s format
Expand source code
def seconds_to_dhms(t_sec):
'''
Description:
Provide a time in seconds and obtain a string with the time in days, hours, minutes, and seconds

Inputs:
- t_sec = a time in seconds (float or int)

Outputs:
- time_str = string containing the time prettily formatted in d/h/m/s format

'''
m, s = divmod(t_sec, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)

if d != 0:
time_str = "{:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(d,h,m,s)
elif h != 0:
time_str = "{:0.0f}h {:0.0f}m {:0.2f}s".format(h,m,s)
elif m != 0:
time_str = "{:0.0f}m {:0.2f}s".format(m,s)
elif s != 0:
time_str = "{:0.2f}s".format(s)
else:
time_str = ""

return time_str
 def seconds_to_human_time(t_sec, time_style='mixed', abbreviation_style='shortest') 

## Description

Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds, presented in the shortest and most human-friendly/readable format

## Inputs

(required)

• t_sec = a time in seconds (float or int)

## Inputs

(optional)

• time_style = string denoting whether times are split into mixed units ('mixed', default, e.g. 1d 12h), one value in the largest units ('single_largest', e.g. 1.5d), or one value in the largest integer units ('single_integer', e.g. 36h)
• abbreviation_style = string denoting style of abbreviation (D='shortest'); select from: ['shortest','normal','none'], for example: h/hr(s)/hour(s), m/min/minute(s)

## Outputs

• time_str = string containing the time prettily formatted in the selected format
Expand source code
def seconds_to_human_time(t_sec,time_style='mixed',abbreviation_style='shortest'):
'''
Description:
Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds,
presented in the shortest and most human-friendly/readable format

Inputs:
(required)

- t_sec = a time in seconds (float or int)

Inputs:
(optional)

- time_style = string denoting whether times are split into mixed units ('mixed', default, e.g. 1d 12h),
one value in the largest units ('single_largest', e.g. 1.5d), or one value in the largest
integer units ('single_integer', e.g. 36h)
- abbreviation_style = string denoting style of abbreviation (D='shortest'); select from: ['shortest','normal','none'],
for example: h/hr(s)/hour(s), m/min/minute(s)

Outputs:
- time_str = string containing the time prettily formatted in the selected format

'''

if abbreviation_style=='shortest':
tu_strs = ['s','m','h','d','y']
elif abbreviation_style=='normal':
tu_strs = [' sec',' min',' hr',' day',' yr']
elif abbreviation_style=='none':
tu_strs = [' second',' minute',' hour',' day',' year']
else:
print('Check value of "abbreviation_style"; assuming "shortest" was intended.')
abbreviation_style = 'shortest'
tu_strs = ['s','m','h','d','y']

m, s = divmod(t_sec, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
y, d = divmod(d, 365)

if y>=4 : # if leap year occurred
n_leap_years = int(y/4)
d = d-n_leap_years

time_str = ''

if time_style=='single_integer':
divs = [60,60,24,365.25]
t = t_sec
for i in range(len(divs)):
t_new = t/divs[i]
if not t_new.is_integer():
time_str = "{:8.4g}{:}".format(t,tu_strs[i])
if t>1.0 and ((abbreviation_style=='normal' and i!=0 and i!=1) or abbreviation_style=='none'):
time_str += 's'
break
else:
t = t_new

elif time_style=='single_largest':
divs = [365.25,24,60,60]
t_yr = t_sec/(60*60*24*365.25)
t = t_yr
for i in range(len(divs)+1):
if round(t,6)>=1.0:
time_str = "{:8.4g}{:}".format(t,tu_strs[4-i])
if t>1.0 and ((abbreviation_style=='normal' and i!=4 and i!=3) or abbreviation_style=='none'):
time_str += 's'
break
else:
t = t*divs[i]

else:
vals = [y,d,h,m,s]
for vi in range(len(vals)):
if vals[vi] != 0:
if vi==4:
time_str += " {:0.2g}{:}".format(vals[vi],tu_strs[4-vi]) # 4 = number of total time units - 1
else:
time_str += " {:0.0g}{:}".format(vals[vi],tu_strs[4-vi]) # 4 = number of total time units - 1
# make units plural if warranted
if vals[vi]>1.0 and ((abbreviation_style=='normal' and vi!=4 and vi!=3) or abbreviation_style=='none'):
time_str += 's'

if time_str=='': time_str = '0{:}'.format(tu_strs[0])
time_str = time_str.strip()

return time_str
 def seconds_to_ydhms(t_sec) 

## Description

Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds

## Inputs

• t_sec = a time in seconds (float or int)

## Outputs

• time_str = string containing the time prettily formatted in y/d/h/m/s format
Expand source code
def seconds_to_ydhms(t_sec):
'''
Description:
Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds

Inputs:
- t_sec = a time in seconds (float or int)

Outputs:
- time_str = string containing the time prettily formatted in y/d/h/m/s format

'''
m, s = divmod(t_sec, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
y, d = divmod(d, 365)

if y>=4 : # if leap year occurred
n_leap_years = int(y/4)
d = d-n_leap_years

if y != 0:
time_str = "{:0.0f}y {:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(y,d,h,m,s)
elif d != 0:
time_str = "{:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(d,h,m,s)
elif h != 0:
time_str = "{:0.0f}h {:0.0f}m {:0.2f}s".format(h,m,s)
elif m != 0:
time_str = "{:0.0f}m {:0.2f}s".format(m,s)
elif s != 0:
time_str = "{:0.2f}s".format(s)
else:
time_str = ""

return time_str
 def slugify(value) 

## Description

Normalizes string, converts to lowercase, removes non-alpha characters,and converts spaces to hyphens.
This is useful for quickly converting TeX strings, plot titles, etc. into legal filenames.

## Dependencies

• import unicodedata as ud
• import re

## Input

• value = string to be "slugified"

## Output

• value converted to a string only consisting of characters legal in filenames
Expand source code
def slugify(value):
'''
Description:
Normalizes string, converts to lowercase, removes non-alpha characters,and converts spaces to hyphens.
This is useful for quickly converting TeX strings, plot titles, etc. into legal filenames.

Dependencies:
- import unicodedata as ud
- import re

Input:
- value = string to be "slugified"

Output:
- value converted to a string only consisting of characters legal in filenames
'''
old_value = value
value = str(ud.normalize('NFKD', value).encode('ascii', 'ignore'))
value = str(re.sub('[^\w\s-]', '', value).strip().lower())
value = str(re.sub('[-\s]+', '-', value))
if value[0]=='b' and old_value[0]!='b': value = value[1:] # TeX strings sometimes case resulting string to being with 'b'
return value
 def table_array_generator(core_table, row_headers=None, row_header_spans=None, column_headers=None, column_header_spans=None, float_formatting='{:g}') 

## Description

Generates a properly populated array which can be easily converted to a table.

## Dependencies

import numpy as np

## Notes

At its core, a table at minimum consists of an array of 1x1 cells. In addition, it may be padded to its left and top with row and column headers, respectively, which may span multiple rows/columns.
The number of row-header columns or column-header rows can be arbitrary.

## Inputs

(required)

• core_table = an RxC array (number of rows x number of columns) or equivalent list of table values constituting the core portion of the table composed of 1x1 cell elements

## Inputs

(optional)

• row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable:
• row_header_spans = A list (or list of lists) of the number of rows each row header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead.
• column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable:
• column_header_spans = A list (or list of lists) of the number of column each column header spans. If these are to be uniform for all entries, a single value (or list of single values) may be provided instead.
• float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}')

## Outputs

• table_array = an (R+num_col_heads)x(C+num_row_heads) array of strings forming the table
Expand source code
def table_array_generator(core_table,row_headers=None,row_header_spans=None,column_headers=None,column_header_spans=None,float_formatting='{:g}'):
'''
Description:
Generates a properly populated array which can be easily converted to a table.

Dependencies:
import numpy as np

Notes:

At its core, a table at minimum consists of an array of 1x1 cells.  In addition, it may be padded to
its left and top with row and column headers, respectively, which may span multiple rows/columns.
The number of row-header columns or column-header rows can be arbitrary.

Inputs:
(required)

- core_table = an RxC array (number of rows x number of columns) or equivalent list of table values constituting
the core portion of the table composed of 1x1 cell elements

Inputs:
(optional)

- row_headers = A list (or list of lists) of row headers; it must be accompanied by the below variable:
- row_header_spans = A list (or list of lists) of the number of rows each row header spans.  If these are to
be uniform for all entries, a single value (or list of single values) may be provided instead.
- column_headers = A list (or list of lists) of column headers; it must be accompanied by the below variable:
- column_header_spans = A list (or list of lists) of the number of column each column header spans.  If these are to
be uniform for all entries, a single value (or list of single values) may be provided instead.
- float_formatting = default formatting string for floating point numbers passed to this function (D='{:g}')

Outputs:
- table_array = an (R+num_col_heads)x(C+num_row_heads) array of strings forming the table
'''

core_num_rows, core_num_cols = np.shape(core_table)
core_table = np.array(core_table)

# identify structure of row and column headers, if included
num_row_header_columns = 0
if row_headers:
if not isinstance(row_headers,list):
print('row_headers is not a list (or list of lists), quitting...')
return None
if not row_header_spans:
print('row_header_spans must also be provided, quitting...')
return None
if isinstance(row_headers[0],list):
num_row_header_columns = len(row_headers)
else:
num_row_header_columns = 1

num_column_header_rows = 0
if column_headers:
if not isinstance(column_headers,list):
print('column_headers is not a list (or list of lists), quitting...')
return None
if not column_header_spans:
print('column_header_spans must also be provided, quitting...')
return None
if isinstance(column_headers[0],list):
num_column_header_rows = len(column_headers)
else:
num_column_header_rows = 1

nrows = core_num_rows + num_column_header_rows
ncols = core_num_cols + num_row_header_columns

# initialize table array
table_array = np.empty((nrows,ncols),dtype=object)
for ri in range(nrows):
for ci in range(ncols):
table_array[ri,ci] = ' '

# Now populate its values
# first, put core table into the bottom right of table
table_array[-core_num_rows:,-core_num_cols:] = core_table

# then row headers...
if row_headers:
for rhi in range(num_row_header_columns-1,-1,-1):
ci = rhi
if num_row_header_columns==1:
this_header_set = row_headers
this_header_spans = row_header_spans
else:
this_header_set = row_headers[rhi]
this_header_spans = row_header_spans[rhi]

if not isinstance(this_header_spans,list):
tmp = this_header_spans
this_header_spans = [tmp for i in this_header_set]

if np.sum(this_header_spans) > nrows:
print('Total span of row headers ({}) exceeds total number of rows calculated ({}); please reassess index {} of row headers lists'.format(str(int(np.sum(this_header_spans))),str(nrows),str(rhi)))
return None

ri = nrows
for hei in range(len(this_header_set)-1,-1,-1):
ri = ri - this_header_spans[hei]
if isinstance(this_header_set[hei],str):
val = this_header_set[hei]
elif isinstance(this_header_set[hei],float):
val=float_formatting.format(this_header_set[hei])
else:
val = str(this_header_set[hei])
table_array[ri,ci] = val

# then column headers...
if column_headers:
for chi in range(num_column_header_rows-1,-1,-1):
ri = chi
if num_column_header_rows==1:
this_header_set = column_headers
this_header_spans = column_header_spans
else:
this_header_set = column_headers[chi]
this_header_spans = column_header_spans[chi]

if not isinstance(this_header_spans,list):
tmp = this_header_spans
this_header_spans = [tmp for i in this_header_set]

if np.sum(this_header_spans) > ncols:
print('Total span of column headers ({}) exceeds total number of columns calculated ({}); please reassess index {} of column headers lists'.format(str(int(np.sum(this_header_spans))),str(ncols),str(chi)))
return None

ci = ncols
for hei in range(len(this_header_set)-1,-1,-1):
ci = ci - this_header_spans[hei]
if isinstance(this_header_set[hei],str):
val = this_header_set[hei]
elif isinstance(this_header_set[hei],float):
val=float_formatting.format(this_header_set[hei])
else:
val = str(this_header_set[hei])
table_array[ri,ci] = val

return table_array
 def tally(data, bin_edges=[], min_bin_left_edge=None, max_bin_right_edge=None, nbins=None, bin_width=None, divide_by_bin_width=False, normalization=None, scaling_factor=1, place_overflow_at_ends=True, return_uncertainties=False, return_event_indices_histogram=False) 

## Description

Tally number of incidences of values falling within a desired binning structure

## Inputs

• data = list of values to be tallied/histogrammed
• bin_edges = list of N+1 bin edge values for a tally of N bins
• min_bin_left_edge = left/minimum edge value of the first bin
• max_bin_right_edge = right/maximum edge value of the last bin
• nbins = number of equally-sized bins to be created from min_bin_left_edge to max_bin_right_edge
• bin_width = constant width of bins to be created from min_bin_left_edge to max_bin_right_edge
• divide_by_bin_width = Boolean denoting whether final bin values are divided by their bin widths (D=False)
• normalization = determine how the resulting histogram is normalized (D=None), options are: [None, 'unity-sum', 'unity-max-val']. If None, no additional normalization is done. If unity-sum, the data is normalized such that its sum will be 1. If unity-max-val, the data is normalized such that the maximum value is 1. The operation occurs after any bin width normalization from divide_by_bin_width but before any scaling from scaling_factor.
• scaling_factor = value which all final bins are multiplied/scaled by (D=1)
• place_overflow_at_ends = handling of values outside of binning range (D=True); if True extreme values are tallied in the first/last bin, if False extreme values are discarded
• return_uncertainties = Boolean denoting if should return an extra N-length list whose elements
are the statistical uncertainties (square root w/ normalizations) of the tally bins (D=False)
• return_event_indices_histogram = Boolean denoting if should return an extra N-length list whose elements
are each a list of the event indices corresponding to each bin (D=False)

## Notes

Regarding the binning structure, this function only needs to be provided bin_edges directly (takes priority) or the information needed to calculate bin_edges, that is: min_bin_left_edge and max_bin_right_edge and either nbins or bin_width. (Priority is given to nbins if both are provided.)

## Outputs

• tallied_hist = N-length list of tallied data
• bin_edges = list of N+1 bin edge values for a tally of N bins
• tallied_hist_err = (optional) N-length list of statistical uncertainties of tallied data
• tallied_event_indicies = (optional) N-length list of, for each bin, a list of the event indices populating it
Expand source code
def tally(data, bin_edges=[], min_bin_left_edge=None, max_bin_right_edge=None, nbins=None, bin_width=None, divide_by_bin_width=False, normalization=None, scaling_factor=1, place_overflow_at_ends=True, return_uncertainties=False, return_event_indices_histogram=False):
'''
Description:
Tally number of incidences of values falling within a desired binning structure

Inputs:
- data = list of values to be tallied/histogrammed
- bin_edges = list of N+1 bin edge values for a tally of N bins
- min_bin_left_edge = left/minimum edge value of the first bin
- max_bin_right_edge = right/maximum edge value of the last bin
- nbins = number of equally-sized bins to be created from min_bin_left_edge to max_bin_right_edge
- bin_width = constant width of bins to be created from min_bin_left_edge to max_bin_right_edge
- divide_by_bin_width = Boolean denoting whether final bin values are divided by their bin widths (D=False)
- normalization = determine how the resulting histogram is normalized (D=None), options are:
[None, 'unity-sum', 'unity-max-val'].  If None, no additional normalization is done.
If unity-sum, the data is normalized such that its sum will be 1.  If unity-max-val, the
data is normalized such that the maximum value is 1.  The operation occurs after any bin
width normalization from divide_by_bin_width but before any scaling from scaling_factor.
- scaling_factor = value which all final bins are multiplied/scaled by (D=1)
- place_overflow_at_ends = handling of values outside of binning range (D=True); if True extreme
values are tallied in the first/last bin, if False extreme values are discarded
- return_uncertainties = Boolean denoting if should return an extra N-length list whose elements
are the statistical uncertainties (square root w/ normalizations) of the tally bins (D=False)
- return_event_indices_histogram = Boolean denoting if should return an extra N-length list whose elements
are each a list of the event indices corresponding to each bin (D=False)

Notes:
Regarding the binning structure, this function only needs to be provided bin_edges directly (takes priority)
or the information needed to calculate bin_edges, that is: min_bin_left_edge and max_bin_right_edge and
either nbins or bin_width.  (Priority is given to nbins if both are provided.)

Outputs:
- tallied_hist = N-length list of tallied data
- bin_edges = list of N+1 bin edge values for a tally of N bins
- tallied_hist_err = (optional) N-length list of statistical uncertainties of tallied data
- tallied_event_indicies = (optional) N-length list of, for each bin, a list of the event indices populating it
'''

normalization_valid_entries = [None, 'unity-sum', 'unity-max-val']
if normalization not in normalization_valid_entries:
print("Entered normalization option of ",normalization," is not a valid option; please select from the following: [None, 'unity-sum', 'unity-max-val']".format())

if len(bin_edges)!=0:
bin_edges = np.array(bin_edges)
else:
if nbins != None:
bin_edges = np.linspace(min_bin_left_edge,max_bin_right_edge,num=nbins+1)
else:
bin_edges = np.arange(min_bin_left_edge,max_bin_right_edge+bin_width,step=bin_width)

nbins = len(bin_edges) - 1

if return_event_indices_histogram:
tallied_event_indicies = []
tallied_hist = np.zeros(nbins)
for i in range(nbins):
tallied_event_indicies.append([])
# events must be histogrammed manually
for i, val in enumerate(data):
if val < bin_edges[0]:
if place_overflow_at_ends:
tallied_hist[0] += 1
tallied_event_indicies[0].append(i)
continue
if val > bin_edges[-1]:
if place_overflow_at_ends:
tallied_hist[-1] += 1
tallied_event_indicies[-1].append(i)
continue
for j, be in enumerate(bin_edges):
if be > val: # found right edge of bin containing val
tallied_hist[j-1] += 1
tallied_event_indicies[j-1].append(i)
break

else:
tallied_hist, bins = np.histogram(data,bins=bin_edges)

if return_uncertainties:
tallied_hist_err = np.sqrt(tallied_hist)
if divide_by_bin_width: tallied_hist_err = tallied_hist_err/(bin_edges[1:]-bin_edges[:-1])
if normalization=='unity-sum': tallied_hist_err = tallied_hist_err/np.sum(tallied_hist)
if normalization=='unity-max-val': tallied_hist_err = tallied_hist_err/np.max(tallied_hist)
if scaling_factor != 1: tallied_hist_err = tallied_hist_err*scaling_factor

if divide_by_bin_width: tallied_hist = tallied_hist/(bin_edges[1:]-bin_edges[:-1])
if normalization=='unity-sum': tallied_hist = tallied_hist/np.sum(tallied_hist)
if normalization=='unity-max-val': tallied_hist = tallied_hist/np.max(tallied_hist)
if scaling_factor != 1: tallied_hist = tallied_hist*scaling_factor

if return_event_indices_histogram:
if return_uncertainties:
return tallied_hist,bin_edges,tallied_hist_err,tallied_event_indicies
else:
return tallied_hist,bin_edges,tallied_event_indicies
else:
if return_uncertainties:
return tallied_hist,bin_edges,tallied_hist_err
else:
return tallied_hist,bin_edges
 def time_str_to_sec_multiplier(time_str) 

## Description

Provide a time unit and this function provides what those time units need to be multiplied by to obtain seconds.

## Inputs

• time_str = string containing time units character(s) [s,m,h,d,y,ms,us,ns,ps,fs]

## Outputs

• m = multiplier to convert a time of the supplied units to seconds
Expand source code
def time_str_to_sec_multiplier(time_str):
'''
Description:
Provide a time unit and this function provides what those time units need to be multiplied by to obtain seconds.

Inputs:
- time_str = string containing time units character(s) [s,m,h,d,y,ms,us,ns,ps,fs]

Outputs:
- m = multiplier to convert a time of the supplied units to seconds

'''
try:
if time_str == 's':
m = 1
elif time_str == 'm':
m = 60
elif time_str == 'h':
m = 60*60
elif time_str == 'd':
m = 60*60*24
elif time_str == 'y':
m = 60*60*24*365.25
elif time_str == 'ms':
m = 1e-3
elif time_str == 'us':
m = 1e-6
elif time_str == 'ns':
m = 1e-9
elif time_str == 'ps':
m = 1e-12
elif time_str == 'fs':
m = 1e-15
return m
except:
print('"{}" is not a valid time unit; please use one of the following: [s,m,h,d,y,ms,us,ns,ps,fs]'.format(time_str))
return None
 def time_units_converter(t1_val, t1_units='s', t2_units='s') 

## Description

Provide a decimal time, its current units, and the desired units to obtain a new decimal time in the desired units

## Inputs

• t1_val = a decimal time value (float or int)
• t1_units = (optional) a time units string for the input time value (D='s'); select from: ['s','m','h','d','y','ms','us','ns','ps','fs'] (str)
• t2_units = (optional) a time units string for the output time value (D='s'); select from: ['s','m','h','d','y','ms','us','ns','ps','fs'] (str)

## Outputs

• t2_val = a decimal time value in the units specified by t2_units (float)
Expand source code
def time_units_converter(t1_val,t1_units='s',t2_units='s'):
'''
Description:
Provide a decimal time, its current units, and the desired units to obtain a new decimal time in the desired units

Inputs:
- t1_val = a decimal time value (float or int)
- t1_units = (optional) a time units string for the input time value (D='s'); select from: ['s','m','h','d','y','ms','us','ns','ps','fs'] (str)
- t2_units = (optional) a time units string for the output time value (D='s'); select from: ['s','m','h','d','y','ms','us','ns','ps','fs'] (str)

Outputs:
- t2_val = a decimal time value in the units specified by t2_units (float)

'''
t1_sec = t1_val*time_str_to_sec_multiplier(t1_units)
t2_val = t1_sec/time_str_to_sec_multiplier(t2_units)

return t2_val
 def truncate_colormap(cmap, min_val=0.0, max_val=1.0, n=100) 

## Description

Truncate a colormap object's bounds to a subset of the original colormap, renormalizing new bounds to [0,1]. For instance, the upper and lower 20% of a colormap could be removed with cmap=truncate_colormap(cmap, min_val=0.2, max_val=0.8).

## Dependencies

• from matplotlib.colors import LinearSegmentedColormap
• import numpy as np

## Inputs

• cmap = callable colormap object or string of matplotlib colormap name to be truncated and rescaled
• min_val = float specifying new lower bound of cmap in [0,1) (D=0.0)
• max_val = float specifying new upper bound of cmap in (0,1] (D=1.0)
• n = integer specifying number of bins cmap will be subdivided into (D=100); note that nearest neighbor linear interpolation will be used when providing values to and evaluating the new colormap. Higher values of n just correspond to a higher-quality and more accurate interpolation.

## Outputs

• callable colormap object which can be provided to plotting functions or evaluated for any value from 0 to 1
Expand source code
def truncate_colormap(cmap, min_val=0.0, max_val=1.0, n=100):
'''
Description:
Truncate a colormap object's bounds to a subset of the original colormap, renormalizing new bounds to [0,1].
For instance, the upper and lower 20% of a colormap could be removed with cmap=truncate_colormap(cmap, min_val=0.2, max_val=0.8).

Dependencies:
- from matplotlib.colors import LinearSegmentedColormap
- import numpy as np

Inputs:
- cmap = callable colormap object or string of matplotlib colormap name to be truncated and rescaled
- min_val = float specifying new lower bound of cmap in [0,1) (D=0.0)
- max_val = float specifying new upper bound of cmap in (0,1] (D=1.0)
- n = integer specifying number of bins cmap will be subdivided into (D=100); note that nearest neighbor
linear interpolation will be used when providing values to and evaluating the new colormap.
Higher values of n just correspond to a higher-quality and more accurate interpolation.

Outputs:
- callable colormap object which can be provided to plotting functions or evaluated for any value from 0 to 1
'''
if isinstance(cmap,str): cmap = get_colormap(cmap)
new_cmap = LinearSegmentedColormap.from_list(
'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=min_val, b=max_val),
cmap(np.linspace(min_val, max_val, n)))
return new_cmap