Merge branch 'general-config' into 'development'

General config

See merge request damask/DAMASK!245
This commit is contained in:
Philip Eisenlohr 2020-10-08 00:48:16 +02:00
commit 846cbdd2e4
7 changed files with 249 additions and 149 deletions

View File

@ -18,7 +18,8 @@ from ._lattice import Symmetry, Lattice# noqa
from ._orientation import Orientation # noqa from ._orientation import Orientation # noqa
from ._result import Result # noqa from ._result import Result # noqa
from ._geom import Geom # noqa from ._geom import Geom # noqa
from ._material import Material # noqa from ._config import Config # noqa
from ._configmaterial import ConfigMaterial # noqa
from . import solver # noqa from . import solver # noqa
from . import util # noqa from . import util # noqa
from . import seeds # noqa from . import seeds # noqa

80
python/damask/_config.py Normal file
View File

@ -0,0 +1,80 @@
from io import StringIO
import abc
import yaml
class NiceDumper(yaml.SafeDumper):
"""Make YAML readable for humans."""
def write_line_break(self, data=None):
super().write_line_break(data)
if len(self.indents) == 1:
super().write_line_break()
def increase_indent(self, flow=False, indentless=False):
return super().increase_indent(flow, False)
class Config(dict):
"""YAML-based configuration."""
def __repr__(self):
"""Show as in file."""
output = StringIO()
self.save(output)
output.seek(0)
return ''.join(output.readlines())
@classmethod
def load(cls,fname):
"""
Load from yaml file.
Parameters
----------
fname : file, str, or pathlib.Path
Filename or file for writing.
"""
try:
fhandle = open(fname)
except TypeError:
fhandle = fname
return cls(yaml.safe_load(fhandle))
def save(self,fname,**kwargs):
"""
Save to yaml file.
Parameters
----------
fname : file, str, or pathlib.Path
Filename or file for writing.
**kwargs : dict
Keyword arguments parsed to yaml.dump.
"""
try:
fhandle = open(fname,'w')
except TypeError:
fhandle = fname
if 'width' not in kwargs:
kwargs['width'] = 256
if 'default_flow_style' not in kwargs:
kwargs['default_flow_style'] = None
fhandle.write(yaml.dump(dict(self),Dumper=NiceDumper,**kwargs))
@property
@abc.abstractmethod
def is_complete(self):
"""Check for completeness."""
pass
@property
@abc.abstractmethod
def is_valid(self):
"""Check for valid file layout."""
pass

View File

