2020-09-19 16:31:19 +05:30
|
|
|
import copy
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
2020-10-09 17:49:19 +05:30
|
|
|
from . import grid_filters
|
2020-09-30 11:23:25 +05:30
|
|
|
from . import Config
|
2020-09-19 16:31:19 +05:30
|
|
|
from . import Lattice
|
|
|
|
from . import Rotation
|
|
|
|
|
2020-09-30 11:23:25 +05:30
|
|
|
class ConfigMaterial(Config):
|
2020-09-19 16:31:19 +05:30
|
|
|
"""Material configuration."""
|
|
|
|
|
2020-09-30 16:02:37 +05:30
|
|
|
def save(self,fname='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
|
2020-09-30 16:02:37 +05:30
|
|
|
Keyword arguments parsed to yaml.dump.
|
2020-09-30 12:19:55 +05:30
|
|
|
|
|
|
|
"""
|
2020-09-30 16:02:37 +05:30
|
|
|
super().save(fname,**kwargs)
|
2020-09-30 12:19:55 +05:30
|
|
|
|
2020-10-09 11:15:20 +05:30
|
|
|
|
|
|
|
@staticmethod
|
2020-10-09 22:49:05 +05:30
|
|
|
def from_table(table,coordinates=None,constituents={},**kwargs):
|
2020-10-09 17:49:19 +05:30
|
|
|
"""
|
|
|
|
Load from an ASCII table.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2020-10-09 22:49:05 +05:30
|
|
|
table : damask.Table
|
2020-10-09 17:49:19 +05:30
|
|
|
Table that contains material information.
|
|
|
|
coordinates : str, optional
|
|
|
|
Label of spatial coordiates. Used for sorting and performing a
|
|
|
|
sanity check. Default to None, in which case no sorting or checking is
|
|
|
|
peformed.
|
|
|
|
constituents : dict, optional
|
|
|
|
Entries for 'constituents'. The key is the name and the value specifies
|
|
|
|
the label of the data column in the table
|
|
|
|
**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
|
2020-10-09 22:49:05 +05:30
|
|
|
>>> t = damask.Table.load('small.txt')
|
|
|
|
>>> t
|
2020-10-09 17:49:19 +05:30
|
|
|
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
|
2020-10-09 22:49:05 +05:30
|
|
|
>>> cm.from_table(t,'pos',{'O':'qu','phase':'phase'},homogenization='homog')
|
2020-10-09 17:49:19 +05:30
|
|
|
material:
|
|
|
|
- constituents:
|
|
|
|
- O: [0.19, 0.8, 0.24, -0.51]
|
|
|
|
fraction: 1.0
|
|
|
|
phase: Aluminum
|
|
|
|
homogenization: SX
|
|
|
|
- constituents:
|
|
|
|
- O: [0.8, 0.19, 0.24, -0.51]
|
|
|
|
fraction: 1.0
|
|
|
|
phase: Steel
|
|
|
|
homogenization: SX
|
|
|
|
|
|
|
|
"""
|
|
|
|
if coordinates is not None:
|
2020-10-09 22:49:05 +05:30
|
|
|
t = table.sort_by([f'{i}_{coordinates}' for i in range(3,0,-1)])
|
2020-10-09 17:49:19 +05:30
|
|
|
grid_filters.coord0_check(t.get(coordinates))
|
2020-10-09 22:49:05 +05:30
|
|
|
else:
|
|
|
|
t = table
|
|
|
|
|
2020-10-09 11:15:20 +05:30
|
|
|
constituents_ = {k:t.get(v) for k,v in constituents.items()}
|
|
|
|
kwargs_ = {k:t.get(v) for k,v in kwargs.items()}
|
|
|
|
|
|
|
|
_,idx = np.unique(np.hstack(list({**constituents_,**kwargs_}.values())),return_index=True,axis=0)
|
|
|
|
|
|
|
|
constituents_ = {k:v[idx].squeeze() for k,v in constituents_.items()}
|
|
|
|
kwargs_ = {k:v[idx].squeeze() for k,v in kwargs_.items()}
|
|
|
|
|
|
|
|
return ConfigMaterial().material_add(constituents_,**kwargs_)
|
|
|
|
|
|
|
|
|
2020-09-19 16:31:19 +05:30
|
|
|
@property
|
|
|
|
def is_complete(self):
|
|
|
|
"""Check for completeness."""
|
2020-09-21 22:40:20 +05:30
|
|
|
ok = True
|
2020-10-02 21:21:33 +05:30
|
|
|
for top_level in ['homogenization','phase','material']:
|
2020-09-21 22:40:20 +05:30
|
|
|
# ToDo: With python 3.8 as prerequisite we can shorten with :=
|
|
|
|
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')
|
2020-09-21 22:40:20 +05:30
|
|
|
|
|
|
|
if ok:
|
|
|
|
homogenization = set()
|
|
|
|
phase = set()
|
2020-10-02 21:21:33 +05:30
|
|
|
for i,v in enumerate(self['material']):
|
2020-09-21 22:40:20 +05:30
|
|
|
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}')
|
2020-09-21 22:40:20 +05:30
|
|
|
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}')
|
2020-09-21 22:40:20 +05:30
|
|
|
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}')
|
2020-09-21 22:40:20 +05:30
|
|
|
ok = False
|
|
|
|
|
|
|
|
for k,v in self['phase'].items():
|
|
|
|
if 'lattice' not in v:
|
|
|
|
print(f'No lattice specified in phase {k}')
|
|
|
|
ok = False
|
|
|
|
|
2020-10-09 00:59:58 +05:30
|
|
|
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
|
2020-09-21 22:40:20 +05:30
|
|
|
|
|
|
|
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
|
2020-09-19 16:31:19 +05:30
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_valid(self):
|
|
|
|
"""Check for valid file layout."""
|
|
|
|
ok = True
|
|
|
|
|
|
|
|
if 'phase' in self:
|
2020-09-21 22:40:20 +05:30
|
|
|
for k,v in self['phase'].items():
|
2020-09-19 16:31:19 +05:30
|
|
|
if 'lattice' in v:
|
|
|
|
try:
|
|
|
|
Lattice(v['lattice'])
|
|
|
|
except KeyError:
|
|
|
|
s = v['lattice']
|
|
|
|
print(f"Invalid lattice: '{s}' in phase '{k}'")
|
|
|
|
ok = False
|
|
|
|
|
2020-10-02 21:21:33 +05:30
|
|
|
if 'material' in self:
|
|
|
|
for i,v in enumerate(self['material']):
|
2020-09-19 16:31:19 +05:30
|
|
|
if 'constituents' in v:
|
|
|
|
f = 0.0
|
|
|
|
for c in v['constituents']:
|
|
|
|
f+= float(c['fraction'])
|
2020-10-02 21:21:33 +05:30
|
|
|
if 'O' in c:
|
2020-09-19 16:31:19 +05:30
|
|
|
try:
|
2020-10-02 21:21:33 +05:30
|
|
|
Rotation.from_quaternion(c['O'])
|
2020-09-19 16:31:19 +05:30
|
|
|
except ValueError:
|
2020-10-02 21:21:33 +05:30
|
|
|
o = c['O']
|
|
|
|
print(f"Invalid orientation: '{o}' in material '{i}'")
|
2020-09-19 16:31:19 +05:30
|
|
|
ok = False
|
|
|
|
if not np.isclose(f,1.0):
|
2020-10-02 21:21:33 +05:30
|
|
|
print(f"Invalid total fraction '{f}' in material '{i}'")
|
2020-09-19 16:31:19 +05:30
|
|
|
ok = False
|
|
|
|
|
|
|
|
return ok
|
|
|
|
|
|
|
|
|
2020-10-02 21:21:33 +05:30
|
|
|
def material_rename_phase(self,mapping,ID=None,constituent=None):
|
2020-09-19 16:31:19 +05:30
|
|
|
"""
|
2020-10-02 21:21:33 +05:30
|
|
|
Change phase name in material.
|
2020-09-19 16:31:19 +05:30
|
|
|
|
|
|
|
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.
|
2020-09-19 16:31:19 +05:30
|
|
|
constituent: list of ints, optional
|
|
|
|
Limit renaming to selected constituents.
|
|
|
|
|
|
|
|
"""
|
|
|
|
dup = copy.deepcopy(self)
|
2020-10-02 21:21:33 +05:30
|
|
|
for i,m in enumerate(dup['material']):
|
2020-09-19 16:31:19 +05:30
|
|
|
if ID 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
|
|
|
|
|
|
|
|
|
2020-10-02 21:21:33 +05:30
|
|
|
def material_rename_homogenization(self,mapping,ID=None):
|
2020-09-19 16:31:19 +05:30
|
|
|
"""
|
2020-10-02 21:21:33 +05:30
|
|
|
Change homogenization name in material.
|
2020-09-19 16:31:19 +05:30
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
mapping: dictionary
|
|
|
|
Mapping from old name to new name
|
|
|
|
ID: list of ints, optional
|
|
|
|
Limit renaming to selected homogenization IDs.
|
|
|
|
|
|
|
|
"""
|
|
|
|
dup = copy.deepcopy(self)
|
2020-10-02 21:21:33 +05:30
|
|
|
for i,m in enumerate(dup['material']):
|
2020-09-19 16:31:19 +05:30
|
|
|
if ID and i not in ID: continue
|
|
|
|
try:
|
|
|
|
m['homogenization'] = mapping[m['homogenization']]
|
|
|
|
except KeyError:
|
|
|
|
continue
|
|
|
|
return dup
|
2020-10-09 00:59:58 +05:30
|
|
|
|
|
|
|
|
|
|
|
def material_add(self,constituents,**kwargs):
|
|
|
|
"""
|
|
|
|
Add material entries.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2020-10-09 17:49:19 +05:30
|
|
|
constituents : dict
|
|
|
|
Entries for 'constituents'. The key is the name and the value specifies
|
|
|
|
the label of the data column in the table
|
|
|
|
**kwargs
|
|
|
|
Keyword arguments where the key is the name and the value specifies
|
|
|
|
the label of the data column in the table
|
2020-10-09 00:59:58 +05:30
|
|
|
|
|
|
|
Examples
|
|
|
|
--------
|
2020-10-09 17:49:19 +05:30
|
|
|
>>> import damask
|
|
|
|
>>> m = damask.ConfigMaterial()
|
|
|
|
>>> O = damask.Rotation.from_random(3).as_quaternion()
|
|
|
|
>>> phase = ['Aluminum','Steel','Aluminum']
|
|
|
|
>>> m.material_add(constituents={'phase':phase,'O':O},homogenization='SX')
|
|
|
|
material:
|
|
|
|
- constituents:
|
|
|
|
- O: [0.577764, -0.146299, -0.617669, 0.513010]
|
|
|
|
fraction: 1.0
|
|
|
|
phase: Aluminum
|
|
|
|
homogenization: SX
|
|
|
|
- constituents:
|
|
|
|
- O: [0.184176, 0.340305, 0.737247, 0.553840]
|
|
|
|
fraction: 1.0
|
|
|
|
phase: Steel
|
|
|
|
homogenization: SX
|
|
|
|
- constituents:
|
|
|
|
- O: [0.0886257, -0.144848, 0.615674, -0.769487]
|
|
|
|
fraction: 1.0
|
|
|
|
phase: Aluminum
|
|
|
|
homogenization: SX
|
2020-10-09 22:49:05 +05:30
|
|
|
|
2020-10-09 00:59:58 +05:30
|
|
|
"""
|
|
|
|
c = [{'constituents':u} for u in ConfigMaterial._constituents(**constituents)]
|
|
|
|
for k,v in kwargs.items():
|
2020-10-09 11:15:20 +05:30
|
|
|
if hasattr(v,'__len__') and not isinstance(v,str):
|
|
|
|
if len(v) != len(c):
|
|
|
|
raise ValueError('len mismatch 1')
|
2020-10-09 00:59:58 +05:30
|
|
|
for i,vv in enumerate(v):
|
|
|
|
c[i][k] = [w.item() for w in vv] if isinstance(vv,np.ndarray) else vv.item()
|
|
|
|
else:
|
|
|
|
for i in range(len(c)):
|
|
|
|
c[i][k] = v
|
|
|
|
dup = copy.deepcopy(self)
|
|
|
|
if 'material' not in dup: dup['material'] = []
|
|
|
|
dup['material'] +=c
|
|
|
|
|
|
|
|
return dup
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _constituents(N=1,**kwargs):
|
2020-10-09 11:15:20 +05:30
|
|
|
"""Construct list of constituents."""
|
2020-10-09 00:59:58 +05:30
|
|
|
for v in kwargs.values():
|
|
|
|
if hasattr(v,'__len__') and not isinstance(v,str): N_material = len(v)
|
|
|
|
|
|
|
|
if N == 1:
|
|
|
|
m = [[{'fraction':1.0}] for _ in range(N_material)]
|
|
|
|
for k,v in kwargs.items():
|
|
|
|
if hasattr(v,'__len__') and not isinstance(v,str):
|
|
|
|
if len(v) != N_material:
|
2020-10-09 11:15:20 +05:30
|
|
|
raise ValueError('len mismatch 2')
|
2020-10-09 00:59:58 +05:30
|
|
|
for i,vv in enumerate(np.array(v)):
|
|
|
|
m[i][0][k] = [w.item() for w in vv] if isinstance(vv,np.ndarray) else vv.item()
|
|
|
|
else:
|
|
|
|
for i in range(N_material):
|
|
|
|
m[i][0][k] = v
|
|
|
|
return m
|
|
|
|
else:
|
|
|
|
raise NotImplementedError
|