DAMASK_EICMD/python/damask/_configmaterial.py

409 lines
14 KiB
Python

from os import path
import numpy as np
import h5py
from . import Config
from . import Rotation
from . import Orientation
from . import util
class ConfigMaterial(Config):
"""Material configuration."""
_defaults = {'material': [],
'homogenization': {},
'phase': {}}
def __init__(self,d=_defaults):
"""Initialize object with default dictionary keys."""
super().__init__(d)
def save(self,fname='material.yaml',**kwargs):
"""
Save to yaml file.
Parameters
----------
fname : file, str, or pathlib.Path, optional
Filename or file for writing. Defaults to 'material.yaml'.
**kwargs
Keyword arguments parsed to yaml.dump.
"""
super().save(fname,**kwargs)
@classmethod
def load(cls,fname='material.yaml'):
"""
Load from yaml file.
Parameters
----------
fname : file, str, or pathlib.Path, optional
Filename or file for writing. Defaults to 'material.yaml'.
"""
return super(ConfigMaterial,cls).load(fname)
@staticmethod
def from_table(table,**kwargs):
"""
Load from an ASCII table.
Parameters
----------
table : damask.Table
Table that contains material information.
**kwargs
Keyword arguments where the key is the name and the value specifies
the label of the data column in the table.
Examples
--------
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> t = damask.Table.load('small.txt')
>>> t
pos pos pos qu qu qu qu phase homog
0 0 0 0 0.19 0.8 0.24 -0.51 Aluminum SX
1 1 0 0 0.8 0.19 0.24 -0.51 Steel SX
1 1 1 0 0.8 0.19 0.24 -0.51 Steel SX
>>> cm.from_table(t,O='qu',phase='phase',homogenization='homog')
material:
- constituents:
- O: [0.19, 0.8, 0.24, -0.51]
v: 1.0
phase: Aluminum
homogenization: SX
- constituents:
- O: [0.8, 0.19, 0.24, -0.51]
v: 1.0
phase: Steel
homogenization: SX
homogenization: {}
phase: {}
"""
kwargs_ = {k:table.get(v) for k,v in kwargs.items()}
_,idx = np.unique(np.hstack(list(kwargs_.values())),return_index=True,axis=0)
idx = np.sort(idx)
kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()}
return ConfigMaterial().material_add(**kwargs_)
@staticmethod
def load_DREAM3D(fname,base_group,data_group,ori_data,phase_id,phase_name):
"""
Load material data from DREAM3D file.
The parts of homogenization and phase need to be added by the user.
Parameters
----------
fname : str
path to the DREAM3D file.
base_group : str
Name of the group (folder) below 'DataContainers',
for example 'SyntheticVolumeDataContainer'.
data_group : str
Name of the group (folder) having relevant data for conversion,
for example 'Grain Data' or 'CellData'.
ori_data : str
Name of the dataset having orientation data (working with Euler Angles in dream3D file),
For example 'EulerAngles'.
phase_id : str
Name of the dataset containing phase IDs for each grain,
for example 'Phases'.
phase_name : list
List with name of the phases.
Examples
--------
for grain based data with single phase
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> cm.load_from_Dream3D('20grains16x16x16.dream3D','SyntheticVolumeDataContainer', 'Grain Data',
... 'EulerAngles','Phases',['Ferrite'])
for point based data with single phase
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> cm.load_from_Dream3D('20grains16x16x16.dream3D','SyntheticVolumeDataContainer', 'CellData',
... 'EulerAngles','Phases',['Ferrite'])
for grain based data with dual phase
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> cm.load_from_Dream3D('20grains16x16x16.dream3D','SyntheticVolumeDataContainer', 'Grain Data',
... 'EulerAngles','Phases',['Ferrite','Martensite'])
for point based data with dual phase
>>> import damask
>>> import damask.ConfigMaterial as cm
>>> cm.load_from_Dream3D('20grains16x16x16.dream3D','SyntheticVolumeDataContainer', 'CellData',
... 'EulerAngles','Phases',['Ferrite','Martensite'])
"""
root_dir = 'DataContainers'
hdf = h5py.File(fname,'r')
orientation_path = path.join(root_dir,base_group,data_group,ori_data)
if hdf[orientation_path].attrs['TupleDimensions'].shape == (3,):
grain_orientations = np.array(hdf[orientation_path]).reshape(-1,3,order='F')
else:
grain_orientations = np.array(hdf[orientation_path])[1:]
grain_quats = Rotation.from_Euler_angles(grain_orientations).as_quaternion()
phase_path = path.join(root_dir,base_group,data_group,phase_id)
if hdf[phase_path].attrs['TupleDimensions'].shape == (3,):
grain_phase = np.array(hdf[phase_path]).reshape(-1,order='F')
else:
grain_phase = np.array(hdf[phase_path])[1:]
grain_phase = grain_phase.reshape(len(grain_phase),)
phase_name_list = [phase_name[i - 1] for i in grain_phase]
return ConfigMaterial().material_add(phase=phase_name_list, O = grain_quats) # noqa
@property
def is_complete(self):
"""Check for completeness."""
ok = True
for top_level in ['homogenization','phase','material']:
ok &= top_level in self
if top_level not in self: print(f'{top_level} entry missing')
if ok:
ok &= len(self['material']) > 0
if len(self['material']) < 1: print('Incomplete material definition')
if ok:
homogenization = set()
phase = set()
for i,v in enumerate(self['material']):
if 'homogenization' in v:
homogenization.add(v['homogenization'])
else:
print(f'No homogenization specified in material {i}')
ok = False
if 'constituents' in v:
for ii,vv in enumerate(v['constituents']):
if 'O' not in vv:
print('No orientation specified in constituent {ii} of material {i}')
ok = False
if 'phase' in vv:
phase.add(vv['phase'])
else:
print(f'No phase specified in constituent {ii} of material {i}')
ok = False
for k,v in self['phase'].items():
if 'lattice' not in v:
print(f'No lattice specified in phase {k}')
ok = False
for k,v in self['homogenization'].items():
if 'N_constituents' not in v:
print(f'No. of constituents not specified in homogenization {k}')
ok = False
if phase - set(self['phase']):
print(f'Phase(s) {phase-set(self["phase"])} missing')
ok = False
if homogenization - set(self['homogenization']):
print(f'Homogenization(s) {homogenization-set(self["homogenization"])} missing')
ok = False
return ok
@property
def is_valid(self):
"""Check for valid content."""
ok = True
if 'phase' in self:
for k,v in self['phase'].items():
if 'lattice' in v:
try:
Orientation(lattice=v['lattice'])
except KeyError:
print(f"Invalid lattice '{v['lattice']}' in phase '{k}'")
ok = False
if 'material' in self:
for i,m in enumerate(self['material']):
if 'constituents' in m:
v = 0.0
for c in m['constituents']:
v += float(c['v'])
if 'O' in c:
try:
Rotation.from_quaternion(c['O'])
except ValueError:
print(f"Invalid orientation '{c['O']}' in material '{i}'")
ok = False
if not np.isclose(v,1.0):
print(f"Total fraction v = {v} ≠ 1 in material '{i}'")
ok = False
return ok
def material_rename_phase(self,mapping,ID=None,constituent=None):
"""
Change phase name in material.
Parameters
----------
mapping: dictionary
Mapping from old name to new name
ID: list of ints, optional
Limit renaming to selected material IDs.
constituent: list of ints, optional
Limit renaming to selected constituents.
Returns
-------
cfg : damask.ConfigMaterial
Updated material configuration.
"""
dup = self.copy()
for i,m in enumerate(dup['material']):
if ID is not None and i not in ID: continue
for c in m['constituents']:
if constituent is not None and c not in constituent: continue
try:
c['phase'] = mapping[c['phase']]
except KeyError:
continue
return dup
def material_rename_homogenization(self,mapping,ID=None):
"""
Change homogenization name in material.
Parameters
----------
mapping: dictionary
Mapping from old name to new name
ID: list of ints, optional
Limit renaming to selected homogenization IDs.
Returns
-------
cfg : damask.ConfigMaterial
Updated material configuration.
"""
dup = self.copy()
for i,m in enumerate(dup['material']):
if ID is not None and i not in ID: continue
try:
m['homogenization'] = mapping[m['homogenization']]
except KeyError:
continue
return dup
def material_add(self,**kwargs):
"""
Add material entries.
Parameters
----------
**kwargs
Key-value pairs.
Returns
-------
cfg : damask.ConfigMaterial
Updated material configuration.
Examples
--------
>>> import numpy as np
>>> import damask
>>> m = damask.ConfigMaterial().material_add(phase = ['Aluminum','Steel'],
... O = damask.Rotation.from_random(2),
... homogenization = 'SX')
>>> m
material:
- constituents:
- O: [0.577764, -0.146299, -0.617669, 0.513010]
v: 1.0
phase: Aluminum
homogenization: SX
- constituents:
- O: [0.184176, 0.340305, 0.737247, 0.553840]
v: 1.0
phase: Steel
homogenization: SX
homogenization: {}
phase: {}
>>> m = damask.ConfigMaterial().material_add(phase = np.array(['Austenite','Martensite']).reshape(1,2),
... O = damask.Rotation.from_random((2,2)),
... v = np.array([0.2,0.8]).reshape(1,2),
... homogenization = ['A','B'])
>>> m
material:
- constituents:
- phase: Austenite
O: [0.659802978293224, 0.6953785848195171, 0.22426295326327111, -0.17554139512785227]
v: 0.2
- phase: Martensite
O: [0.49356745891301596, 0.2841806579193434, -0.7487679215072818, -0.339085707289975]
v: 0.8
homogenization: A
- constituents:
- phase: Austenite
O: [0.26542221365204055, 0.7268854930702071, 0.4474726435701472, -0.44828201137283735]
v: 0.2
- phase: Martensite
O: [0.6545817158479885, -0.08004812803625233, -0.6226561293931374, 0.4212059104577611]
v: 0.8
homogenization: B
homogenization: {}
phase: {}
"""
N,n,shaped = 1,1,{}
for k,v in kwargs.items():
shaped[k] = np.array(v)
s = shaped[k].shape[:-1] if k=='O' else shaped[k].shape
N = max(N,s[0]) if len(s)>0 else N
n = max(n,s[1]) if len(s)>1 else n
mat = [{'constituents':[{} for _ in range(n)]} for _ in range(N)]
if 'v' not in kwargs:
shaped['v'] = np.broadcast_to(1/n,(N,n))
for k,v in shaped.items():
target = (N,n,4) if k=='O' else (N,n)
obj = np.broadcast_to(v.reshape(util.shapeshifter(v.shape,target,mode='right')),target)
for i in range(N):
if k in ['phase','O','v']:
for j in range(n):
mat[i]['constituents'][j][k] = obj[i,j].item() if isinstance(obj[i,j],np.generic) else obj[i,j]
else:
mat[i][k] = obj[i,0].item() if isinstance(obj[i,0],np.generic) else obj[i,0]
dup = self.copy()
dup['material'] = dup['material'] + mat if 'material' in dup else mat
return dup