@ -1,93 +1,60 @@
from io import StringIO
import copy import copy
import yaml
import numpy as np import numpy as np
from . import Config
from . import Lattice from . import Lattice
from . import Rotation from . import Rotation
class NiceDumper(yaml.SafeDumper): class ConfigMaterial(Config):
"""Make YAML readable for humans."""
def write_line_break(self, data=None):
super().write_line_break(data)
if len(self.indents) == 1:
super().write_line_break()
def increase_indent(self, flow=False, indentless=False):
return super().increase_indent(flow, False)
class Material(dict):
"""Material configuration.""" """Material configuration."""
def __repr__(self): def save(self,fname='material.yaml',**kwargs):
"""Show as in file."""
output = StringIO()
self.save(output)
output.seek(0)
return ''.join(output.readlines())
@staticmethod
def load(fname):
"""Load from yaml file."""
try:
fhandle = open(fname)
except TypeError:
fhandle = fname
return Material(yaml.safe_load(fhandle))
def save(self,fname='material.yaml'):
""" """
Save to yaml file. Save to yaml file.
Parameters Parameters
---------- ----------
fname : file, str, or pathlib.Path fname : file, str, or pathlib.Path, optional
Filename or file for reading. Filename or file for writing. Defaults to 'material.yaml'.
**kwargs : dict
Keyword arguments parsed to yaml.dump.
""" """
try: super().save(fname,**kwargs)
fhandle = open(fname,'w')
except TypeError:
fhandle = fname
fhandle.write(yaml.dump(dict(self),width=256,default_flow_style=None,Dumper=NiceDumper))
@property @property
def is_complete(self): def is_complete(self):
"""Check for completeness.""" """Check for completeness."""
ok = True ok = True
for top_level in ['homogenization','phase','microstructure']: for top_level in ['homogenization','phase','material']:
# ToDo: With python 3.8 as prerequisite we can shorten with := # ToDo: With python 3.8 as prerequisite we can shorten with :=
ok &= top_level in self ok &= top_level in self
if top_level not in self: print(f'{top_level} entry missing') if top_level not in self: print(f'{top_level} entry missing')
if ok: if ok:
ok &= len(self['microstructure']) > 0 ok &= len(self['material']) > 0
if len(self['microstructure']) < 1: print('Incomplete microstructure definition') if len(self['material']) < 1: print('Incomplete material definition')
if ok: if ok:
homogenization = set() homogenization = set()
phase = set() phase = set()
for i,v in enumerate(self['microstructure']): for i,v in enumerate(self['material']):
if 'homogenization' in v: if 'homogenization' in v:
homogenization.add(v['homogenization']) homogenization.add(v['homogenization'])
else: else:
print(f'No homogenization specified in microstructure {i}') print(f'No homogenization specified in material {i}')
ok = False ok = False
if 'constituents' in v: if 'constituents' in v:
for ii,vv in enumerate(v['constituents']): for ii,vv in enumerate(v['constituents']):
if 'orientation' not in vv: if 'O' not in vv:
print('No orientation specified in constituent {ii} of microstructure {i}') print('No orientation specified in constituent {ii} of material {i}')
ok = False ok = False
if 'phase' in vv: if 'phase' in vv:
phase.add(vv['phase']) phase.add(vv['phase'])
else: else:
print(f'No phase specified in constituent {ii} of microstructure {i}') print(f'No phase specified in constituent {ii} of material {i}')
ok = False ok = False
for k,v in self['phase'].items(): for k,v in self['phase'].items():
@ -125,42 +92,42 @@ class Material(dict):
print(f"Invalid lattice: '{s}' in phase '{k}'") print(f"Invalid lattice: '{s}' in phase '{k}'")
ok = False ok = False
if 'microstructure' in self: if 'material' in self:
for i,v in enumerate(self['microstructure']): for i,v in enumerate(self['material']):
if 'constituents' in v: if 'constituents' in v:
f = 0.0 f = 0.0
for c in v['constituents']: for c in v['constituents']:
f+= float(c['fraction']) f+= float(c['fraction'])
if 'orientation' in c: if 'O' in c:
try: try:
Rotation.from_quaternion(c['orientation']) Rotation.from_quaternion(c['O'])
except ValueError: except ValueError:
o = c['orientation'] o = c['O']
print(f"Invalid orientation: '{o}' in microstructure '{i}'") print(f"Invalid orientation: '{o}' in material '{i}'")
ok = False ok = False
if not np.isclose(f,1.0): if not np.isclose(f,1.0):
print(f"Invalid total fraction '{f}' in microstructure '{i}'") print(f"Invalid total fraction '{f}' in material '{i}'")
ok = False ok = False
return ok return ok
def microstructure_rename_phase(self,mapping,ID=None,constituent=None): def material_rename_phase(self,mapping,ID=None,constituent=None):
""" """
Change phase name in microstructure. Change phase name in material.
Parameters Parameters
---------- ----------
mapping: dictionary mapping: dictionary
Mapping from old name to new name Mapping from old name to new name
ID: list of ints, optional ID: list of ints, optional
Limit renaming to selected microstructure IDs. Limit renaming to selected material IDs.
constituent: list of ints, optional constituent: list of ints, optional
Limit renaming to selected constituents. Limit renaming to selected constituents.
""" """
dup = copy.deepcopy(self) dup = copy.deepcopy(self)
for i,m in enumerate(dup['microstructure']): for i,m in enumerate(dup['material']):
if ID and i not in ID: continue if ID and i not in ID: continue
for c in m['constituents']: for c in m['constituents']:
if constituent is not None and c not in constituent: continue if constituent is not None and c not in constituent: continue
@ -171,9 +138,9 @@ class Material(dict):
return dup return dup
def microstructure_rename_homogenization(self,mapping,ID=None): def material_rename_homogenization(self,mapping,ID=None):
""" """
Change homogenization name in microstructure. Change homogenization name in material.
Parameters Parameters
---------- ----------
@ -184,7 +151,7 @@ class Material(dict):
""" """
dup = copy.deepcopy(self) dup = copy.deepcopy(self)
for i,m in enumerate(dup['microstructure']): for i,m in enumerate(dup['material']):
if ID and i not in ID: continue if ID and i not in ID: continue
try: try:
m['homogenization'] = mapping[m['homogenization']] m['homogenization'] = mapping[m['homogenization']]

