From 46ad436d76ba3f833bdb1b39148762d75733aec1 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 1 Feb 2023 20:31:04 +0100 Subject: [PATCH] check for invalid materialpoint configuration --- python/damask/_configmaterial.py | 53 ++++++++++++++--------------- python/tests/test_ConfigMaterial.py | 37 +++++++++++++++++--- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index e318c1ebf..9691587aa 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -8,6 +8,7 @@ from . import Config from . import Rotation from . import Orientation from . import util +from . import tensor from . import Table @@ -443,7 +444,7 @@ class ConfigMaterial(Config): Phase label (per constituent). v: (array-like) of float, optional Constituent volume fraction (per constituent). - Defaults to 1/N_constituents + Defaults to 1/N_constituent. O: (array-like) of damask.Rotation or np.array/list of shape(4), optional Orientation as unit quaternion (per constituent). V_e: (array-like) of np.array/list of shape(3,3), optional @@ -536,48 +537,44 @@ class ConfigMaterial(Config): phase: {Austenite: null, Ferrite: null} """ - kwargs = {} - for keyword,value in zip(['homogenization','phase','v','O','V_e'],[homogenization,phase,v,O,V_e]): - if value is not None: kwargs[keyword] = value - - _constituent_properties = ['phase','O','v','V_e'] - _dim = {'O':(4,),'V_e':(3,3,)} - _ex = dict((k, -len(v)) for k, v in _dim.items()) + dim = {'O':(4,),'V_e':(3,3,)} + ex = dict((keyword, -len(val)) for keyword,val in dim.items()) N_materials,N_constituents = 1,1 - shaped : Dict[str, Union[None,np.ndarray]] = \ - {'v': None, - 'phase': None, - 'homogenization': None, - } - - for arg,value in kwargs.items(): - shaped[arg] = np.array(value) - s = shaped[arg].shape[:_ex.get(arg,None)] # type: ignore + shaped = {} + for arg,val in zip(['homogenization','phase','v','O','V_e'],[homogenization,phase,v,O,V_e]): + if val is None: continue + shaped[arg] = np.array(val) + s = shaped[arg].shape[:ex.get(arg,None)] # type: ignore N_materials = max(N_materials,s[0]) if len(s)>0 else N_materials N_constituents = max(N_constituents,s[1]) if len(s)>1 else N_constituents - shaped['v'] = np.array(1./N_constituents) if shaped['v'] is None else shaped['v'] + shaped['v'] = np.array(shaped.get('v',1./N_constituents),float) mat: Sequence[dict] = [{'constituents':[{} for _ in range(N_constituents)]} for _ in range(N_materials)] for k,v in shaped.items(): - target = (N_materials,N_constituents) + _dim.get(k,()) - obj = np.broadcast_to(np.array(v).reshape(util.shapeshifter(() if v is None else v.shape, - target, - mode = 'right')), - target) + target = (N_materials,N_constituents) + dim.get(k,()) + obj = np.broadcast_to(np.array(v).reshape(util.shapeshifter(np.array(v).shape,target,'right')),target) + if k == 'v': + total = obj if len(np.atleast_1d(obj)) == 1 else np.sum(obj,axis=-1) + if np.min(obj) < 0 or np.min(total) < 0 or np.max(total) > 1: + raise ValueError('volume fraction "v" out of range') + if k == 'O' and not np.allclose(1.0,np.linalg.norm(obj,axis=-1)): + raise ValueError('orientation "O" is not a unit quaterion') + elif k == 'V_e' and not np.allclose(obj,tensor.symmetric(obj)): + raise ValueError('elastic stretch "V_e" is not symmetric') for i in range(N_materials): - if k in _constituent_properties: - for j in range(N_constituents): - mat[i]['constituents'][j][k] = obj[i,j].item() if isinstance(obj[i,j],np.generic) else obj[i,j] + if k == 'homogenization': + mat[i][k] = obj[i,0] else: - mat[i][k] = obj[i,0].item() if isinstance(obj[i,0],np.generic) else obj[i,0] + for j in range(N_constituents): + mat[i]['constituents'][j][k] = obj[i,j] dup = self.copy() dup['material'] = dup['material'] + mat if 'material' in dup else mat - for what in [item for item in ['phase','homogenization'] if shaped[item] is not None]: + for what in [item for item in ['phase','homogenization'] if item in shaped]: for k in np.unique(shaped[what]): # type: ignore if k not in dup[what]: dup[what][str(k)] = None diff --git a/python/tests/test_ConfigMaterial.py b/python/tests/test_ConfigMaterial.py index a9cf9089d..8cc5a2e30 100644 --- a/python/tests/test_ConfigMaterial.py +++ b/python/tests/test_ConfigMaterial.py @@ -16,6 +16,27 @@ def ref_path(ref_path_base): class TestConfigMaterial: + def test_init_empty(self): + c = ConfigMaterial() + assert len(c) == 3 + assert c['homogenization'] == {} + assert c['phase'] == {} + assert c['material'] == [] + + def test_init_d(self): + c = ConfigMaterial(config={'phase':4}) + assert len(c) == 1 + assert c['phase'] == 4 + + @pytest.mark.parametrize('kwargs',[{'homogenization':{'SX':{}}}, + {'phase':{'Aluminum':{}}}, + {'material':[{'A':1},{'B':2}]}]) + def test_init_some(self,kwargs): + c = ConfigMaterial(**kwargs) + assert len(c) == 3 + for k,v in kwargs.items(): + if k in kwargs: assert v == kwargs[k] + @pytest.mark.parametrize('fname',[None,'test.yaml']) def test_load_save(self,ref_path,tmp_path,fname): reference = ConfigMaterial.load(ref_path/'material.yaml') @@ -88,14 +109,14 @@ class TestConfigMaterial: def test_from_table(self): N = np.random.randint(3,10) a = np.vstack((np.hstack((np.arange(N),np.arange(N)[::-1])), - np.ones(N*2),np.zeros(N*2),np.ones(N*2),np.ones(N*2), + np.zeros(N*2),np.ones(N*2),np.zeros(N*2),np.zeros(N*2), np.ones(N*2), )).T t = Table({'varying':1,'constant':4,'ones':1},a) c = ConfigMaterial.from_table(t,**{'phase':'varying','O':'constant','homogenization':'ones'}) assert len(c['material']) == N for i,m in enumerate(c['material']): - assert m['homogenization'] == 1 and (m['constituents'][0]['O'] == [1,0,1,1]).all() + assert m['homogenization'] == 1 and (m['constituents'][0]['O'] == [0,1,0,0]).all() def test_updated_dicts(self,ref_path): m1 = ConfigMaterial().material_add(phase=['Aluminum'],O=[1.0,0.0,0.0,0.0],homogenization='SX') @@ -109,14 +130,14 @@ class TestConfigMaterial: def test_from_table_with_constant(self): N = np.random.randint(3,10) a = np.vstack((np.hstack((np.arange(N),np.arange(N)[::-1])), - np.ones(N*2),np.zeros(N*2),np.ones(N*2),np.ones(N*2), + np.zeros(N*2),np.ones(N*2),np.zeros(N*2),np.zeros(N*2), np.ones(N*2), )).T t = Table({'varying':1,'constant':4,'ones':1},a) c = ConfigMaterial.from_table(t,**{'phase':'varying','O':'constant','homogenization':1}) assert len(c['material']) == N for i,m in enumerate(c['material']): - assert m['homogenization'] == 1 and (m['constituents'][0]['O'] == [1,0,1,1]).all() + assert m['homogenization'] == 1 and (m['constituents'][0]['O'] == [0,1,0,0]).all() @pytest.mark.parametrize('N,n,kw',[ (1,1,{'phase':'Gold', @@ -137,6 +158,14 @@ class TestConfigMaterial: assert len(m['material']) == N assert len(m['material'][0]['constituents']) == n + @pytest.mark.parametrize('shape',[(),(4,),(5,2)]) + @pytest.mark.parametrize('kw',[{'V_e':np.random.rand(3,3)}, + {'O':np.random.rand(4)}, + {'v':np.array(2)}]) + def test_material_add_invalid(self,kw,shape): + kw = {arg:np.broadcast_to(val,shape+val.shape) for arg,val in kw.items()} + with pytest.raises(ValueError): + ConfigMaterial().material_add(**kw) @pytest.mark.parametrize('cell_ensemble_data',[None,'CellEnsembleData']) def test_load_DREAM3D(self,ref_path,cell_ensemble_data):