From 6f45203c98c342571258790d703ffcf7ef60a578 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 19 Sep 2020 13:01:19 +0200 Subject: [PATCH 1/3] first version of a pyaml based class for material configuration pyaml is (again) actively maintained and the ruamel.pyaml API is instable --- python/damask/__init__.py | 1 + python/damask/_material.py | 156 ++++++++++++++++++ python/tests/reference/Material/material.yaml | 42 +++++ python/tests/test_Material.py | 61 +++++++ 4 files changed, 260 insertions(+) create mode 100644 python/damask/_material.py create mode 100644 python/tests/reference/Material/material.yaml create mode 100644 python/tests/test_Material.py diff --git a/python/damask/__init__.py b/python/damask/__init__.py index 1404e88d1..3f2ff6813 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -18,6 +18,7 @@ from ._lattice import Symmetry, Lattice# noqa from ._orientation import Orientation # noqa from ._result import Result # noqa from ._geom import Geom # noqa +from ._material import Material # noqa from . import solver # noqa # deprecated diff --git a/python/damask/_material.py b/python/damask/_material.py new file mode 100644 index 000000000..476a46149 --- /dev/null +++ b/python/damask/_material.py @@ -0,0 +1,156 @@ +from io import StringIO +import copy + +import yaml +import numpy as np + +from . import Lattice +from . import Rotation + +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 Material(dict): + """Material configuration.""" + + def __repr__(self): + """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. + + Parameters + ---------- + fname : file, str, or pathlib.Path + Filename or file for reading. + + """ + try: + fhandle = open(fname,'w') + except TypeError: + fhandle = fname + fhandle.write(yaml.dump(dict(self),width=256,default_flow_style=None,Dumper=NiceDumper)) + + + @property + def is_complete(self): + """Check for completeness.""" + try: + ok = len(self['microstructure']) > 0 + for m in self['microstructure']: + ok &= m['homogenization'] in self['homogenization'] + for c in m['constituents']: + c['orientation'] + ok &= c['phase'] in self['phase'] + for p in self['phase'].values(): + ok &= 'lattice' in p + return ok + except KeyError: + return False + + + @property + def is_valid(self): + """Check for valid file layout.""" + ok = True + + if 'phase' in self: + for k,v in zip(self['phase'].keys(),self['phase'].values()): + if 'lattice' in v: + try: + Lattice(v['lattice']) + except KeyError: + s = v['lattice'] + print(f"Invalid lattice: '{s}' in phase '{k}'") + ok = False + + if 'microstructure' in self: + for i,v in enumerate(self['microstructure']): + if 'constituents' in v: + f = 0.0 + for c in v['constituents']: + f+= float(c['fraction']) + if 'orientation' in c: + try: + Rotation.from_quaternion(c['orientation']) + except ValueError: + o = c['orientation'] + print(f"Invalid orientation: '{o}' in microstructure '{i}'") + ok = False + if not np.isclose(f,1.0): + print(f"Invalid total fraction '{f}' in microstructure '{i}'") + ok = False + + return ok + + + def microstructure_rename_phase(self,mapping,ID=None,constituent=None): + """ + Change phase name in microstructure. + + Parameters + ---------- + mapping: dictionary + Mapping from old name to new name + ID: list of ints, optional + Limit renaming to selected microstructure IDs. + constituent: list of ints, optional + Limit renaming to selected constituents. + + """ + dup = copy.deepcopy(self) + for i,m in enumerate(dup['microstructure']): + 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 + + + def microstructure_rename_homogenization(self,mapping,ID=None): + """ + Change homogenization name in microstructure. + + 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) + for i,m in enumerate(dup['microstructure']): + if ID and i not in ID: continue + try: + m['homogenization'] = mapping[m['homogenization']] + except KeyError: + continue + return dup diff --git a/python/tests/reference/Material/material.yaml b/python/tests/reference/Material/material.yaml new file mode 100644 index 000000000..1d3cf08a8 --- /dev/null +++ b/python/tests/reference/Material/material.yaml @@ -0,0 +1,42 @@ +homogenization: + SX: + mech: {type: none} + Taylor: + mech: {type: isostrain, N_constituents: 2} + +microstructure: + - constituents: + - fraction: 1.0 + orientation: [1.0, 0.0, 0.0, 0.0] + phase: Aluminum + homogenization: SX + - constituents: + - fraction: 1.0 + orientation: [0.7936696712125002, -0.28765777461664166, -0.3436487135089419, 0.4113964260949434] + phase: Aluminum + homogenization: SX + - constituents: + - fraction: 1.0 + orientation: [0.3986143167493579, -0.7014883552495493, 0.2154871765709027, 0.5500781677772945] + phase: Aluminum + homogenization: SX + - homogenization: Taylor + constituents: + - fraction: .5 + orientation: [0.28645844315788244, -0.022571491243423537, -0.467933059311115, -0.8357456192708106] + phase: Aluminum + - fraction: .5 + orientation: [0.3986143167493579, -0.7014883552495493, 0.2154871765709027, 0.5500781677772945] + phase: Steel + +phase: + Aluminum: + elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} + generic: + output: [F, P, Fe, Fp, Lp] + lattice: fcc + Steel: + elasticity: {C_11: 233.3e9, C_12: 135.5e9, C_44: 118.0e9, type: hooke} + generic: + output: [F, P, Fe, Fp, Lp] + lattice: bcc diff --git a/python/tests/test_Material.py b/python/tests/test_Material.py new file mode 100644 index 000000000..567dfe646 --- /dev/null +++ b/python/tests/test_Material.py @@ -0,0 +1,61 @@ +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 From 5ef761fb985ffb2af5015115ec966481bf6bb284 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 21 Sep 2020 19:10:20 +0200 Subject: [PATCH 2/3] inform the user about missing items in material.yaml --- python/damask/_material.py | 63 ++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/python/damask/_material.py b/python/damask/_material.py index 476a46149..106d1582e 100644 --- a/python/damask/_material.py +++ b/python/damask/_material.py @@ -59,18 +59,55 @@ class Material(dict): @property def is_complete(self): """Check for completeness.""" - try: - ok = len(self['microstructure']) > 0 - for m in self['microstructure']: - ok &= m['homogenization'] in self['homogenization'] - for c in m['constituents']: - c['orientation'] - ok &= c['phase'] in self['phase'] - for p in self['phase'].values(): - ok &= 'lattice' in p - return ok - except KeyError: - return False + ok = True + for top_level in ['homogenization','phase','microstructure']: + # 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: + ok &= len(self['microstructure']) > 0 + if len(self['microstructure']) < 1: print('Incomplete microstructure definition') + + if ok: + homogenization = set() + phase = set() + for i,v in enumerate(self['microstructure']): + if 'homogenization' in v: + homogenization.add(v['homogenization']) + else: + print(f'No homogenization specified in microstructure {i}') + ok = False + + if 'constituents' in v: + for ii,vv in enumerate(v['constituents']): + if 'orientation' not in vv: + print('No orientation specified in constituent {ii} of microstructure {i}') + ok = False + if 'phase' in vv: + phase.add(vv['phase']) + else: + print(f'No phase specified in constituent {ii} of microstructure {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 @@ -79,7 +116,7 @@ class Material(dict): ok = True if 'phase' in self: - for k,v in zip(self['phase'].keys(),self['phase'].values()): + for k,v in self['phase'].items(): if 'lattice' in v: try: Lattice(v['lattice']) From 03cf971f55294172df4f7a9ab5f9aa9a4947f88c Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 22 Sep 2020 01:56:52 +0200 Subject: [PATCH 3/3] [skip ci] updated version information after successful test of v3.0.0-alpha-245-g5ef761fb9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bebbc79d0..e073be898 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v3.0.0-alpha-233-g190f8a82 +v3.0.0-alpha-245-g5ef761fb9