"""This module contains functions relevant to the ALARA activation code and the Chebyshev Rational Approximation Method
"""
from __future__ import print_function
import os
import collections
from warnings import warn
from pyne.utils import QAWarning
import numpy as np
import tables as tb
warn(__name__ + " is not yet QA compliant.", QAWarning)
try:
basestring
except NameError:
basestring = str
try:
from itaps import iMesh, iBase, iMeshExtensions
except ImportError:
warn("the PyTAPS optional dependency could not be imported. "
"Some aspects of the alara module may be incomplete.",
QAWarning)
from pyne.mesh import Mesh, MeshError
from pyne.material import Material, from_atom_frac
from pyne import nucname
from pyne.nucname import serpent, alara, znum, anum
from pyne.data import N_A, decay_const, decay_children, branch_ratio
from pyne.xs.data_source import SimpleDataSource
[docs]def mesh_to_fluxin(flux_mesh, flux_tag, fluxin="fluxin.out",
reverse=False):
"""This function creates an ALARA fluxin file from fluxes tagged on a PyNE
Mesh object. Fluxes are printed in the order of the flux_mesh.__iter__().
Parameters
----------
flux_mesh : PyNE Mesh object
Contains the mesh with fluxes tagged on each volume element.
flux_tag : string
The name of the tag of the flux mesh. Flux values for different energy
groups are assumed to be represented as vector tags.
fluxin : string
The name of the ALARA fluxin file to be output.
reverse : bool
If true, fluxes will be printed in the reverse order as they appear in
the flux vector tagged on the mesh.
"""
tag_flux = flux_mesh.mesh.getTagHandle(flux_tag)
# find number of e_groups
e_groups = flux_mesh.mesh.getTagHandle(flux_tag)[list(
flux_mesh.mesh.iterate(iBase.Type.region,
iMesh.Topology.all))[0]]
e_groups = np.atleast_1d(e_groups)
num_e_groups = len(e_groups)
# Establish for loop bounds based on if forward or backward printing
# is requested
if not reverse:
start = 0
stop = num_e_groups
direction = 1
else:
start = num_e_groups - 1
stop = -1
direction = -1
output = ""
for i, mat, ve in flux_mesh:
# print flux data to file
count = 0
flux_data = np.atleast_1d(tag_flux[ve])
for i in range(start, stop, direction):
output += "{:.6E} ".format(flux_data[i])
# fluxin formatting: create a new line after every 6th entry
count += 1
if count % 6 == 0:
output += "\n"
output += "\n\n"
with open(fluxin, "w") as f:
f.write(output)
[docs]def photon_source_to_hdf5(filename, chunkshape=(10000,)):
"""Converts a plaintext photon source file to an HDF5 version for
quick later use.
This function produces a single HDF5 file named <filename>.h5 containing the
table headings:
idx : int
The volume element index assuming the volume elements appear in xyz
order (z changing fastest) within the photon source file in the case of
a structured mesh or imesh.iterate() order for an unstructured mesh.
nuc : str
The nuclide name as it appears in the photon source file.
time : str
The decay time as it appears in the photon source file.
phtn_src : 1D array of floats
Contains the photon source density for each energy group.
Parameters
----------
filename : str
The path to the file
chunkshape : tuple of int
A 1D tuple of the HDF5 chunkshape.
"""
f = open(filename, 'r')
header = f.readline().strip().split('\t')
f.seek(0)
G = len(header) - 2
dt = np.dtype([
('idx', np.int64),
('nuc', 'S6'),
('time', 'S20'),
('phtn_src', np.float64, G),
])
filters = tb.Filters(complevel=1, complib='zlib')
h5f = tb.open_file(filename + '.h5', 'w', filters=filters)
tab = h5f.create_table('/', 'data', dt, chunkshape=chunkshape)
chunksize = chunkshape[0]
rows = np.empty(chunksize, dtype=dt)
idx = 0
old = ""
for i, line in enumerate(f, 1):
ls = line.strip().split('\t')
# Keep track of the idx by delimiting by the last TOTAL line in a
# volume element.
if ls[0] != 'TOTAL' and old == 'TOTAL':
idx += 1
j = (i-1) % chunksize
rows[j] = (idx, ls[0].strip(), ls[1].strip(),
np.array(ls[2:], dtype=np.float64))
# Save the nuclide in order to keep track of idx
old = ls[0]
if i % chunksize == 0:
tab.append(rows)
rows = np.empty(chunksize, dtype=dt)
if i % chunksize != 0:
tab.append(rows[:j+1])
h5f.close()
f.close()
[docs]def photon_source_hdf5_to_mesh(mesh, filename, tags):
"""This function reads in an hdf5 file produced by photon_source_to_hdf5
and tags the requested data to the mesh of a PyNE Mesh object. Any
combinations of nuclides and decay times are allowed. The photon source
file is assumed to be in mesh.__iter__() order
Parameters
----------
mesh : PyNE Mesh
The object containing the imesh instance to be tagged.
filename : str
The path of the hdf5 version of the photon source file.
tags: dict
A dictionary were the keys are tuples with two values. The first is a
string denoting an nuclide in any form that is understood by
pyne.nucname (e.g. '1001', 'U-235', '242Am') or 'TOTAL' for all
nuclides. The second is a string denoting the decay time as it appears
in the file (e.g. 'shutdown', '1 h' '3 d'). The values of the
dictionary are the requested tag names for the combination of nuclide
and decay time. For example if one wanted tags for the photon source
densities from U235 at shutdown and from all nuclides at 1 hour, the
dictionary could be:
tags = {('U-235', 'shutdown') : 'tag1', ('TOTAL', '1 h') : 'tag2'}
"""
# find number of energy groups
with tb.open_file(filename) as h5f:
num_e_groups = len(h5f.root.data[0][3])
# create a dict of tag handles for all keys of the tags dict
tag_handles = {}
for tag_name in tags.values():
tag_handles[tag_name] = \
mesh.mesh.createTag(tag_name, num_e_groups, float)
# iterate through each requested nuclide/dectay time
for cond in tags.keys():
with tb.open_file(filename) as h5f:
# Convert nuclide to the form found in the ALARA phtn_src
# file, which is similar to the Serpent form. Note this form is
# different from the ALARA input nuclide form found in nucname.
if cond[0] != "TOTAL":
nuc = serpent(cond[0]).lower()
else:
nuc = "TOTAL"
# create of array of rows that match the nuclide/decay criteria
matched_data = h5f.root.data.read_where(
"(nuc == '{0}') & (time == '{1}')".format(nuc, cond[1]))
idx = 0
for i, _, ve in mesh:
if matched_data[idx][0] == i:
tag_handles[tags[cond]][ve] = matched_data[idx][3]
idx += 1
else:
tag_handles[tags[cond]][ve] = [0] * num_e_groups
[docs]def record_to_geom(mesh, cell_fracs, cell_mats, geom_file, matlib_file,
sig_figs=6):
"""This function preforms the same task as alara.mesh_to_geom, except the
geometry is on the basis of the stuctured array output of
dagmc.discretize_geom rather than a PyNE material object with materials.
This allows for more efficient ALARA runs by minimizing the number of
materials in the ALARA matlib. This is done by treating mixtures that are
equal up to <sig_figs> digits to be the same mixture within ALARA.
Parameters
----------
mesh : PyNE Mesh object
The Mesh object for which the geometry is discretized.
cell_fracs : structured array
The output from dagmc.discretize_geom(). A sorted, one dimensional
array, each entry containing the following fields:
:idx: int
The volume element index.
:cell: int
The geometry cell number.
:vol_frac: float
The volume fraction of the cell withing the mesh ve.
:rel_error: float
The relative error associated with the volume fraction.
cell_mats : dict
Maps geometry cell numbers to PyNE Material objects. Each PyNE material
object must have 'name' specified in Material.metadata.
geom_file : str
The name of the file to print the geometry and material blocks.
matlib_file : str
The name of the file to print the matlib.
sig_figs : int
The number of significant figures that two mixtures must have in common
to be treated as the same mixture within ALARA.
"""
# Create geometry information header. Note that the shape of the geometry
# (rectangular) is actually inconsequential to the ALARA calculation so
# unstructured meshes are not adversely affected.
geometry = 'geometry rectangular\n\n'
# Create three strings in order to create all ALARA input blocks in a
# single mesh iteration.
volume = 'volume\n' # volume input block
mat_loading = 'mat_loading\n' # material loading input block
mixture = '' # mixture blocks
unique_mixtures = []
for i, mat, ve in mesh:
volume += ' {0: 1.6E} zone_{1}\n'.format(mesh.elem_volume(ve), i)
ve_mixture = {}
for row in cell_fracs[cell_fracs['idx'] == i]:
cell_mat = cell_mats[row['cell']]
name = cell_mat.metadata['name']
if _is_void(name):
name = 'mat_void'
if name not in ve_mixture.keys():
ve_mixture[name] = np.round(row['vol_frac'], sig_figs)
else:
ve_mixture[name] += np.round(row['vol_frac'], sig_figs)
if ve_mixture not in unique_mixtures:
unique_mixtures.append(ve_mixture)
mixture += 'mixture mix_{0}\n'.format(
unique_mixtures.index(ve_mixture))
for key, value in ve_mixture.items():
mixture += ' material {0} 1 {1}\n'.format(key, value)
mixture += 'end\n\n'
mat_loading += ' zone_{0} mix_{1}\n'.format(i,
unique_mixtures.index(ve_mixture))
volume += 'end\n\n'
mat_loading += 'end\n\n'
with open(geom_file, 'w') as f:
f.write(geometry + volume + mat_loading + mixture)
matlib = '' # ALARA material library string
printed_mats = []
print_void = False
for mat in cell_mats.values():
name = mat.metadata['name']
if _is_void(name):
print_void = True
continue
if name not in printed_mats:
printed_mats.append(name)
matlib += '{0} {1: 1.6E} {2}\n'.format(name, mat.density,
len(mat.comp))
for nuc, comp in mat.comp.iteritems():
matlib += '{0} {1: 1.6E} {2}\n'.format(alara(nuc),
comp*100.0, znum(nuc))
matlib += '\n'
if print_void:
matlib += '# void material\nmat_void 0.0 1\nhe 1 2\n'
with open(matlib_file, 'w') as f:
f.write(matlib)
def _is_void(name):
"""Private function for determining if a material name specifies void.
"""
lname = name.lower()
return 'vacuum' in lname or 'void' in lname or 'graveyard' in lname
[docs]def mesh_to_geom(mesh, geom_file, matlib_file):
"""This function reads the materials of a PyNE mesh object and prints the
geometry and materials portion of an ALARA input file, as well as a
corresponding matlib file. If the mesh is structured, xyz ordering is used
(z changing fastest). If the mesh is unstructured iMesh.iterate order is
used.
Parameters
----------
mesh : PyNE Mesh object
The Mesh object containing the materials to be printed.
geom_file : str
The name of the file to print the geometry and material blocks.
matlib_file : str
The name of the file to print the matlib.
"""
# Create geometry information header. Note that the shape of the geometry
# (rectangular) is actually inconsequential to the ALARA calculation so
# unstructured meshes are not adversely affected.
geometry = "geometry rectangular\n\n"
# Create three strings in order to create all ALARA input blocks in a
# single mesh iteration.
volume = "volume\n" # volume input block
mat_loading = "mat_loading\n" # material loading input block
mixture = "" # mixture blocks
matlib = "" # ALARA material library string
for i, mat, ve in mesh:
volume += " {0: 1.6E} zone_{1}\n".format(mesh.elem_volume(ve), i)
mat_loading += " zone_{0} mix_{0}\n".format(i)
matlib += "mat_{0} {1: 1.6E} {2}\n".format(i, mesh.density[i],
len(mesh.comp[i]))
mixture += ("mixture mix_{0}\n"
" material mat_{0} 1 1\nend\n\n".format(i))
for nuc, comp in mesh.comp[i].iteritems():
matlib += "{0} {1: 1.6E} {2}\n".format(alara(nuc), comp*100.0,
znum(nuc))
matlib += "\n"
volume += "end\n\n"
mat_loading += "end\n\n"
with open(geom_file, 'w') as f:
f.write(geometry + volume + mat_loading + mixture)
with open(matlib_file, 'w') as f:
f.write(matlib)
[docs]def num_density_to_mesh(lines, time, m):
"""num_density_to_mesh(lines, time, m)
This function reads ALARA output containing number density information and
creates material objects which are then added to a supplied PyNE Mesh object.
The volumes within ALARA are assummed to appear in the same order as the
idx on the Mesh object.
Parameters
----------
lines : list or str
ALARA output from ALARA run with 'number_density' in the 'output' block
of the input file. Lines can either be a filename or the equivalent to
calling readlines() on an ALARA output file. If reading in ALARA output
from stdout, call split('\n') before passing it in as the lines parameter.
time : str
The decay time for which number densities are requested (e.g. '1 h',
'shutdown', etc.)
m : PyNE Mesh
Mesh object for which mats will be applied to.
"""
if isinstance(lines, basestring):
with open(lines) as f:
lines = f.readlines()
elif not isinstance(lines, collections.Sequence):
raise TypeError("Lines argument not a file or sequence.")
# Advance file to number density portion.
header = 'Number Density [atoms/cm3]'
line = ""
while line.rstrip() != header:
line = lines.pop(0)
# Get decay time index from next line (the column the decay time answers
# appear in.
line_strs = lines.pop(0).replace('\t', ' ')
time_index = [s.strip() for s in line_strs.split(' ')
if s.strip()].index(time)
# Create a dict of mats for the mesh.
mats = {}
count = 0
# Read through file until enough material objects are create to fill mesh.
while count != len(m):
# Pop lines to the start of the next material.
while (lines.pop(0) + " " )[0] != '=':
pass
# Create a new material object and add to mats dict.
line = lines.pop(0)
nucvec = {}
density = 0.0
# Read lines until '=' delimiter at the end of a material.
while line[0] != '=':
nuc = line.split()[0]
n = float(line.split()[time_index])
if n != 0.0:
nucvec[nuc] = n
density += n * anum(nuc)/N_A
line = lines.pop(0)
mat = from_atom_frac(nucvec, density=density, mass=0)
mats[count] = mat
count += 1
m.mats = mats
[docs]def irradiation_blocks(material_lib, element_lib, data_library, cooling,
flux_file, irr_time, output = "number_density",
truncation=1E-12, impurity = (5E-6, 1E-3),
dump_file = "dump_file"):
"""irradiation_blocks(material_lib, element_lib, data_library, cooling,
flux_file, irr_time, output = "number_density",
truncation=1E-12, impurity = (5E-6, 1E-3),
dump_file = "dump_file")
This function returns a string of the irradation-related input blocks. This
function is meant to be used with files created by the mesh_to_geom
function, in order to append the remaining input blocks to form a complete
ALARA input file. Only the simplest irradiation schedule is supported: a
single pulse of time <irr_time>. The notation in this function is consistent
with the ALARA users' guide, found at:
http://alara.engr.wisc.edu/users.guide.html/
Parameters
----------
material_lib : str
Path to material library.
element_lib : str
Path to element library.
data_library : str
The data_library card (see ALARA user's guide).
cooling : str or iterable of str
Cooling times for which output is requested. Given in ALARA form (e.g.
"1 h", "0.5 y"). Note that "shutdown" is always implicitly included.
flux_file : str
Path to the "fluxin" file.
irr_time : str
The duration of the single pulse irradiation. Given in the ALARA form
(e.g. "1 h", "0.5 y").
output : str or iterable of str, optional.
The requested output blocks (see ALARA users' guide).
truncation : float, optional
The chain truncation value (see ALARA users' guide).
impurity : tuple of two floats, optional
The impurity parameters (see ALARA users' guide).
dump_file: str, optional
Path to the dump file.
Returns
-------
s : str
Irradition-related ALARA input blocks.
"""
s = ""
# Material, element, and data_library blocks
s += "material_lib {0}\n".format(material_lib)
s += "element_lib {0}\n".format(element_lib)
s += "data_library {0}\n\n".format(data_library)
# Cooling times
s += "cooling\n"
if isinstance(cooling, collections.Iterable) and not isinstance(cooling, basestring):
for c in cooling:
s += " {0}\n".format(c)
else:
s += " {0}\n".format(cooling)
s += "end\n\n"
# Flux block
s += "flux flux_1 {0} 1.0 0 default\n".format(flux_file)
# Flux schedule
s += ("schedule simple_schedule\n"
" {0} flux_1 pulse_once 0 s\nend\n\n".format(irr_time))
s += "pulsehistory pulse_once\n 1 0.0 s\nend\n\n"
# Output block
s += "output zone\n units Ci cm3\n"
if isinstance(output, collections.Iterable) and not isinstance(output, basestring):
for out in output:
s += " {0}\n".format(out)
else:
s += " {0}\n".format(output)
s += "end\n\n"
# Other parameters
s += "truncation {0}\n".format(truncation)
s += "impurity {0} {1}\n".format(impurity[0], impurity[1])
s += "dump_file {0}\n".format(dump_file)
return s
[docs]def phtn_src_energy_bounds(input_file):
"""Reads an ALARA input file and extracts the energy bounds from the
photon_source block.
Parameters
----------
input_file : str
The ALARA input file name, which must contain a photon_source block.
Returns
-------
e_bounds : list of floats
The lower and upper energy bounds for the photon_source discretization.
"""
phtn_src_lines = ""
with open(input_file, 'r') as f:
line = f.readline()
while not (' photon_source ' in line and line.strip()[0] != "#"):
line = f.readline()
num_groups = float(line.split()[3])
upper_bounds = [float(x) for x in line.split()[4:]]
while len(upper_bounds) < num_groups:
line = f.readline()
upper_bounds += [float(x) for x in line.split("#")[0].split('end')[0].split()]
e_bounds = [0.] + upper_bounds
return e_bounds
def _build_matrix(N):
""" This function builds burnup matrix, A. Decay only.
"""
A = np.zeros((len(N), len(N)))
# convert N to id form
N_id = []
for i in range(len(N)):
if isinstance(N[i], str):
ID = nucname.id(N[i])
else:
ID = N[i]
N_id.append(ID)
sds = SimpleDataSource()
# Decay
for i in range(len(N)):
A[i, i] -= decay_const(N_id[i])
# Find decay parents
for k in range(len(N)):
if N_id[i] in decay_children(N_id[k]):
A[i, k] += branch_ratio(N_id[k], N_id[i])*decay_const(N_id[k])
return A
def _rat_apprx_14(A, t, n_0):
""" CRAM of order 14
Parameters
---------
A : numpy array
Burnup matrix
t : float
Time step
n_0: numpy array
Inital composition vector
"""
theta = np.array([ -8.8977731864688888199 + 16.630982619902085304j,
-3.7032750494234480603 + 13.656371871483268171j,
-.2087586382501301251 + 10.991260561901260913j,
3.9933697105785685194 + 6.0048316422350373178j,
5.0893450605806245066 + 3.5888240290270065102j,
5.6231425727459771248 + 1.1940690463439669766j,
2.2697838292311127097 + 8.4617379730402214019j])
alpha = np.array([-.000071542880635890672853 + .00014361043349541300111j,
.0094390253107361688779 - .01784791958483017511j,
-.37636003878226968717 + .33518347029450104214j,
-23.498232091082701191 - 5.8083591297142074004j,
46.933274488831293047 + 45.643649768827760791j,
-27.875161940145646468 - 102.14733999056451434j,
4.8071120988325088907 - 1.3209793837428723881j])
alpha_0 = np.array([1.8321743782540412751e-14])
s = 7
A = A*t
n = 0*n_0
for j in range(7):
n = n + np.linalg.solve(A - theta[j] * np.identity(np.shape(A)[0]), alpha[j]*n_0)
n = 2*n.real
n = n + alpha_0*n_0
return n
def _rat_apprx_16(A, t, n_0):
""" CRAM of order 16
Parameters
---------
A : numpy array
Burnup matrix
t : float
Time step
n_0: numpy array
Inital composition vector
"""
theta = np.array([ -10.843917078696988026 + 19.277446167181652284j,
-5.2649713434426468895 + 16.220221473167927305j,
5.9481522689511774808 + 3.5874573620183222829j,
3.5091036084149180974 + 8.4361989858843750826j,
6.4161776990994341923 + 1.1941223933701386874j,
1.4193758971856659786 + 10.925363484496722585j,
4.9931747377179963991 + 5.9968817136039422260j,
-1.4139284624888862114 + 13.497725698892745389j])
alpha = np.array([ -.0000005090152186522491565 - .00002422001765285228797j,
.00021151742182466030907 + .0043892969647380673918j,
113.39775178483930527 + 101.9472170421585645j,
15.059585270023467528 - 5.7514052776421819979j,
-64.500878025539646595 - 224.59440762652096056j,
-1.4793007113557999718 + 1.7686588323782937906j,
-62.518392463207918892 - 11.19039109428322848j,
.041023136835410021273 - .15743466173455468191j])
alpha_0 = np.array([2.1248537104952237488e-16])
s = 8
A = A*t
n = 0*n_0
for j in range(8):
n = n + np.linalg.solve(A - theta[j] * np.identity(np.shape(A)[0]), alpha[j]*n_0)
n = 2*n.real
n = n + alpha_0*n_0
return n
[docs]def cram(N, t, n_0, order):
""" This function returns matrix exponential solution n using CRAM14 or CRAM16
Parameters
----------
N : list or array
Array of nuclides under consideration
t : float
Time step
n_0 : list or array
Nuclide concentration vector
order : int
Order of method. Only 14 and 16 are supported.
"""
n_0 = np.array(n_0)
A = _build_matrix(N)
if order == 14:
return _rat_apprx_14(A, t, n_0)
elif order == 16:
return _rat_apprx_16(A, t, n_0)
else:
msg = 'Rational approximation of degree {0} is not supported.'.format(order)
raise ValueError(msg)