View File

@ -2,32 +2,32 @@ homogenization:
SX: SX:
mech: {type: none} mech: {type: none}
Taylor: Taylor:
mech: {type: isostrain, N_constituents: 2} mech: {N_constituents: 2, type: isostrain}
microstructure: material:
- constituents: - constituents:
- fraction: 1.0 - fraction: 1.0
orientation: [1.0, 0.0, 0.0, 0.0] O: [1.0, 0.0, 0.0, 0.0]
phase: Aluminum phase: Aluminum
homogenization: SX homogenization: SX
- constituents: - constituents:
- fraction: 1.0 - fraction: 1.0
orientation: [0.7936696712125002, -0.28765777461664166, -0.3436487135089419, 0.4113964260949434] O: [0.7936696712125002, -0.28765777461664166, -0.3436487135089419, 0.4113964260949434]
phase: Aluminum phase: Aluminum
homogenization: SX homogenization: SX
- constituents: - constituents:
- fraction: 1.0 - fraction: 1.0
orientation: [0.3986143167493579, -0.7014883552495493, 0.2154871765709027, 0.5500781677772945] O: [0.3986143167493579, -0.7014883552495493, 0.2154871765709027, 0.5500781677772945]
phase: Aluminum phase: Aluminum
homogenization: SX homogenization: SX
- homogenization: Taylor - constituents:
constituents: - fraction: 0.5
- fraction: .5 O: [0.28645844315788244, -0.022571491243423537, -0.467933059311115, -0.8357456192708106]
orientation: [0.28645844315788244, -0.022571491243423537, -0.467933059311115, -0.8357456192708106]
phase: Aluminum phase: Aluminum
- fraction: .5 - fraction: 0.5
orientation: [0.3986143167493579, -0.7014883552495493, 0.2154871765709027, 0.5500781677772945] O: [0.3986143167493579, -0.7014883552495493, 0.2154871765709027, 0.5500781677772945]
phase: Steel phase: Steel
homogenization: Taylor
phase: phase:
Aluminum: Aluminum:

View File

@ -0,0 +1,37 @@
import pytest
from damask import Config
class TestConfig:
@pytest.mark.parametrize('flow_style',[None,True,False])
def test_load_save_str(self,tmp_path,flow_style):
config = Config()
config['A'] = 1
config['B'] = [2,3]
config.save(tmp_path/'config.yaml',default_flow_style=flow_style)
assert Config.load(tmp_path/'config.yaml') == config
def test_load_save_file(self,tmp_path):
config = Config()
config['A'] = 1
config['B'] = [2,3]
with open(tmp_path/'config.yaml','w') as f:
config.save(f)
with open(tmp_path/'config.yaml') as f:
assert Config.load(f) == config
def test_repr(self,tmp_path):
config = Config()
config['A'] = 1
config['B'] = [2,3]
with open(tmp_path/'config.yaml','w') as f:
f.write(config.__repr__())
assert Config.load(tmp_path/'config.yaml') == config
def test_abstract_is_valid(self):
assert Config().is_valid is None
def test_abstract_is_complete(self):
assert Config().is_complete is None

View File

