844 lines
27 KiB
Python
844 lines
27 KiB
Python
from queue import Queue
|
||
import re
|
||
import glob
|
||
|
||
import h5py
|
||
import numpy as np
|
||
|
||
from . import util
|
||
from . import version
|
||
from . import mechanics
|
||
|
||
# ------------------------------------------------------------------
|
||
class DADF5():
|
||
"""
|
||
Read and write to DADF5 files.
|
||
|
||
DADF5 files contain DAMASK results.
|
||
"""
|
||
|
||
# ------------------------------------------------------------------
|
||
def __init__(self,filename):
|
||
"""
|
||
Opens an existing DADF5 file.
|
||
|
||
Parameters
|
||
----------
|
||
filename : str
|
||
name of the DADF5 file to be openend.
|
||
|
||
"""
|
||
with h5py.File(filename,'r') as f:
|
||
|
||
try:
|
||
self.version_major = f.attrs['DADF5_version_major']
|
||
self.version_minor = f.attrs['DADF5_version_minor']
|
||
except KeyError:
|
||
self.version_major = f.attrs['DADF5-major']
|
||
self.version_minor = f.attrs['DADF5-minor']
|
||
|
||
if self.version_major != 0 or not 2 <= self.version_minor <= 4:
|
||
raise TypeError('Unsupported DADF5 version {} '.format(f.attrs['DADF5-version']))
|
||
|
||
self.structured = 'grid' in f['geometry'].attrs.keys()
|
||
|
||
if self.structured:
|
||
self.grid = f['geometry'].attrs['grid']
|
||
self.size = f['geometry'].attrs['size']
|
||
|
||
r=re.compile('inc[0-9]+')
|
||
increments_unsorted = {int(i[3:]):i for i in f.keys() if r.match(i)}
|
||
self.increments = [increments_unsorted[i] for i in sorted(increments_unsorted)]
|
||
self.times = [round(f[i].attrs['time/s'],12) for i in self.increments]
|
||
|
||
self.Nmaterialpoints, self.Nconstituents = np.shape(f['mapping/cellResults/constituent'])
|
||
self.materialpoints = [m.decode() for m in np.unique(f['mapping/cellResults/materialpoint']['Name'])]
|
||
self.constituents = [c.decode() for c in np.unique(f['mapping/cellResults/constituent'] ['Name'])]
|
||
|
||
self.con_physics = []
|
||
for c in self.constituents:
|
||
self.con_physics += f['/'.join([self.increments[0],'constituent',c])].keys()
|
||
self.con_physics = list(set(self.con_physics)) # make unique
|
||
|
||
self.mat_physics = []
|
||
for m in self.materialpoints:
|
||
self.mat_physics += f['/'.join([self.increments[0],'materialpoint',m])].keys()
|
||
self.mat_physics = list(set(self.mat_physics)) # make unique
|
||
|
||
self.visible= {'increments': self.increments,
|
||
'constituents': self.constituents,
|
||
'materialpoints': self.materialpoints,
|
||
'constituent': range(self.Nconstituents), # ToDo: stupid naming
|
||
'con_physics': self.con_physics,
|
||
'mat_physics': self.mat_physics}
|
||
|
||
self.filename = filename
|
||
|
||
|
||
def __manage_visible(self,datasets,what,action):
|
||
"""
|
||
Manages the visibility of the groups.
|
||
|
||
Parameters
|
||
----------
|
||
datasets : list of str or Boolean
|
||
name of datasets as list, supports ? and * wildcards.
|
||
True is equivalent to [*], False is equivalent to []
|
||
what : str
|
||
attribute to change (must be in self.visible)
|
||
action : str
|
||
select from 'set', 'add', and 'del'
|
||
|
||
"""
|
||
# allow True/False and string arguments
|
||
if datasets is True:
|
||
datasets = ['*']
|
||
elif datasets is False:
|
||
datasets = []
|
||
choice = [datasets] if isinstance(datasets,str) else datasets
|
||
|
||
valid = [e for e_ in [glob.fnmatch.filter(getattr(self,what),s) for s in choice] for e in e_]
|
||
existing = set(self.visible[what])
|
||
|
||
if action == 'set':
|
||
self.visible[what] = valid
|
||
elif action == 'add':
|
||
self.visible[what] = list(existing.union(valid))
|
||
elif action == 'del':
|
||
self.visible[what] = list(existing.difference_update(valid))
|
||
|
||
|
||
def __time_to_inc(self,start,end):
|
||
selected = []
|
||
for i,time in enumerate(self.times):
|
||
if start <= time <= end:
|
||
selected.append(self.increments[i])
|
||
return selected
|
||
|
||
|
||
def set_by_time(self,start,end):
|
||
"""
|
||
Set active increments based on start and end time.
|
||
|
||
Parameters
|
||
----------
|
||
start : float
|
||
start time (included)
|
||
end : float
|
||
end time (included)
|
||
|
||
"""
|
||
self.__manage_visible(self.__time_to_inc(start,end),'increments','set')
|
||
|
||
|
||
def add_by_time(self,start,end):
|
||
"""
|
||
Add to active increments based on start and end time.
|
||
|
||
Parameters
|
||
----------
|
||
start : float
|
||
start time (included)
|
||
end : float
|
||
end time (included)
|
||
|
||
"""
|
||
self.__manage_visible(self.__time_to_inc(start,end),'increments','add')
|
||
|
||
|
||
def del_by_time(self,start,end):
|
||
"""
|
||
Delete from active increments based on start and end time.
|
||
|
||
Parameters
|
||
----------
|
||
start : float
|
||
start time (included)
|
||
end : float
|
||
end time (included)
|
||
|
||
"""
|
||
self.__manage_visible(self.__time_to_inc(start,end),'increments','del')
|
||
|
||
|
||
def set_by_increment(self,start,end):
|
||
"""
|
||
Set active time increments based on start and end increment.
|
||
|
||
Parameters
|
||
----------
|
||
start : int
|
||
start increment (included)
|
||
end : int
|
||
end increment (included)
|
||
|
||
"""
|
||
if self.version_minor >= 4:
|
||
self.__manage_visible([ 'inc{}'.format(i) for i in range(start,end+1)],'increments','set')
|
||
else:
|
||
self.__manage_visible(['inc{:05d}'.format(i) for i in range(start,end+1)],'increments','set')
|
||
|
||
|
||
def add_by_increment(self,start,end):
|
||
"""
|
||
Add to active time increments based on start and end increment.
|
||
|
||
Parameters
|
||
----------
|
||
start : int
|
||
start increment (included)
|
||
end : int
|
||
end increment (included)
|
||
|
||
"""
|
||
if self.version_minor >= 4:
|
||
self.__manage_visible([ 'inc{}'.format(i) for i in range(start,end+1)],'increments','add')
|
||
else:
|
||
self.__manage_visible(['inc{:05d}'.format(i) for i in range(start,end+1)],'increments','add')
|
||
|
||
|
||
def del_by_increment(self,start,end):
|
||
"""
|
||
Delet from active time increments based on start and end increment.
|
||
|
||
Parameters
|
||
----------
|
||
start : int
|
||
start increment (included)
|
||
end : int
|
||
end increment (included)
|
||
|
||
"""
|
||
if self.version_minor >= 4:
|
||
self.__manage_visible([ 'inc{}'.format(i) for i in range(start,end+1)],'increments','del')
|
||
else:
|
||
self.__manage_visible(['inc{:05d}'.format(i) for i in range(start,end+1)],'increments','del')
|
||
|
||
|
||
def iter_visible(self,what):
|
||
"""
|
||
Iterate over visible items by setting each one visible.
|
||
|
||
Parameters
|
||
----------
|
||
what : str
|
||
attribute to change (must be in self.visible)
|
||
|
||
"""
|
||
datasets = self.visible[what]
|
||
last_datasets = datasets.copy()
|
||
for dataset in datasets:
|
||
if last_datasets != self.visible[what]:
|
||
self.__manage_visible(datasets,what,'set')
|
||
raise Exception
|
||
self.__manage_visible(dataset,what,'set')
|
||
last_datasets = self.visible[what]
|
||
yield dataset
|
||
self.__manage_visible(datasets,what,'set')
|
||
|
||
|
||
def set_visible(self,what,datasets):
|
||
"""
|
||
Set active groups.
|
||
|
||
Parameters
|
||
----------
|
||
datasets : list of str or Boolean
|
||
name of datasets as list, supports ? and * wildcards.
|
||
True is equivalent to [*], False is equivalent to []
|
||
what : str
|
||
attribute to change (must be in self.visible)
|
||
|
||
"""
|
||
self.__manage_visible(datasets,what,'set')
|
||
|
||
|
||
def add_visible(self,what,datasets):
|
||
"""
|
||
Add to active groups.
|
||
|
||
Parameters
|
||
----------
|
||
datasets : list of str or Boolean
|
||
name of datasets as list, supports ? and * wildcards.
|
||
True is equivalent to [*], False is equivalent to []
|
||
what : str
|
||
attribute to change (must be in self.visible)
|
||
|
||
"""
|
||
self.__manage_visible(datasets,what,'add')
|
||
|
||
|
||
def del_visible(self,what,datasets):
|
||
"""
|
||
Delete from active groupse.
|
||
|
||
Parameters
|
||
----------
|
||
datasets : list of str or Boolean
|
||
name of datasets as list, supports ? and * wildcards.
|
||
True is equivalent to [*], False is equivalent to []
|
||
what : str
|
||
attribute to change (must be in self.visible)
|
||
|
||
"""
|
||
self.__manage_visible(datasets,what,'del')
|
||
|
||
|
||
def groups_with_datasets(self,datasets):
|
||
"""
|
||
Get groups that contain all requested datasets.
|
||
|
||
Only groups within inc?????/constituent/*_*/* inc?????/materialpoint/*_*/*
|
||
are considered as they contain the data.
|
||
Single strings will be treated as list with one entry.
|
||
|
||
Wild card matching is allowed, but the number of arguments need to fit.
|
||
|
||
Parameters
|
||
----------
|
||
datasets : iterable or str or boolean
|
||
|
||
Examples
|
||
--------
|
||
datasets = False matches no group
|
||
datasets = True matches all groups
|
||
datasets = ['F','P'] matches a group with ['F','P','sigma']
|
||
datasets = ['*','P'] matches a group with ['F','P']
|
||
datasets = ['*'] does not match a group with ['F','P','sigma']
|
||
datasets = ['*','*'] does not match a group with ['F','P','sigma']
|
||
datasets = ['*','*','*'] matches a group with ['F','P','sigma']
|
||
|
||
"""
|
||
if datasets is False: return []
|
||
sets = [datasets] if isinstance(datasets,str) else datasets
|
||
|
||
groups = []
|
||
|
||
with h5py.File(self.filename,'r') as f:
|
||
for i in self.iter_visible('increments'):
|
||
for o,p in zip(['constituents','materialpoints'],['con_physics','mat_physics']):
|
||
for oo in self.iter_visible(o):
|
||
for pp in self.iter_visible(p):
|
||
group = '/'.join([i,o[:-1],oo,pp]) # o[:-1]: plural/singular issue
|
||
if sets is True:
|
||
groups.append(group)
|
||
else:
|
||
match = [e for e_ in [glob.fnmatch.filter(f[group].keys(),s) for s in sets] for e in e_]
|
||
if len(set(match)) == len(sets) : groups.append(group)
|
||
return groups
|
||
|
||
|
||
def list_data(self):
|
||
"""Return information on all active datasets in the file."""
|
||
message = ''
|
||
with h5py.File(self.filename,'r') as f:
|
||
for s,i in enumerate(self.iter_visible('increments')):
|
||
message+='\n{} ({}s)\n'.format(i,self.times[s])
|
||
for o,p in zip(['constituents','materialpoints'],['con_physics','mat_physics']):
|
||
for oo in self.iter_visible(o):
|
||
message+=' {}\n'.format(oo)
|
||
for pp in self.iter_visible(p):
|
||
message+=' {}\n'.format(pp)
|
||
group = '/'.join([i,o[:-1],oo,pp]) # o[:-1]: plural/singular issue
|
||
for d in f[group].keys():
|
||
try:
|
||
dataset = f['/'.join([group,d])]
|
||
message+=' {} / ({}): {}\n'.format(d,dataset.attrs['Unit'].decode(),dataset.attrs['Description'].decode())
|
||
except KeyError:
|
||
pass
|
||
return message
|
||
|
||
|
||
def get_dataset_location(self,label):
|
||
"""Return the location of all active datasets with given label."""
|
||
path = []
|
||
with h5py.File(self.filename,'r') as f:
|
||
for i in self.iter_visible('increments'):
|
||
k = '/'.join([i,'geometry',label])
|
||
try:
|
||
f[k]
|
||
path.append(k)
|
||
except KeyError as e:
|
||
print('unable to locate geometry dataset: {}'.format(str(e)))
|
||
for o,p in zip(['constituents','materialpoints'],['con_physics','mat_physics']):
|
||
for oo in self.iter_visible(o):
|
||
for pp in self.iter_visible(p):
|
||
k = '/'.join([i,o[:-1],oo,pp,label])
|
||
try:
|
||
f[k]
|
||
path.append(k)
|
||
except KeyError as e:
|
||
print('unable to locate {} dataset: {}'.format(o,str(e)))
|
||
return path
|
||
|
||
|
||
def get_constituent_ID(self,c=0):
|
||
"""Pointwise constituent ID."""
|
||
with h5py.File(self.filename,'r') as f:
|
||
names = f['/mapping/cellResults/constituent']['Name'][:,c].astype('str')
|
||
return np.array([int(n.split('_')[0]) for n in names.tolist()],dtype=np.int32)
|
||
|
||
|
||
def get_crystal_structure(self): # ToDo: extension to multi constituents/phase
|
||
"""Info about the crystal structure."""
|
||
with h5py.File(self.filename,'r') as f:
|
||
return f[self.get_dataset_location('orientation')[0]].attrs['Lattice'].astype('str') # np.bytes_ to string
|
||
|
||
|
||
def read_dataset(self,path,c=0,plain=False):
|
||
"""
|
||
Dataset for all points/cells.
|
||
|
||
If more than one path is given, the dataset is composed of the individual contributions.
|
||
"""
|
||
with h5py.File(self.filename,'r') as f:
|
||
shape = (self.Nmaterialpoints,) + np.shape(f[path[0]])[1:]
|
||
if len(shape) == 1: shape = shape +(1,)
|
||
dataset = np.full(shape,np.nan,dtype=np.dtype(f[path[0]]))
|
||
for pa in path:
|
||
label = pa.split('/')[2]
|
||
|
||
if (pa.split('/')[1] == 'geometry'):
|
||
dataset = np.array(f[pa])
|
||
continue
|
||
|
||
p = np.where(f['mapping/cellResults/constituent'][:,c]['Name'] == str.encode(label))[0]
|
||
if len(p)>0:
|
||
u = (f['mapping/cellResults/constituent']['Position'][p,c])
|
||
a = np.array(f[pa])
|
||
if len(a.shape) == 1:
|
||
a=a.reshape([a.shape[0],1])
|
||
dataset[p,:] = a[u,:]
|
||
|
||
p = np.where(f['mapping/cellResults/materialpoint']['Name'] == str.encode(label))[0]
|
||
if len(p)>0:
|
||
u = (f['mapping/cellResults/materialpoint']['Position'][p.tolist()])
|
||
a = np.array(f[pa])
|
||
if len(a.shape) == 1:
|
||
a=a.reshape([a.shape[0],1])
|
||
dataset[p,:] = a[u,:]
|
||
|
||
if plain and dataset.dtype.names is not None:
|
||
return dataset.view(('float64',len(dataset.dtype.names)))
|
||
else:
|
||
return dataset
|
||
|
||
|
||
def cell_coordinates(self):
|
||
"""Return initial coordinates of the cell centers."""
|
||
if self.structured:
|
||
delta = self.size/self.grid*0.5
|
||
z, y, x = np.meshgrid(np.linspace(delta[2],self.size[2]-delta[2],self.grid[2]),
|
||
np.linspace(delta[1],self.size[1]-delta[1],self.grid[1]),
|
||
np.linspace(delta[0],self.size[0]-delta[0],self.grid[0]),
|
||
)
|
||
return np.concatenate((x[:,:,:,None],y[:,:,:,None],y[:,:,:,None]),axis = 3).reshape([np.product(self.grid),3])
|
||
else:
|
||
with h5py.File(self.filename,'r') as f:
|
||
return f['geometry/x_c'][()]
|
||
|
||
|
||
def add_Cauchy(self,P='P',F='F'):
|
||
"""
|
||
Add Cauchy stress calculated from 1. Piola-Kirchhoff stress and deformation gradient.
|
||
|
||
Parameters
|
||
----------
|
||
P : str, optional
|
||
Label of the dataset containing the 1. Piola-Kirchhoff stress. Default value is ‘P’.
|
||
F : str, optional
|
||
Label of the dataset containing the deformation gradient. Default value is ‘F’.
|
||
|
||
"""
|
||
def __add_Cauchy(F,P):
|
||
|
||
return {
|
||
'data': mechanics.Cauchy(F['data'],P['data']),
|
||
'label': 'sigma',
|
||
'meta': {
|
||
'Unit': P['meta']['Unit'],
|
||
'Description': 'Cauchy stress calculated from {} ({}) '.format(P['label'],P['meta']['Description'])+\
|
||
'and deformation gradient {} ({})'.format(F['label'],F['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_Cauchy v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':F,'arg':'F'},
|
||
{'label':P,'arg':'P'} ]
|
||
|
||
self.__add_generic_pointwise(__add_Cauchy,requested)
|
||
|
||
|
||
def add_Mises(self,x):
|
||
"""
|
||
Add the equivalent Mises stress or strain of a symmetric tensor.
|
||
|
||
Parameters
|
||
----------
|
||
x : str
|
||
Label of the dataset containing a symmetric stress or strain tensor.
|
||
|
||
"""
|
||
def __add_Mises(x):
|
||
|
||
t = 'strain' if x['meta']['Unit'] == '1' else \
|
||
'stress'
|
||
return {
|
||
'data': mechanics.Mises_strain(x['data']) if t=='strain' else mechanics.Mises_stress(x['data']),
|
||
'label': '{}_vM'.format(x['label']),
|
||
'meta': {
|
||
'Unit': x['meta']['Unit'],
|
||
'Description': 'Mises equivalent {} of {} ({})'.format(t,x['label'],x['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_Mises v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':x,'arg':'x'}]
|
||
|
||
self.__add_generic_pointwise(__add_Mises,requested)
|
||
|
||
|
||
def add_norm(self,x,ord=None):
|
||
"""
|
||
Add the norm of vector or tensor.
|
||
|
||
Parameters
|
||
----------
|
||
x : str
|
||
Label of the dataset containing a vector or tensor.
|
||
ord : {non-zero int, inf, -inf, ‘fro’, ‘nuc’}, optional
|
||
Order of the norm. inf means numpy’s inf object. For details refer to numpy.linalg.norm.
|
||
|
||
"""
|
||
def __add_norm(x,ord):
|
||
|
||
o = ord
|
||
if len(x['data'].shape) == 2:
|
||
axis = 1
|
||
t = 'vector'
|
||
if o is None: o = 2
|
||
elif len(x['data'].shape) == 3:
|
||
axis = (1,2)
|
||
t = 'tensor'
|
||
if o is None: o = 'fro'
|
||
else:
|
||
raise ValueError
|
||
|
||
return {
|
||
'data': np.linalg.norm(x['data'],ord=o,axis=axis,keepdims=True),
|
||
'label': '|{}|_{}'.format(x['label'],o),
|
||
'meta': {
|
||
'Unit': x['meta']['Unit'],
|
||
'Description': '{}-Norm of {} {} ({})'.format(ord,t,x['label'],x['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_norm v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':x,'arg':'x'}]
|
||
|
||
self.__add_generic_pointwise(__add_norm,requested,{'ord':ord})
|
||
|
||
|
||
def add_absolute(self,x):
|
||
"""
|
||
Add absolute value.
|
||
|
||
Parameters
|
||
----------
|
||
x : str
|
||
Label of the dataset containing a scalar, vector, or tensor.
|
||
|
||
"""
|
||
def __add_absolute(x):
|
||
|
||
return {
|
||
'data': np.abs(x['data']),
|
||
'label': '|{}|'.format(x['label']),
|
||
'meta': {
|
||
'Unit': x['meta']['Unit'],
|
||
'Description': 'Absolute value of {} ({})'.format(x['label'],x['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_abs v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':x,'arg':'x'}]
|
||
|
||
self.__add_generic_pointwise(__add_absolute,requested)
|
||
|
||
|
||
def add_determinant(self,x):
|
||
"""
|
||
Add the determinant of a tensor.
|
||
|
||
Parameters
|
||
----------
|
||
x : str
|
||
Label of the dataset containing a tensor.
|
||
|
||
"""
|
||
def __add_determinant(x):
|
||
|
||
return {
|
||
'data': np.linalg.det(x['data']),
|
||
'label': 'det({})'.format(x['label']),
|
||
'meta': {
|
||
'Unit': x['meta']['Unit'],
|
||
'Description': 'Determinant of tensor {} ({})'.format(x['label'],x['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_determinant v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':x,'arg':'x'}]
|
||
|
||
self.__add_generic_pointwise(__add_determinant,requested)
|
||
|
||
|
||
def add_spherical(self,x):
|
||
"""
|
||
Add the spherical (hydrostatic) part of a tensor.
|
||
|
||
Parameters
|
||
----------
|
||
x : str
|
||
Label of the dataset containing a tensor.
|
||
|
||
"""
|
||
def __add_spherical(x):
|
||
|
||
if not np.all(np.array(x['data'].shape[1:]) == np.array([3,3])):
|
||
raise ValueError
|
||
|
||
return {
|
||
'data': mechanics.spherical_part(x['data']),
|
||
'label': 'p_{}'.format(x['label']),
|
||
'meta': {
|
||
'Unit': x['meta']['Unit'],
|
||
'Description': 'Spherical component of tensor {} ({})'.format(x['label'],x['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_spherical v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':x,'arg':'x'}]
|
||
|
||
self.__add_generic_pointwise(__add_spherical,requested)
|
||
|
||
|
||
def add_deviator(self,x):
|
||
"""
|
||
Add the deviatoric part of a tensor.
|
||
|
||
Parameters
|
||
----------
|
||
x : str
|
||
Label of the dataset containing a tensor.
|
||
|
||
"""
|
||
def __add_deviator(x):
|
||
|
||
if not np.all(np.array(x['data'].shape[1:]) == np.array([3,3])):
|
||
raise ValueError
|
||
|
||
return {
|
||
'data': mechanics.deviatoric_part(x['data']),
|
||
'label': 's_{}'.format(x['label']),
|
||
'meta': {
|
||
'Unit': x['meta']['Unit'],
|
||
'Description': 'Deviator of tensor {} ({})'.format(x['label'],x['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_deviator v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':x,'arg':'x'}]
|
||
|
||
self.__add_generic_pointwise(__add_deviator,requested)
|
||
|
||
|
||
def add_calculation(self,formula,label,unit='n/a',description=None,vectorized=True):
|
||
"""
|
||
Add result of a general formula.
|
||
|
||
Parameters
|
||
----------
|
||
formula : str
|
||
Formula, refer to datasets by ‘#Label#‘.
|
||
label : str
|
||
Label of the dataset containing the result of the calculation.
|
||
unit : str, optional
|
||
Physical unit of the result.
|
||
description : str, optional
|
||
Human readable description of the result.
|
||
vectorized : bool, optional
|
||
Indicate whether the formula is written in vectorized form. Default is ‘True’.
|
||
|
||
"""
|
||
if vectorized is not True:
|
||
raise NotImplementedError
|
||
|
||
def __add_calculation(**kwargs):
|
||
|
||
formula = kwargs['formula']
|
||
for d in re.findall(r'#(.*?)#',formula):
|
||
formula = formula.replace('#{}#'.format(d),"kwargs['{}']['data']".format(d))
|
||
|
||
return {
|
||
'data': eval(formula),
|
||
'label': kwargs['label'],
|
||
'meta': {
|
||
'Unit': kwargs['unit'],
|
||
'Description': '{} (formula: {})'.format(kwargs['description'],kwargs['formula']),
|
||
'Creator': 'dadf5.py:add_calculation v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':d,'arg':d} for d in set(re.findall(r'#(.*?)#',formula))] # datasets used in the formula
|
||
pass_through = {'formula':formula,'label':label,'unit':unit,'description':description}
|
||
|
||
self.__add_generic_pointwise(__add_calculation,requested,pass_through)
|
||
|
||
|
||
def add_strain_tensor(self,F='F',t='U',m=0):
|
||
"""
|
||
Add strain tensor calculated from a deformation gradient.
|
||
|
||
For details refer to damask.mechanics.strain_tensor
|
||
|
||
Parameters
|
||
----------
|
||
F : str, optional
|
||
Label of the dataset containing the deformation gradient. Default value is ‘F’.
|
||
t : {‘V’, ‘U’}, optional
|
||
Type of the polar decomposition, ‘V’ for right stretch tensor and ‘U’ for left stretch tensor.
|
||
Defaults value is ‘U’.
|
||
m : float, optional
|
||
Order of the strain calculation. Default value is ‘0.0’.
|
||
|
||
"""
|
||
def __add_strain_tensor(F,t,m):
|
||
|
||
return {
|
||
'data': mechanics.strain_tensor(F['data'],t,m),
|
||
'label': 'epsilon_{}^{}({})'.format(t,m,F['label']),
|
||
'meta': {
|
||
'Unit': F['meta']['Unit'],
|
||
'Description': 'Strain tensor of {} ({})'.format(F['label'],F['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_strain_tensor v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':F,'arg':'F'}]
|
||
|
||
self.__add_generic_pointwise(__add_strain_tensor,requested,{'t':t,'m':m})
|
||
|
||
|
||
def add_principal_components(self,x):
|
||
"""
|
||
Add principal components of symmetric tensor.
|
||
|
||
The principal components are sorted in descending order, each repeated according to its multiplicity.
|
||
|
||
Parameters
|
||
----------
|
||
x : str
|
||
Label of the dataset containing a symmetric tensor.
|
||
|
||
"""
|
||
def __add_principal_components(x):
|
||
|
||
return {
|
||
'data': mechanics.principal_components(x['data']),
|
||
'label': 'lambda_{}'.format(x['label']),
|
||
'meta': {
|
||
'Unit': x['meta']['Unit'],
|
||
'Description': 'Pricipal components of {} ({})'.format(x['label'],x['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_principal_components v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':x,'arg':'x'}]
|
||
|
||
self.__add_generic_pointwise(__add_principal_components,requested)
|
||
|
||
|
||
def add_maximum_shear(self,x):
|
||
"""
|
||
Add maximum shear components of symmetric tensor.
|
||
|
||
Parameters
|
||
----------
|
||
x : str
|
||
Label of the dataset containing a symmetric tensor.
|
||
|
||
"""
|
||
def __add_maximum_shear(x):
|
||
|
||
return {
|
||
'data': mechanics.maximum_shear(x['data']),
|
||
'label': 'max_shear({})'.format(x['label']),
|
||
'meta': {
|
||
'Unit': x['meta']['Unit'],
|
||
'Description': 'Maximum shear component of of {} ({})'.format(x['label'],x['meta']['Description']),
|
||
'Creator': 'dadf5.py:add_maximum_shear v{}'.format(version)
|
||
}
|
||
}
|
||
|
||
requested = [{'label':x,'arg':'x'}]
|
||
|
||
self.__add_generic_pointwise(__add_maximum_shear,requested)
|
||
|
||
|
||
def __add_generic_pointwise(self,func,datasets_requested,extra_args={}):
|
||
"""
|
||
General function to add pointwise data.
|
||
|
||
Parameters
|
||
----------
|
||
func : function
|
||
Function that calculates a new dataset from one or more datasets per HDF5 group.
|
||
datasets_requested : list of dictionaries
|
||
Details of the datasets to be used: label (in HDF5 file) and arg (argument to which the data is parsed in func).
|
||
extra_args : dictionary, optional
|
||
Any extra arguments parsed to func.
|
||
|
||
"""
|
||
def job(args):
|
||
"""Call function with input data + extra arguments, returns results + group."""
|
||
args['results'].put({**args['func'](**args['in']),'group':args['group']})
|
||
|
||
|
||
N_threads = 1 # ToDo: should be a parameter
|
||
|
||
results = Queue(N_threads)
|
||
pool = util.ThreadPool(N_threads)
|
||
N_added = N_threads + 1
|
||
|
||
todo = []
|
||
# ToDo: It would be more memory efficient to read only from file when required, i.e. do to it in pool.add_task
|
||
for group in self.groups_with_datasets([d['label'] for d in datasets_requested]):
|
||
with h5py.File(self.filename,'r') as f:
|
||
datasets_in = {}
|
||
for d in datasets_requested:
|
||
loc = f[group+'/'+d['label']]
|
||
data = loc[()]
|
||
meta = {k:loc.attrs[k].decode() for k in loc.attrs.keys()}
|
||
datasets_in[d['arg']] = {'data': data, 'meta' : meta, 'label' : d['label']}
|
||
|
||
todo.append({'in':{**datasets_in,**extra_args},'func':func,'group':group,'results':results})
|
||
|
||
pool.map(job, todo[:N_added]) # initialize
|
||
|
||
N_not_calculated = len(todo)
|
||
while N_not_calculated > 0:
|
||
result = results.get()
|
||
with h5py.File(self.filename,'a') as f: # write to file
|
||
dataset_out = f[result['group']].create_dataset(result['label'],data=result['data'])
|
||
for k in result['meta'].keys():
|
||
dataset_out.attrs[k] = result['meta'][k].encode()
|
||
N_not_calculated-=1
|
||
|
||
if N_added < len(todo): # add more jobs
|
||
pool.add_task(job,todo[N_added])
|
||
N_added +=1
|
||
|
||
pool.wait_completion()
|