DAMASK_EICMD/python/damask/_configmaterial.py

512 lines
18 KiB
Python
Raw Permalink Normal View History

import numpy as np
import h5py
2022-02-01 13:00:00 +05:30
from typing import Sequence, Dict, Any, Collection
2022-02-01 13:00:00 +05:30
from ._typehints import FileHandle
from . import Config
from . import Rotation
from . import Orientation
from . import util
2022-02-01 13:00:00 +05:30
from . import Table
class ConfigMaterial(Config):
"""
Material configuration.
Manipulate material configurations for storage in YAML format.
A complete material configuration file has the entries 'material',
'phase', and 'homogenization'. For use in DAMASK, it needs to be
stored as 'material.yaml'.
"""
2022-02-01 13:00:00 +05:30
def __init__(self,
d: Dict[str, Any] = None,
**kwargs):
"""
New material configuration.
Parameters
----------
d : dictionary or YAML string, optional
Initial content. Defaults to None, in which case empty entries for
any missing material, homogenization, and phase entry are created.
kwargs : key=value pairs, optional
Initial content specified as pairs of key=value.
"""
2022-02-01 13:00:00 +05:30
default: Collection
if d is None:
2022-02-01 13:00:00 +05:30
for section, default in {'material':[],'homogenization':{},'phase':{}}.items():
if section not in kwargs: kwargs.update({section:default})
super().__init__(d,**kwargs)
2022-02-01 13:00:00 +05:30
def save(self,
fname: FileHandle = 'material.yaml',
**kwargs):
2020-09-30 12:19:55 +05:30
"""
Save to yaml file.
Parameters
----------
fname : file, str, or pathlib.Path, optional
Filename or file for writing. Defaults to 'material.yaml'.
2020-10-09 17:49:19 +05:30
**kwargs
Keyword arguments parsed to yaml.dump.
2020-09-30 12:19:55 +05:30
"""
super().save(fname,**kwargs)
2020-09-30 12:19:55 +05:30
2020-10-09 11:15:20 +05:30
@classmethod
2022-02-01 13:00:00 +05:30
def load(cls,
fname: FileHandle = 'material.yaml') -> 'ConfigMaterial':
"""
Load from yaml file.
Parameters
----------
fname : file, str, or pathlib.Path, optional
2021-04-25 11:17:00 +05:30
Filename or file to read from. Defaults to 'material.yaml'.
Returns
-------
loaded : damask.ConfigMaterial
Material configuration from file.
"""
return super(ConfigMaterial,cls).load(fname)
@staticmethod
2022-02-01 13:00:00 +05:30
def load_DREAM3D(fname: str,
grain_data: str = None,
cell_data: str = None,
cell_ensemble_data: str = 'CellEnsembleData',
phases: str = 'Phases',
Euler_angles: str = 'EulerAngles',
phase_names: str = 'PhaseName',
base_group: str = None) -> 'ConfigMaterial':
2021-03-12 13:13:57 +05:30
"""
2021-03-20 18:07:06 +05:30
Load DREAM.3D (HDF5) file.
2021-01-11 19:35:48 +05:30
2021-03-20 18:07:06 +05:30
Data in DREAM.3D files can be stored per cell ('CellData')
and/or per grain ('Grain Data'). Per default, cell-wise data
is assumed.
damask.Grid.load_DREAM3D allows to get the corresponding geometry
for the grid solver.
Parameters
----------
fname : str
Filename of the DREAM.3D (HDF5) file.
2021-03-20 18:07:06 +05:30
grain_data : str
Name of the group (folder) containing grain-wise data. Defaults
to None, in which case cell-wise data is used.
cell_data : str
2021-03-23 18:58:56 +05:30
Name of the group (folder) containing cell-wise data. Defaults to
None in wich case it is automatically detected.
2021-03-20 18:07:06 +05:30
cell_ensemble_data : str
Name of the group (folder) containing data of cell ensembles. This
group is used to inquire the name of the phases. Phases will get
numeric IDs if this group is not found. Defaults to 'CellEnsembleData'.
2021-03-20 18:07:06 +05:30
phases : str
Name of the dataset containing the phase ID (cell-wise or grain-wise).
Defaults to 'Phases'.
Euler_angles : str
Name of the dataset containing the crystallographic orientation as
Euler angles in radians (cell-wise or grain-wise). Defaults to 'EulerAngles'.
phase_names : str
Name of the dataset containing the phase names. Phases will get
numeric IDs if this dataset is not found. Defaults to 'PhaseName'.
base_group : str
2021-03-20 18:07:06 +05:30
Path to the group (folder) that contains geometry (_SIMPL_GEOMETRY),
and grain- or cell-wise data. Defaults to None, in which case
it is set as the path that contains _SIMPL_GEOMETRY/SPACING.
2021-06-17 21:56:37 +05:30
Notes
-----
Homogenization and phase entries are emtpy and need to be defined separately.
2021-04-25 11:17:00 +05:30
Returns
-------
loaded : damask.ConfigMaterial
Material configuration from file.
"""
2021-03-20 04:19:41 +05:30
b = util.DREAM3D_base_group(fname) if base_group is None else base_group
2021-03-23 18:58:56 +05:30
c = util.DREAM3D_cell_data_group(fname) if cell_data is None else cell_data
f = h5py.File(fname,'r')
2021-01-11 19:32:15 +05:30
if grain_data is None:
phase = f['/'.join([b,c,phases])][()].flatten()
O = Rotation.from_Euler_angles(f['/'.join([b,c,Euler_angles])]).as_quaternion().reshape(-1,4) # noqa
_,idx = np.unique(np.hstack([O,phase.reshape(-1,1)]),return_index=True,axis=0)
idx = np.sort(idx)
2021-01-12 17:31:11 +05:30
else:
phase = f['/'.join([b,grain_data,phases])][()]
O = Rotation.from_Euler_angles(f['/'.join([b,grain_data,Euler_angles])]).as_quaternion() # noqa
idx = np.arange(phase.size)
2021-01-12 17:31:11 +05:30
if cell_ensemble_data is not None and phase_names is not None:
try:
names = np.array([s.decode() for s in f['/'.join([b,cell_ensemble_data,phase_names])]])
phase = names[phase]
except KeyError:
pass
2021-01-11 19:32:15 +05:30
2021-03-23 16:34:40 +05:30
base_config = ConfigMaterial({'phase':{k if isinstance(k,int) else str(k):'t.b.d.' for k in np.unique(phase)},
'homogenization':{'direct':{'N_constituents':1}}})
constituent = {k:np.atleast_1d(v[idx].squeeze()) for k,v in zip(['O','phase'],[O,phase])}
return base_config.material_add(**constituent,homogenization='direct')
2021-03-12 13:13:57 +05:30
@staticmethod
2022-02-01 13:00:00 +05:30
def from_table(table: Table,
**kwargs) -> 'ConfigMaterial':
"""
Generate from an ASCII table.
Parameters
----------
table : damask.Table
Table that contains material information.
**kwargs
Keyword arguments where the key is the property name and
the value specifies either the label of the data column in the table
or a constant value.
2021-04-25 11:17:00 +05:30
Returns
-------
new : damask.ConfigMaterial
Material configuration from values in 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: {}
>>> cm.from_table(t,O='qu',phase='phase',homogenization='single_crystal')
material:
- constituents:
- O: [0.19, 0.8, 0.24, -0.51]
v: 1.0
phase: Aluminum
homogenization: single_crystal
- constituents:
- O: [0.8, 0.19, 0.24, -0.51]
v: 1.0
phase: Steel
homogenization: single_crystal
homogenization: {}
phase: {}
"""
kwargs_ = {k:table.get(v) if v in table.labels else np.atleast_2d([v]*len(table)).T 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_)
@property
2022-02-01 13:00:00 +05:30
def is_complete(self) -> bool:
"""
Check for completeness.
Only the general file layout is considered.
This check does not consider whether parameters for
a particular phase/homogenization model are missing.
Returns
-------
complete : bool
Whether the material.yaml definition is complete.
"""
ok = True
2020-10-02 21:21:33 +05:30
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:
2020-10-02 21:21:33 +05:30
ok &= len(self['material']) > 0
if len(self['material']) < 1: print('Incomplete material definition')
if ok:
homogenization = set()
phase = set()
2020-10-02 21:21:33 +05:30
for i,v in enumerate(self['material']):
if 'homogenization' in v:
homogenization.add(v['homogenization'])
else:
2020-10-02 21:21:33 +05:30
print(f'No homogenization specified in material {i}')
ok = False
if 'constituents' in v:
for ii,vv in enumerate(v['constituents']):
2020-10-02 21:21:33 +05:30
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:
2020-10-02 21:21:33 +05:30
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
2022-02-01 13:00:00 +05:30
def is_valid(self) -> bool:
"""
Check for valid content.
Only the generic file content is considered.
This check does not consider whether parameters for a
particular phase/homogenization mode are out of bounds.
Returns
-------
valid : bool
Whether the material.yaml definition is valid.
"""
ok = True
if 'phase' in self:
for k,v in self['phase'].items():
if 'lattice' in v:
try:
Orientation(lattice=v['lattice'])
except KeyError:
2021-02-24 05:24:55 +05:30
print(f"Invalid lattice '{v['lattice']}' in phase '{k}'")
ok = False
2020-10-02 21:21:33 +05:30
if 'material' in self:
for i,m in enumerate(self['material']):
if 'constituents' in m:
v = 0.0
for c in m['constituents']:
2021-02-24 05:24:55 +05:30
v += float(c['v'])
2020-10-02 21:21:33 +05:30
if 'O' in c:
try:
2020-10-02 21:21:33 +05:30
Rotation.from_quaternion(c['O'])
except ValueError:
2021-02-24 05:24:55 +05:30
print(f"Invalid orientation '{c['O']}' in material '{i}'")
ok = False
if not np.isclose(v,1.0):
2021-02-24 05:24:55 +05:30
print(f"Total fraction v = {v} ≠ 1 in material '{i}'")
ok = False
return ok
2022-02-01 13:00:00 +05:30
def material_rename_phase(self,
mapping: Dict[str, str],
ID: Sequence[int] = None,
constituent: Sequence[int] = None) -> 'ConfigMaterial':
"""
2020-10-02 21:21:33 +05:30
Change phase name in material.
Parameters
----------
mapping: dictionary
Mapping from old name to new name
ID: list of ints, optional
2020-10-02 21:21:33 +05:30
Limit renaming to selected material IDs.
constituent: list of ints, optional
Limit renaming to selected constituents.
2021-02-24 05:24:55 +05:30
Returns
-------
updated : damask.ConfigMaterial
2021-02-24 05:24:55 +05:30
Updated material configuration.
"""
2021-01-03 16:33:40 +05:30
dup = self.copy()
2020-10-02 21:21:33 +05:30
for i,m in enumerate(dup['material']):
2021-02-19 21:04:28 +05:30
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
2022-02-01 13:00:00 +05:30
def material_rename_homogenization(self,
mapping: Dict[str, str],
ID: Sequence[int] = None) -> 'ConfigMaterial':
"""
2020-10-02 21:21:33 +05:30
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.
2021-02-24 05:24:55 +05:30
Returns
-------
updated : damask.ConfigMaterial
2021-02-24 05:24:55 +05:30
Updated material configuration.
"""
2021-01-03 16:33:40 +05:30
dup = self.copy()
2020-10-02 21:21:33 +05:30
for i,m in enumerate(dup['material']):
2021-02-19 21:04:28 +05:30
if ID is not None and i not in ID: continue
try:
m['homogenization'] = mapping[m['homogenization']]
except KeyError:
continue
return dup
2022-02-01 13:00:00 +05:30
def material_add(self,
**kwargs: Any) -> 'ConfigMaterial':
"""
Add material entries.
Parameters
----------
2020-10-09 17:49:19 +05:30
**kwargs
2020-10-29 02:22:51 +05:30
Key-value pairs.
2021-02-24 05:10:32 +05:30
Returns
-------
updated : damask.ConfigMaterial
2021-02-24 05:10:32 +05:30
Updated material configuration.
Examples
--------
Create a dual-phase steel microstructure for micromechanical simulations:
2021-02-24 05:10:32 +05:30
>>> import numpy as np
2020-10-09 17:49:19 +05:30
>>> import damask
>>> m = damask.ConfigMaterial()
>>> m = m.material_add(phase = ['Ferrite','Martensite'],
... O = damask.Rotation.from_random(2),
... homogenization = 'SX')
2020-10-30 00:39:13 +05:30
>>> m
2020-10-09 17:49:19 +05:30
material:
- constituents:
- O: [0.577764, -0.146299, -0.617669, 0.513010]
2021-02-04 18:16:01 +05:30
v: 1.0
phase: Ferrite
2020-10-09 17:49:19 +05:30
homogenization: SX
- constituents:
- O: [0.184176, 0.340305, 0.737247, 0.553840]
2021-02-04 18:16:01 +05:30
v: 1.0
phase: Martensite
2020-10-09 17:49:19 +05:30
homogenization: SX
2021-02-24 05:10:32 +05:30
homogenization: {}
phase: {}
Create a duplex stainless steel microstructure for forming simulations:
>>> import numpy as np
>>> import damask
>>> m = damask.ConfigMaterial()
>>> m = m.material_add(phase = np.array(['Austenite','Ferrite']).reshape(1,2),
... O = damask.Rotation.from_random((2,2)),
... v = np.array([0.2,0.8]).reshape(1,2),
... homogenization = 'Taylor')
2021-02-24 05:10:32 +05:30
>>> m
material:
2020-10-09 17:49:19 +05:30
- constituents:
2021-02-24 05:10:32 +05:30
- phase: Austenite
O: [0.659802978293224, 0.6953785848195171, 0.22426295326327111, -0.17554139512785227]
v: 0.2
- phase: Ferrite
2021-02-24 05:10:32 +05:30
O: [0.49356745891301596, 0.2841806579193434, -0.7487679215072818, -0.339085707289975]
v: 0.8
homogenization: Taylor
2021-02-24 05:10:32 +05:30
- constituents:
- phase: Austenite
O: [0.26542221365204055, 0.7268854930702071, 0.4474726435701472, -0.44828201137283735]
v: 0.2
- phase: Ferrite
2021-02-24 05:10:32 +05:30
O: [0.6545817158479885, -0.08004812803625233, -0.6226561293931374, 0.4212059104577611]
v: 0.8
homogenization: Taylor
2020-12-18 11:39:05 +05:30
homogenization: {}
phase: {}
"""
N,n,shaped = 1,1,{}
map_dim = {'O':-1,'V_e':-2}
for k,v in kwargs.items():
shaped[k] = np.array(v)
2021-11-14 11:21:47 +05:30
s = shaped[k].shape[:map_dim.get(k,None)]
N = max(N,s[0]) if len(s)>0 else N
n = max(n,s[1]) if len(s)>1 else n
2022-02-01 13:00:00 +05:30
mat: Sequence[dict] = [{'constituents':[{} for _ in range(n)]} for _ in range(N)]
if 'v' not in kwargs:
shaped['v'] = np.broadcast_to(1/n,(N,n))
map_shape = {'O':(N,n,4),'V_e':(N,n,3,3)}
for k,v in shaped.items():
2021-11-14 11:21:47 +05:30
target = map_shape.get(k,(N,n))
2022-02-01 13:00:00 +05:30
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','V_e']:
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