@ -0,0 +1,76 @@
import os
import pytest
from damask import ConfigMaterial
@pytest.fixture
def reference_dir(reference_dir_base):
"""Directory containing reference results."""
return reference_dir_base/'ConfigMaterial'
class TestConfigMaterial:
@pytest.mark.parametrize('fname',[None,'test.yaml'])
def test_load_save(self,reference_dir,tmp_path,fname):
reference = ConfigMaterial.load(reference_dir/'material.yaml')
os.chdir(tmp_path)
if fname is None:
reference.save()
new = ConfigMaterial.load('material.yaml')
else:
reference.save(fname)
new = ConfigMaterial.load(fname)
assert reference == new
def test_valid_complete(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
assert material_config.is_valid and material_config.is_complete
def test_invalid_lattice(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
material_config['phase']['Aluminum']['lattice']='fxc'
assert not material_config.is_valid
def test_invalid_orientation(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
material_config['material'][0]['constituents'][0]['O']=[0,0,0,0]
assert not material_config.is_valid
def test_invalid_fraction(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
material_config['material'][0]['constituents'][0]['fraction']=.9
assert not material_config.is_valid
@pytest.mark.parametrize('item',['homogenization','phase','material'])
def test_incomplete_missing(self,reference_dir,item):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
del material_config[item]
assert not material_config.is_complete
@pytest.mark.parametrize('item',['O','phase'])
def test_incomplete_material_constituent(self,reference_dir,item):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
del material_config['material'][0]['constituents'][0][item]
assert not material_config.is_complete
def test_incomplete_material_homogenization(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
del material_config['material'][0]['homogenization']
assert not material_config.is_complete
def test_incomplete_phase_lattice(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
del material_config['phase']['Aluminum']['lattice']
assert not material_config.is_complete
def test_incomplete_wrong_phase(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
new = material_config.material_rename_phase({'Steel':'FeNbC'})
assert not new.is_complete
def test_incomplete_wrong_homogenization(self,reference_dir):
material_config = ConfigMaterial.load(reference_dir/'material.yaml')
new = material_config.material_rename_homogenization({'Taylor':'isostrain'})
assert not new.is_complete

View File

@ -1,61 +0,0 @@
import os
import pytest
from damask import Material
@pytest.fixture
def reference_dir(reference_dir_base):
"""Directory containing reference results."""
return reference_dir_base/'Material'
class TestMaterial:
@pytest.mark.parametrize('fname',[None,'test.yaml'])
def test_load_save(self,reference_dir,tmp_path,fname):
reference = Material.load(reference_dir/'material.yaml')
os.chdir(tmp_path)
if fname is None:
reference.save()
new = Material.load('material.yaml')
else:
reference.save(fname)
new = Material.load(fname)
assert reference == new
def test_valid_complete(self,reference_dir):
material_config = Material.load(reference_dir/'material.yaml')
assert material_config.is_valid and material_config.is_complete
def test_invalid_lattice(self,reference_dir):
material_config = Material.load(reference_dir/'material.yaml')
material_config['phase']['Aluminum']['lattice']='fxc'
assert not material_config.is_valid
def test_invalid_orientation(self,reference_dir):
material_config = Material.load(reference_dir/'material.yaml')
material_config['microstructure'][0]['constituents'][0]['orientation']=[0,0,0,0]
assert not material_config.is_valid
def test_invalid_fraction(self,reference_dir):
material_config = Material.load(reference_dir/'material.yaml')
material_config['microstructure'][0]['constituents'][0]['fraction']=.9
assert not material_config.is_valid
@pytest.mark.parametrize('item',['homogenization','phase','microstructure'])
def test_incomplete_missing(self,reference_dir,item):
material_config = Material.load(reference_dir/'material.yaml')
del material_config[item]
assert not material_config.is_complete
def test_incomplete_wrong_phase(self,reference_dir):
material_config = Material.load(reference_dir/'material.yaml')
new = material_config.microstructure_rename_phase({'Steel':'FeNbC'})
assert not new.is_complete
def test_incomplete_wrong_homogenization(self,reference_dir):
material_config = Material.load(reference_dir/'material.yaml')
new = material_config.microstructure_rename_homogenization({'Taylor':'isostrain'})
assert not new.is_complete