diff --git a/PRIVATE b/PRIVATE index f654a4143..0289c1bbf 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit f654a4143b1fbbecd137dc5d2193f5cf48ab1448 +Subproject commit 0289c1bbfec1a1aef77a8cbaeed134035549e738 diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index 1cda2e46b..e2f34e92a 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -3,6 +3,7 @@ import numpy as np from . import Config from . import Rotation from . import Orientation +from . import util class ConfigMaterial(Config): """Material configuration.""" @@ -46,7 +47,7 @@ class ConfigMaterial(Config): @staticmethod - def from_table(table,constituents={},**kwargs): + def from_table(table,**kwargs): """ Load from an ASCII table. @@ -54,12 +55,9 @@ class ConfigMaterial(Config): ---------- table : damask.Table Table that contains material information. - 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 + Keyword arguments where the key is the name and the value specifies + the label of the data column in the table. Examples -------- @@ -70,7 +68,8 @@ class ConfigMaterial(Config): 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 - >>> cm.from_table(t,{'O':'qu','phase':'phase'},homogenization='homog') + 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] @@ -86,16 +85,13 @@ class ConfigMaterial(Config): phase: {} """ - constituents_ = {k:table.get(v) for k,v in constituents.items()} kwargs_ = {k:table.get(v) for k,v in kwargs.items()} - _,idx = np.unique(np.hstack(list({**constituents_,**kwargs_}.values())),return_index=True,axis=0) - + _,idx = np.unique(np.hstack(list(kwargs_.values())),return_index=True,axis=0) idx = np.sort(idx) - constituents_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in constituents_.items()} - kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()} + kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()} - return ConfigMaterial().material_add(constituents_,**kwargs_) + return ConfigMaterial().material_add(**kwargs_) @property @@ -153,7 +149,7 @@ class ConfigMaterial(Config): @property def is_valid(self): - """Check for valid file layout.""" + """Check for valid content.""" ok = True if 'phase' in self: @@ -162,8 +158,7 @@ class ConfigMaterial(Config): try: Orientation(lattice=v['lattice']) except KeyError: - s = v['lattice'] - print(f"Invalid lattice: '{s}' in phase '{k}'") + print(f"Invalid lattice '{v['lattice']}' in phase '{k}'") ok = False if 'material' in self: @@ -171,16 +166,15 @@ class ConfigMaterial(Config): if 'constituents' in m: v = 0.0 for c in m['constituents']: - v+= float(c['v']) + v += float(c['v']) if 'O' in c: try: Rotation.from_quaternion(c['O']) except ValueError: - o = c['O'] - print(f"Invalid orientation: '{o}' in material '{i}'") + print(f"Invalid orientation '{c['O']}' in material '{i}'") ok = False if not np.isclose(v,1.0): - print(f"Invalid total fraction (v) '{v}' in material '{i}'") + print(f"Total fraction v = {v} ≠ 1 in material '{i}'") ok = False return ok @@ -199,6 +193,11 @@ class ConfigMaterial(Config): constituent: list of ints, optional Limit renaming to selected constituents. + Returns + ------- + cfg : damask.ConfigMaterial + Updated material configuration. + """ dup = self.copy() for i,m in enumerate(dup['material']): @@ -223,6 +222,11 @@ class ConfigMaterial(Config): ID: list of ints, optional Limit renaming to selected homogenization IDs. + Returns + ------- + cfg : damask.ConfigMaterial + Updated material configuration. + """ dup = self.copy() for i,m in enumerate(dup['material']): @@ -234,24 +238,27 @@ class ConfigMaterial(Config): return dup - def material_add(self,constituents=None,**kwargs): + def material_add(self,**kwargs): """ Add material entries. Parameters ---------- - constituents : dict, optional - Entries for 'constituents' as key-value pair. **kwargs Key-value pairs. + Returns + ------- + cfg : damask.ConfigMaterial + Updated material configuration. + Examples -------- + >>> import numpy as np >>> import damask - >>> O = damask.Rotation.from_random(3) - >>> phase = ['Aluminum','Steel','Aluminum'] - >>> m = damask.ConfigMaterial().material_add(constituents={'phase':phase,'O':O}, - ... homogenization='SX') + >>> m = damask.ConfigMaterial().material_add(phase = ['Aluminum','Steel'], + ... O = damask.Rotation.from_random(2), + ... homogenization = 'SX') >>> m material: - constituents: @@ -264,63 +271,59 @@ class ConfigMaterial(Config): v: 1.0 phase: Steel homogenization: SX + homogenization: {} + phase: {} + + >>> m = damask.ConfigMaterial().material_add(phase = np.array(['Austenite','Martensite']).reshape(1,2), + ... O = damask.Rotation.from_random((2,2)), + ... v = np.array([0.2,0.8]).reshape(1,2), + ... homogenization = ['A','B']) + >>> m + material: - constituents: - - O: [0.0886257, -0.144848, 0.615674, -0.769487] - v: 1.0 - phase: Aluminum - homogenization: SX + - phase: Austenite + O: [0.659802978293224, 0.6953785848195171, 0.22426295326327111, -0.17554139512785227] + v: 0.2 + - phase: Martensite + O: [0.49356745891301596, 0.2841806579193434, -0.7487679215072818, -0.339085707289975] + v: 0.8 + homogenization: A + - constituents: + - phase: Austenite + O: [0.26542221365204055, 0.7268854930702071, 0.4474726435701472, -0.44828201137283735] + v: 0.2 + - phase: Martensite + O: [0.6545817158479885, -0.08004812803625233, -0.6226561293931374, 0.4212059104577611] + v: 0.8 + homogenization: B homogenization: {} phase: {} """ - length = -1 - for v in kwargs.values(): - if hasattr(v,'__len__') and not isinstance(v,str): - if length != -1 and len(v) != length: - raise ValueError('Cannot add entries of different length') - else: - length = len(v) - length = max(1,length) - - c = [{} for _ in range(length)] if constituents is None else \ - [{'constituents':u} for u in ConfigMaterial._constituents(**constituents)] - - if len(c) == 1: c = [c[0] for _ in range(length)] - - if length != 1 and length != len(c): - raise ValueError('Cannot add entries of different length') + N,n,shaped = 1,1,{} for k,v in kwargs.items(): - if hasattr(v,'__len__') and not isinstance(v,str): - for i,vv in enumerate(v): - c[i][k] = vv.item() if isinstance(vv,np.generic) else vv - else: - for i in range(len(c)): - c[i][k] = v + shaped[k] = np.array(v) + s = shaped[k].shape[:-1] if k=='O' else shaped[k].shape + N = max(N,s[0]) if len(s)>0 else N + n = max(n,s[1]) if len(s)>1 else n + + mat = [{'constituents':[{} for _ in range(n)]} for _ in range(N)] + + if 'v' not in kwargs: + shaped['v'] = np.broadcast_to(1/n,(N,n)) + + for k,v in shaped.items(): + target = (N,n,4) if k=='O' else (N,n) + 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']: + 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'] + c if 'material' in dup else c + dup['material'] = dup['material'] + mat if 'material' in dup else mat return dup - - - @staticmethod - def _constituents(N=1,**kwargs): - """Construct list of constituents.""" - N_material=1 - for v in kwargs.values(): - if hasattr(v,'__len__') and not isinstance(v,str): N_material = len(v) - - if N == 1: - m = [[{'v':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: - raise ValueError('Cannot add entries of different length') - for i,vv in enumerate(np.array(v)): - m[i][0][k] = vv.item() if isinstance(vv,np.generic) else vv - else: - for i in range(N_material): - m[i][0][k] = v - return m - else: - raise NotImplementedError diff --git a/python/damask/_grid.py b/python/damask/_grid.py index 8125fb045..a58f2ca2c 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -122,7 +122,7 @@ class Grid: @size.setter def size(self,size): - if len(size) != 3 or any(np.array(size) <= 0): + if len(size) != 3 or any(np.array(size) < 0): raise ValueError(f'invalid size {size}') else: self._size = np.array(size) @@ -303,7 +303,7 @@ class Grid: Need to be ordered (1./x fast, 3./z slow). labels : str or list of str Label(s) of the columns containing the material definition. - Each unique combintation of values results in one material ID. + Each unique combination of values results in one material ID. """ cells,size,origin = grid_filters.cellsSizeOrigin_coordinates0_point(table.get(coordinates)) diff --git a/python/tests/test_ConfigMaterial.py b/python/tests/test_ConfigMaterial.py index 4ad0d31ca..6c316aeaa 100644 --- a/python/tests/test_ConfigMaterial.py +++ b/python/tests/test_ConfigMaterial.py @@ -5,6 +5,7 @@ import numpy as np from damask import ConfigMaterial from damask import Table +from damask import Rotation @pytest.fixture def ref_path(ref_path_base): @@ -85,42 +86,26 @@ 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))).T - t = Table(a,{'varying':2,'constant':2}) - c = ConfigMaterial.from_table(t,constituents={'a':'varying','b':'1_constant'},c='2_constant') + 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))).T + print(a) + t = Table(a,{'varying':1,'constant':4}) + c = ConfigMaterial.from_table(t,**{'phase':'varying','O':'constant','homogenization':'4_constant'}) assert len(c['material']) == N for i,m in enumerate(c['material']): - c = m['constituents'][0] - assert m['c'] == 1 and c['b'] == 0 and (c['a'] == [i,1]).all() + assert m['homogenization'] == 1 and (m['constituents'][0]['O'] == [1,0,1,1]).all() - def test_constituents(self): - c = ConfigMaterial._constituents(c=1,v=[2,3]) - assert c[0][0]['c'] == c[1][0]['c'] == 1 - assert c[0][0]['v'] == c[1][0]['v'] -1 ==2 - - @pytest.mark.parametrize('constituents',[{'W':1,'X':[2,3]},{'Y':4},{'Z':[5,6]}]) - @pytest.mark.parametrize('a',[[7.,8.],9.]) - @pytest.mark.parametrize('b',['bd',['efg','hi']]) - def test_material_add(self,tmp_path,constituents,a,b): - len_c = len(ConfigMaterial()._constituents(1,**constituents)) - len_a = len(a) if isinstance(a,list) else 1 - len_b = len(b) if isinstance(b,list) else 1 - m = ConfigMaterial().material_add(constituents,a=a,b=b) - m.save() - assert len(m['material']) == np.max([len_a,len_b,len_c]) - - @pytest.mark.parametrize('constituents',[{'W':1,'X':np.array([2,3])},{'Y':4},{'Z':np.array([5,6])}]) - @pytest.mark.parametrize('a',[np.array([7,8]),9]) - def test_material_add_np(self,tmp_path,constituents,a): - len_c = len(ConfigMaterial()._constituents(1,**constituents)) - len_a = len(a) if isinstance(a,np.ndarray) else 1 - m = ConfigMaterial().material_add(constituents,ld=a) - m.save() - assert len(m['material']) == np.max([len_a,len_c]) - - @pytest.mark.parametrize('constituents',[{'X':np.array([2,3,4,5])},{'Y':4}]) - @pytest.mark.parametrize('a',[np.array([1,2,3]),[4,5,6]]) - @pytest.mark.parametrize('b',[np.array([6.,7.]),[8.,9.]]) - def test_material_add_invalid(self,constituents,a,b): - with pytest.raises(ValueError): - ConfigMaterial().material_add(constituents,a=a,u=b) + @pytest.mark.parametrize('N,n,kw',[ + (1,1,{'phase':'Gold', + 'O':[1,0,0,0], + 'homogenization':'SX'}), + (3,1,{'phase':'Gold', + 'O':Rotation.from_random(3), + 'homogenization':'SX'}), + (2,3,{'phase':np.broadcast_to(['a','b','c'],(2,3)), + 'O':Rotation.from_random((2,3)), + 'homogenization':['SX','PX']}), + ]) + def test_material_add(self,kw,N,n): + m = ConfigMaterial().material_add(**kw) + assert len(m['material']) == N + assert len(m['material'][0]['constituents']) == n