From 0604e510ec46a36cb70e7c48664758f1da77ae57 Mon Sep 17 00:00:00 2001 From: Test User Date: Fri, 15 Sep 2023 22:52:14 +0200 Subject: [PATCH 01/22] [skip ci] updated version information after successful test of v3.0.0-alpha7-812-g96699f994 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7fa9afb07..821be3454 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-alpha7-807-g104efaed5 +3.0.0-alpha7-812-g96699f994 From fbeb5a15f05ead2f5c3b45facb5809c591b86048 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Fri, 15 Sep 2023 21:53:42 -0400 Subject: [PATCH 02/22] include updated conftest.py in PRIVATE --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index ed4e161d4..3c511e078 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit ed4e161d4302852a31c59a1d1d9b11d49f1a5427 +Subproject commit 3c511e078243d8652506039549c3234572e0b3bb From e4fee4aba4735de095f5cb22f964fef4dcbdadff Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Sat, 16 Sep 2023 14:30:24 -0400 Subject: [PATCH 03/22] updated PRIVATE --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 3c511e078..8811a8c33 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 3c511e078243d8652506039549c3234572e0b3bb +Subproject commit 8811a8c331030db5bc55336a84d4874fb449d09e From 4d34c407337d371fb9983c95a960413b1e27aff4 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Sat, 16 Sep 2023 15:04:05 -0400 Subject: [PATCH 04/22] use correct plastic_detect_changes test --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 8811a8c33..dba037dec 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 8811a8c331030db5bc55336a84d4874fb449d09e +Subproject commit dba037dec5be4f5b616bb021c4cca9f1c0b96e84 From 4b6f66b254e16797536aa9732ea85a6f9a12b3a1 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Tue, 19 Sep 2023 09:17:06 -0400 Subject: [PATCH 05/22] new PRIVATE content --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index dba037dec..60984d0da 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit dba037dec5be4f5b616bb021c4cca9f1c0b96e84 +Subproject commit 60984d0da1215c61d2c1a76f75980cd0dd634b30 From b3f98ab87762f195384ad91f6677ae009a830f7d Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Tue, 19 Sep 2023 17:43:27 -0400 Subject: [PATCH 06/22] more flexible shapeblending --- python/damask/util.py | 29 +++++++++++++++++++++++++---- python/tests/test_util.py | 24 ++++++++++++++---------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/python/damask/util.py b/python/damask/util.py index d61ca221b..e4b7ddcb5 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -512,7 +512,8 @@ def shapeshifter(fro: _Tuple[int, ...], return tuple(final_shape[::-1] if mode == 'left' else final_shape) def shapeblender(a: _Tuple[int, ...], - b: _Tuple[int, ...]) -> _Tuple[int, ...]: + b: _Tuple[int, ...], + keep_ones: bool = True) -> _Tuple[int, ...]: """ Return a shape that overlaps the rightmost entries of 'a' with the leftmost of 'b'. @@ -522,6 +523,9 @@ def shapeblender(a: _Tuple[int, ...], Shape of first array. b : tuple Shape of second array. + keep_ones : bool, optional + Treat innermost '1's as literal value instead of dimensional placeholder. + Defaults to True. Examples -------- @@ -531,13 +535,30 @@ def shapeblender(a: _Tuple[int, ...], (1,2,3) >>> shapeblender((1,),(2,2,1)) (1,2,2,1) + >>> shapeblender((1,),(2,2,1),False) + (2,2,1) >>> shapeblender((3,2),(3,2)) (3,2) """ - i = min(len(a),len(b)) - while i > 0 and a[-i:] != b[:i]: i -= 1 - return a + b[i:] + def is_broadcastable(a,b): + try: + _np.broadcast_shapes(a,b) + return True + except ValueError: + return False + + a_,_b = a,b + if keep_ones: + i = min(len(a_),len(_b)) + while i > 0 and a_[-i:] != _b[:i]: i -= 1 + return a_ + _b[i:] + else: + a_ += max(0,len(_b)-len(a_))*(1,) + while not is_broadcastable(a_,_b): + a_ = a_ + ((1,) if len(a_)<=len(_b) else ()) + _b = ((1,) if len(_b) Date: Tue, 19 Sep 2023 17:50:28 -0400 Subject: [PATCH 07/22] result (and object interpretation) "flows" from rotation and data shapes --- python/damask/_rotation.py | 48 ++++++++++++++++++++--------------- python/tests/test_Rotation.py | 2 +- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index cd3164db4..3353cce6d 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -375,6 +375,11 @@ class Rotation: Return self@other. Rotate vector, second-order tensor, or fourth-order tensor. + `other` is interpreted as an array of tensor quantities with the highest-possible order + considering the shape of `self`. + For instance, shapes of (2,) and (3,3) for `self` and `other` prompt interpretation of + `other` as a second-rank tensor and result in (2,) rotated tensors, whereas + shapes of (2,1) and (3,3) for `self` and `other` result in (2,3) rotated vectors. Parameters ---------- @@ -388,27 +393,30 @@ class Rotation: """ if isinstance(other, np.ndarray): - if self.shape + (3,) == other.shape: - q_m = self.quaternion[...,0] - p_m = self.quaternion[...,1:] - A = q_m**2 - np.einsum('...i,...i',p_m,p_m) - B = 2. * np.einsum('...i,...i',p_m,other) - C = 2. * _P * q_m - return np.block([(A * other[...,i]).reshape(self.shape+(1,)) + - (B * p_m[...,i]).reshape(self.shape+(1,)) + - (C * ( p_m[...,(i+1)%3]*other[...,(i+2)%3]\ - - p_m[...,(i+2)%3]*other[...,(i+1)%3])).reshape(self.shape+(1,)) - for i in [0,1,2]]) - if self.shape + (3,3) == other.shape: - R = self.as_matrix() - return np.einsum('...im,...jn,...mn',R,R,other) - if self.shape + (3,3,3,3) == other.shape: - R = self.as_matrix() - return np.einsum('...im,...jn,...ko,...lp,...mnop',R,R,R,R,other) - else: - raise ValueError('can only rotate vectors, second-order tensors, and fourth-order tensors') + obs = util.shapeblender(self.shape,other.shape,keep_ones=False)[len(self.shape):] + for l in [4,2,1]: + if obs[-l:] == l*(3,): + bs = util.shapeblender(self.shape,other.shape[:-l],False) + self_ = self.broadcast_to(bs) if self.shape != bs else self + if l==1: + q_m = self_.quaternion[...,0] + p_m = self_.quaternion[...,1:] + A = q_m**2 - np.einsum('...i,...i',p_m,p_m) + B = 2. * np.einsum('...i,...i',p_m,other) + C = 2. * _P * q_m + return np.block([(A * other[...,i]) + + (B * p_m[...,i]) + + (C * ( p_m[...,(i+1)%3]*other[...,(i+2)%3] + - p_m[...,(i+2)%3]*other[...,(i+1)%3])) + for i in [0,1,2]]).reshape(bs+(3,),order='F') + else: + return np.einsum({2: '...im,...jn,...mn', + 4: '...im,...jn,...ko,...lp,...mnop'}[l], + *l*[self_.as_matrix()], + other) + raise ValueError('can only rotate vectors, second-order tensors, and fourth-order tensors') elif isinstance(other, Rotation): - raise TypeError('use "R1*R2", i.e. multiplication, to compose rotations "R1" and "R2"') + raise TypeError('use "R2*R1", i.e. multiplication, to compose rotations "R1" and "R2"') else: raise TypeError(f'cannot rotate "{type(other)}"') diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 6b9cd2b1a..5a2d91a6e 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -1065,7 +1065,7 @@ class TestRotation: @pytest.mark.parametrize('data',[np.random.rand(4), np.random.rand(3,2), - np.random.rand(3,2,3,3)]) + np.random.rand(3,3,3,1)]) def test_rotate_invalid_shape(self,data): R = Rotation.from_random() with pytest.raises(ValueError): From 4c4c1fbcc8af13d8b730ce9a7937a7c4c3d77bd2 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Tue, 19 Sep 2023 18:03:55 -0400 Subject: [PATCH 08/22] small polish --- python/damask/_orientation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index cf6684458..15f8464fd 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -804,7 +804,7 @@ class Orientation(Rotation,Crystal): blend += sym_ops.shape v = sym_ops.broadcast_to(shape) \ @ np.broadcast_to(v.reshape(util.shapeshifter(v.shape,shape+(3,))),shape+(3,)) - return ~(self.broadcast_to(blend))@ np.broadcast_to(v,blend+(3,)) + return ~(self.broadcast_to(blend))@np.broadcast_to(v,blend+(3,)) def Schmid(self, *, @@ -833,7 +833,7 @@ class Orientation(Rotation,Crystal): >>> import damask >>> np.set_printoptions(3,suppress=True,floatmode='fixed') >>> O = damask.Orientation.from_Euler_angles(phi=[0,45,0],degrees=True,lattice='cF') - >>> O.Schmid(N_slip=[1]) + >>> O.Schmid(N_slip=[12])[0] array([[ 0.000, 0.000, 0.000], [ 0.577, -0.000, 0.816], [ 0.000, 0.000, 0.000]]) From 108ef2a954ecb99335ee4b85a1a3fc2127c00d5c Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Tue, 19 Sep 2023 18:05:12 -0400 Subject: [PATCH 09/22] reflect updated shape blending --- python/tests/test_Orientation.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 8346397cc..9836ee16e 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -162,7 +162,7 @@ class TestOrientation: ([np.arccos(3**(-.5)),np.pi/4,0],[0,0],[0,0,1],[0,0,1])]) def test_fiber_IPF(self,crystal,sample,direction,color): fiber = Orientation.from_fiber_component(crystal=crystal,sample=sample,family='cubic',shape=200) - print(np.allclose(fiber.IPF_color(direction),color)) + assert np.allclose(fiber.IPF_color(direction),color) @pytest.mark.parametrize('kwargs',[ @@ -455,11 +455,9 @@ class TestOrientation: p = Orientation.from_random(family=family,shape=right) blend = util.shapeblender(o.shape,p.shape) for loc in np.random.randint(0,blend,(10,len(blend))): - # print(f'{a}/{b} @ {loc}') - # print(o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])])) - # print(o.disorientation(p)[tuple(loc)]) - assert o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])]) \ - .isclose(o.disorientation(p)[tuple(loc)]) + l = () if left is None else tuple(np.minimum(np.array(left )-1,loc[:len(left)])) + r = () if right is None else tuple(np.minimum(np.array(right)-1,loc[-len(right):])) + assert o[l].disorientation(p[r]).isclose(o.disorientation(p)[tuple(loc)]) @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('left,right',[ @@ -467,13 +465,16 @@ class TestOrientation: ((2,2),(4,4)), ((3,1),(1,3)), (None,(3,)), + (None,()), ]) def test_IPF_color_blending(self,family,left,right): o = Orientation.from_random(family=family,shape=left) v = np.random.random(right+(3,)) blend = util.shapeblender(o.shape,v.shape[:-1]) for loc in np.random.randint(0,blend,(10,len(blend))): - assert np.allclose(o[tuple(loc[:len(o.shape)])].IPF_color(v[tuple(loc[-len(v.shape[:-1]):])]), + l = () if left is None else tuple(np.minimum(np.array(left )-1,loc[:len(left)])) + r = () if right is None else tuple(np.minimum(np.array(right)-1,loc[-len(right):])) + assert np.allclose(o[l].IPF_color(v[r]), o.IPF_color(v)[tuple(loc)]) @pytest.mark.parametrize('family',crystal_families) @@ -488,7 +489,9 @@ class TestOrientation: v = np.random.random(right+(3,)) blend = util.shapeblender(o.shape,v.shape[:-1]) for loc in np.random.randint(0,blend,(10,len(blend))): - assert np.allclose(o[tuple(loc[:len(o.shape)])].to_SST(v[tuple(loc[-len(v.shape[:-1]):])]), + l = () if left is None else tuple(np.minimum(np.array(left )-1,loc[:len(left)])) + r = () if right is None else tuple(np.minimum(np.array(right)-1,loc[-len(right):])) + assert np.allclose(o[l].to_SST(v[r]), o.to_SST(v)[tuple(loc)]) @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', @@ -514,8 +517,10 @@ class TestOrientation: v = np.random.random(right+(3,)) blend = util.shapeblender(o.shape,v.shape[:-1]) for loc in np.random.randint(0,blend,(10,len(blend))): - assert np.allclose(o[tuple(loc[:len(o.shape)])].to_pole(uvw=v[tuple(loc[-len(v.shape[:-1]):])]), - o.to_pole(uvw=v)[tuple(loc)]) + l = () if left is None else tuple(np.minimum(np.array(left )-1,loc[:len(left)])) + r = () if right is None else tuple(np.minimum(np.array(right)-1,loc[-len(right):])) + assert np.allclose(o[l].to_pole(uvw=v[r]), + o.to_pole(uvw=v)[tuple(loc)]) def test_mul_invalid(self): with pytest.raises(TypeError): From 177da6e2cbcf9e3656ec628147d00b9ee958d1f8 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Thu, 21 Sep 2023 08:15:56 -0400 Subject: [PATCH 10/22] use clean PRIVATE --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 60984d0da..b752a1e5d 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 60984d0da1215c61d2c1a76f75980cd0dd634b30 +Subproject commit b752a1e5d3a10a579329b386ed23d961fe2d35cb From 23c6270d4aef59c23347ff6a1a059b1be933ad06 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Thu, 21 Sep 2023 17:27:38 -0400 Subject: [PATCH 11/22] added examples --- python/damask/_rotation.py | 43 +++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 3353cce6d..c03bff4b2 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -376,7 +376,7 @@ class Rotation: Rotate vector, second-order tensor, or fourth-order tensor. `other` is interpreted as an array of tensor quantities with the highest-possible order - considering the shape of `self`. + considering the shape of `self`. Compatible innermost dimensions will blend. For instance, shapes of (2,) and (3,3) for `self` and `other` prompt interpretation of `other` as a second-rank tensor and result in (2,) rotated tensors, whereas shapes of (2,1) and (3,3) for `self` and `other` result in (2,3) rotated vectors. @@ -391,6 +391,47 @@ class Rotation: rotated : numpy.ndarray, shape (...,3), (...,3,3), or (...,3,3,3,3) Rotated vector or tensor, i.e. transformed to frame defined by rotation. + Examples + -------- + All below examples rely on imported modules: + >>> import numpy as np + >>> import damask + + Application of twelve (random) rotations to a set of five vectors. + + >>> r = damask.Rotation.from_random(shape=(12)) + >>> o = np.ones((5,3)) + >>> (r@o).shape # (12) @ (5, 3) + (12,5, 3) + + Application of a (random) rotation to all twelve second-rank tensors. + + >>> r = damask.Rotation.from_random() + >>> o = np.ones((12,3,3)) + >>> (r@o).shape # (1) @ (12, 3,3) + (12,3,3) + + Application of twelve (random) rotations to the corresponding twelve second-rank tensors. + + >>> r = damask.Rotation.from_random(shape=(12)) + >>> o = np.ones((12,3,3)) + >>> (r@o).shape # (12) @ (3,3) + (12,3,3) + + Application of each of three (random) rotations to all three vectors. + + >>> r = damask.Rotation.from_random(shape=(3)) + >>> o = np.ones((3,3)) + >>> (r[...,np.newaxis]@o[np.newaxis,...]).shape # (3,1) @ (1,3, 3) + (3,3,3) + + Application of twelve (random) rotations to all twelve second-rank tensors. + + >>> r = damask.Rotation.from_random(shape=(12)) + >>> o = np.ones((12,3,3)) + >>> (r@o[np.newaxis,...]).shape # (12) @ (1,12, 3,3) + (12,3,3,3) + """ if isinstance(other, np.ndarray): obs = util.shapeblender(self.shape,other.shape,keep_ones=False)[len(self.shape):] From b0bb904c89e0307f71c3624014917a728b1d8ea1 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 21 Sep 2023 21:24:45 +0200 Subject: [PATCH 12/22] no multiprocessing for adding datasets multiprocessing was not reliable on different platform and caused all kinds of weird behavior --- python/damask/_result.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index 5d04a79b7..ce2cb8896 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -1,5 +1,3 @@ -import multiprocessing as mp -from multiprocessing.synchronize import Lock import re import fnmatch import os @@ -1450,12 +1448,10 @@ class Result: group: str, callback: Callable, datasets: Dict[str, str], - args: Dict[str, str], - lock: Lock) -> List[Union[None, Any]]: + args: Dict[str, str]) -> List[Union[None, Any]]: """Execute job for _add_generic_pointwise.""" try: datasets_in = {} - lock.acquire() with h5py.File(self.fname,'r') as f: for arg,label in datasets.items(): loc = f[group+'/'+label] @@ -1463,7 +1459,6 @@ class Result: 'label':label, 'meta': {k:(v.decode() if not h5py3 and type(v) is bytes else v) \ for k,v in loc.attrs.items()}} - lock.release() r = callback(**datasets_in,**args) return [group,r] except Exception as err: @@ -1490,9 +1485,6 @@ class Result: Arguments parsed to func. """ - pool = mp.Pool(int(os.environ.get('OMP_NUM_THREADS',4))) - lock = mp.Manager().Lock() - groups = [] with h5py.File(self.fname,'r') as f: for inc in self.visible['increments']: @@ -1506,12 +1498,12 @@ class Result: print('No matching dataset found, no data was added.') return - default_arg = partial(self._job_pointwise,callback=func,datasets=datasets,args=args,lock=lock) + default_arg = partial(self._job_pointwise,callback=func,datasets=datasets,args=args) - for group,result in util.show_progress(pool.imap_unordered(default_arg,groups),len(groups)):# type: ignore + for grp in util.show_progress(groups): + group, result = default_arg(grp) # type: ignore if not result: continue - lock.acquire() with h5py.File(self.fname, 'a') as f: try: if not self._protected and '/'.join([group,result['label']]) in f: @@ -1543,10 +1535,6 @@ class Result: except (OSError,RuntimeError) as err: print(f'Could not add dataset: {err}.') - lock.release() - - pool.close() - pool.join() def _mappings(self): From 36c13d2e58cdd47dc4ce5b671d7769177cd4d998 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 21 Sep 2023 23:54:53 +0200 Subject: [PATCH 13/22] no need to have this as class method definition as class method was needed to be visible in multiprocessing --- python/damask/_result.py | 681 ++++++++++++++++++++------------------- 1 file changed, 343 insertions(+), 338 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index ce2cb8896..9cef7093d 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -599,17 +599,6 @@ class Result: f['/geometry/T_c'].attrs['VTK_TYPE'].decode()) - @staticmethod - def _add_absolute(x: Dict[str, Any]) -> Dict[str, Any]: - return { - 'data': np.abs(x['data']), - 'label': f'|{x["label"]}|', - 'meta': { - 'unit': x['meta']['unit'], - 'description': f"absolute value of {x['label']} ({x['meta']['description']})", - 'creator': 'add_absolute' - } - } def add_absolute(self, x: str): """ Add absolute value. @@ -620,28 +609,20 @@ class Result: Name of scalar, vector, or tensor dataset to take absolute value of. """ - self._add_generic_pointwise(self._add_absolute,{'x':x}) + def absolute(x: Dict[str, Any]) -> Dict[str, Any]: + return { + 'data': np.abs(x['data']), + 'label': f'|{x["label"]}|', + 'meta': { + 'unit': x['meta']['unit'], + 'description': f"absolute value of {x['label']} ({x['meta']['description']})", + 'creator': 'add_absolute' + } + } + + self._add_generic_pointwise(absolute,{'x':x}) - @staticmethod - def _add_calculation(**kwargs) -> Dict[str, Any]: - formula = kwargs['formula'] - for d in re.findall(r'#(.*?)#',formula): - formula = formula.replace(f'#{d}#',f"kwargs['{d}']['data']") - data = eval(formula) - - if not hasattr(data,'shape') or data.shape[0] != kwargs[d]['data'].shape[0]: - raise ValueError('"{}" results in invalid shape'.format(kwargs['formula'])) - - return { - 'data': data, - 'label': kwargs['label'], - 'meta': { - 'unit': kwargs['unit'], - 'description': f"{kwargs['description']} (formula: {kwargs['formula']})", - 'creator': 'add_calculation' - } - } def add_calculation(self, formula: str, name: str, @@ -690,24 +671,30 @@ class Result: ... 'Mises equivalent of the Cauchy stress') """ + def calculation(**kwargs) -> Dict[str, Any]: + formula = kwargs['formula'] + for d in re.findall(r'#(.*?)#',formula): + formula = formula.replace(f'#{d}#',f"kwargs['{d}']['data']") + data = eval(formula) + + if not hasattr(data,'shape') or data.shape[0] != kwargs[d]['data'].shape[0]: + raise ValueError('"{}" results in invalid shape'.format(kwargs['formula'])) + + return { + 'data': data, + 'label': kwargs['label'], + 'meta': { + 'unit': kwargs['unit'], + 'description': f"{kwargs['description']} (formula: {kwargs['formula']})", + 'creator': 'add_calculation' + } + } + dataset_mapping = {d:d for d in set(re.findall(r'#(.*?)#',formula))} # datasets used in the formula args = {'formula':formula,'label':name,'unit':unit,'description':description} - self._add_generic_pointwise(self._add_calculation,dataset_mapping,args) + self._add_generic_pointwise(calculation,dataset_mapping,args) - @staticmethod - def _add_stress_Cauchy(P: Dict[str, Any], F: Dict[str, Any]) -> Dict[str, Any]: - return { - 'data': mechanics.stress_Cauchy(P['data'],F['data']), - 'label': 'sigma', - 'meta': { - 'unit': P['meta']['unit'], - 'description': "Cauchy stress calculated " - f"from {P['label']} ({P['meta']['description']})" - f" and {F['label']} ({F['meta']['description']})", - 'creator': 'add_stress_Cauchy' - } - } def add_stress_Cauchy(self, P: str = 'P', F: str = 'F'): @@ -724,20 +711,23 @@ class Result: Defaults to 'F'. """ - self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F}) + + def stress_Cauchy(P: Dict[str, Any], F: Dict[str, Any]) -> Dict[str, Any]: + return { + 'data': mechanics.stress_Cauchy(P['data'],F['data']), + 'label': 'sigma', + 'meta': { + 'unit': P['meta']['unit'], + 'description': "Cauchy stress calculated " + f"from {P['label']} ({P['meta']['description']})" + f" and {F['label']} ({F['meta']['description']})", + 'creator': 'add_stress_Cauchy' + } + } + + self._add_generic_pointwise(stress_Cauchy,{'P':P,'F':F}) - @staticmethod - def _add_determinant(T: Dict[str, Any]) -> Dict[str, Any]: - return { - 'data': np.linalg.det(T['data']), - 'label': f"det({T['label']})", - 'meta': { - 'unit': T['meta']['unit'], - 'description': f"determinant of tensor {T['label']} ({T['meta']['description']})", - 'creator': 'add_determinant' - } - } def add_determinant(self, T: str): """ Add the determinant of a tensor. @@ -756,20 +746,21 @@ class Result: >>> r.add_determinant('F_p') """ - self._add_generic_pointwise(self._add_determinant,{'T':T}) + + def determinant(T: Dict[str, Any]) -> Dict[str, Any]: + return { + 'data': np.linalg.det(T['data']), + 'label': f"det({T['label']})", + 'meta': { + 'unit': T['meta']['unit'], + 'description': f"determinant of tensor {T['label']} ({T['meta']['description']})", + 'creator': 'add_determinant' + } + } + + self._add_generic_pointwise(determinant,{'T':T}) - @staticmethod - def _add_deviator(T: Dict[str, Any]) -> Dict[str, Any]: - return { - 'data': tensor.deviatoric(T['data']), - 'label': f"s_{T['label']}", - 'meta': { - 'unit': T['meta']['unit'], - 'description': f"deviator of tensor {T['label']} ({T['meta']['description']})", - 'creator': 'add_deviator' - } - } def add_deviator(self, T: str): """ Add the deviatoric part of a tensor. @@ -788,29 +779,21 @@ class Result: >>> r.add_deviator('sigma') """ - self._add_generic_pointwise(self._add_deviator,{'T':T}) + + def deviator(T: Dict[str, Any]) -> Dict[str, Any]: + return { + 'data': tensor.deviatoric(T['data']), + 'label': f"s_{T['label']}", + 'meta': { + 'unit': T['meta']['unit'], + 'description': f"deviator of tensor {T['label']} ({T['meta']['description']})", + 'creator': 'add_deviator' + } + } + + self._add_generic_pointwise(deviator,{'T':T}) - @staticmethod - def _add_eigenvalue(T_sym: Dict[str, Any], eigenvalue: Literal['max, mid, min']) -> Dict[str, Any]: - if eigenvalue == 'max': - label,p = 'maximum',2 - elif eigenvalue == 'mid': - label,p = 'intermediate',1 - elif eigenvalue == 'min': - label,p = 'minimum',0 - else: - raise ValueError(f'invalid eigenvalue: {eigenvalue}') - - return { - 'data': tensor.eigenvalues(T_sym['data'])[:,p], - 'label': f"lambda_{eigenvalue}({T_sym['label']})", - 'meta' : { - 'unit': T_sym['meta']['unit'], - 'description': f"{label} eigenvalue of {T_sym['label']} ({T_sym['meta']['description']})", - 'creator': 'add_eigenvalue' - } - } def add_eigenvalue(self, T_sym: str, eigenvalue: Literal['max', 'mid', 'min'] = 'max'): @@ -833,30 +816,30 @@ class Result: >>> r.add_eigenvalue('sigma','min') """ - self._add_generic_pointwise(self._add_eigenvalue,{'T_sym':T_sym},{'eigenvalue':eigenvalue}) + + def eigenval(T_sym: Dict[str, Any], eigenvalue: Literal['max, mid, min']) -> Dict[str, Any]: + if eigenvalue == 'max': + label,p = 'maximum',2 + elif eigenvalue == 'mid': + label,p = 'intermediate',1 + elif eigenvalue == 'min': + label,p = 'minimum',0 + else: + raise ValueError(f'invalid eigenvalue: {eigenvalue}') + + return { + 'data': tensor.eigenvalues(T_sym['data'])[:,p], + 'label': f"lambda_{eigenvalue}({T_sym['label']})", + 'meta' : { + 'unit': T_sym['meta']['unit'], + 'description': f"{label} eigenvalue of {T_sym['label']} ({T_sym['meta']['description']})", + 'creator': 'add_eigenvalue' + } + } + + self._add_generic_pointwise(eigenval,{'T_sym':T_sym},{'eigenvalue':eigenvalue}) - @staticmethod - def _add_eigenvector(T_sym: Dict[str, Any], eigenvalue: Literal['max', 'mid', 'min']) -> Dict[str, Any]: - if eigenvalue == 'max': - label,p = 'maximum',2 - elif eigenvalue == 'mid': - label,p = 'intermediate',1 - elif eigenvalue == 'min': - label,p = 'minimum',0 - else: - raise ValueError(f'invalid eigenvalue: {eigenvalue}') - - return { - 'data': tensor.eigenvectors(T_sym['data'])[:,p], - 'label': f"v_{eigenvalue}({T_sym['label']})", - 'meta' : { - 'unit': '1', - 'description': f"eigenvector corresponding to {label} eigenvalue" - f" of {T_sym['label']} ({T_sym['meta']['description']})", - 'creator': 'add_eigenvector' - } - } def add_eigenvector(self, T_sym: str, eigenvalue: Literal['max', 'mid', 'min'] = 'max'): @@ -872,25 +855,31 @@ class Result: Defaults to 'max'. """ - self._add_generic_pointwise(self._add_eigenvector,{'T_sym':T_sym},{'eigenvalue':eigenvalue}) + + def eigenvector(T_sym: Dict[str, Any], eigenvalue: Literal['max', 'mid', 'min']) -> Dict[str, Any]: + if eigenvalue == 'max': + label,p = 'maximum',2 + elif eigenvalue == 'mid': + label,p = 'intermediate',1 + elif eigenvalue == 'min': + label,p = 'minimum',0 + else: + raise ValueError(f'invalid eigenvalue: {eigenvalue}') + + return { + 'data': tensor.eigenvectors(T_sym['data'])[:,p], + 'label': f"v_{eigenvalue}({T_sym['label']})", + 'meta' : { + 'unit': '1', + 'description': f"eigenvector corresponding to {label} eigenvalue" + f" of {T_sym['label']} ({T_sym['meta']['description']})", + 'creator': 'add_eigenvector' + } + } + + self._add_generic_pointwise(eigenvector,{'T_sym':T_sym},{'eigenvalue':eigenvalue}) - @staticmethod - def _add_IPF_color(l: FloatSequence, q: Dict[str, Any]) -> Dict[str, Any]: - m = util.scale_to_coprime(np.array(l)) - lattice = q['meta']['lattice'] - o = Orientation(rotation = q['data'],lattice=lattice) - - return { - 'data': np.uint8(o.IPF_color(l)*255), - 'label': 'IPFcolor_({} {} {})'.format(*m), - 'meta' : { - 'unit': '8-bit RGB', - 'lattice': q['meta']['lattice'], - 'description': 'Inverse Pole Figure (IPF) colors along sample direction ({} {} {})'.format(*m), - 'creator': 'add_IPF_color' - } - } def add_IPF_color(self, l: FloatSequence, q: str = 'O'): @@ -914,20 +903,26 @@ class Result: >>> r.add_IPF_color(np.array([0,1,1])) """ - self._add_generic_pointwise(self._add_IPF_color,{'q':q},{'l':l}) + + def IPF_color(l: FloatSequence, q: Dict[str, Any]) -> Dict[str, Any]: + m = util.scale_to_coprime(np.array(l)) + lattice = q['meta']['lattice'] + o = Orientation(rotation = q['data'],lattice=lattice) + + return { + 'data': np.uint8(o.IPF_color(l)*255), + 'label': 'IPFcolor_({} {} {})'.format(*m), + 'meta' : { + 'unit': '8-bit RGB', + 'lattice': q['meta']['lattice'], + 'description': 'Inverse Pole Figure (IPF) colors along sample direction ({} {} {})'.format(*m), + 'creator': 'add_IPF_color' + } + } + + self._add_generic_pointwise(IPF_color,{'q':q},{'l':l}) - @staticmethod - def _add_maximum_shear(T_sym: Dict[str, Any]) -> Dict[str, Any]: - return { - 'data': mechanics.maximum_shear(T_sym['data']), - 'label': f"max_shear({T_sym['label']})", - 'meta': { - 'unit': T_sym['meta']['unit'], - 'description': f"maximum shear component of {T_sym['label']} ({T_sym['meta']['description']})", - 'creator': 'add_maximum_shear' - } - } def add_maximum_shear(self, T_sym: str): """ Add maximum shear components of symmetric tensor. @@ -938,30 +933,20 @@ class Result: Name of symmetric tensor dataset. """ - self._add_generic_pointwise(self._add_maximum_shear,{'T_sym':T_sym}) + def maximum_shear(T_sym: Dict[str, Any]) -> Dict[str, Any]: + return { + 'data': mechanics.maximum_shear(T_sym['data']), + 'label': f"max_shear({T_sym['label']})", + 'meta': { + 'unit': T_sym['meta']['unit'], + 'description': f"maximum shear component of {T_sym['label']} ({T_sym['meta']['description']})", + 'creator': 'add_maximum_shear' + } + } + + self._add_generic_pointwise(maximum_shear,{'T_sym':T_sym}) - @staticmethod - def _add_equivalent_Mises(T_sym: Dict[str, Any], kind: str) -> Dict[str, Any]: - k = kind - if k is None: - if T_sym['meta']['unit'] == '1': - k = 'strain' - elif T_sym['meta']['unit'] == 'Pa': - k = 'stress' - if k not in ['stress', 'strain']: - raise ValueError(f'invalid von Mises kind "{kind}"') - - return { - 'data': (mechanics.equivalent_strain_Mises if k=='strain' else \ - mechanics.equivalent_stress_Mises)(T_sym['data']), - 'label': f"{T_sym['label']}_vM", - 'meta': { - 'unit': T_sym['meta']['unit'], - 'description': f"Mises equivalent {k} of {T_sym['label']} ({T_sym['meta']['description']})", - 'creator': 'add_Mises' - } - } def add_equivalent_Mises(self, T_sym: str, kind: Optional[str] = None): @@ -991,32 +976,30 @@ class Result: >>> r.add_equivalent_Mises('epsilon_V^0.0(F)') """ - self._add_generic_pointwise(self._add_equivalent_Mises,{'T_sym':T_sym},{'kind':kind}) + def equivalent_Mises(T_sym: Dict[str, Any], kind: str) -> Dict[str, Any]: + k = kind + if k is None: + if T_sym['meta']['unit'] == '1': + k = 'strain' + elif T_sym['meta']['unit'] == 'Pa': + k = 'stress' + if k not in ['stress', 'strain']: + raise ValueError(f'invalid von Mises kind "{kind}"') + + return { + 'data': (mechanics.equivalent_strain_Mises if k=='strain' else \ + mechanics.equivalent_stress_Mises)(T_sym['data']), + 'label': f"{T_sym['label']}_vM", + 'meta': { + 'unit': T_sym['meta']['unit'], + 'description': f"Mises equivalent {k} of {T_sym['label']} ({T_sym['meta']['description']})", + 'creator': 'add_Mises' + } + } + + self._add_generic_pointwise(equivalent_Mises,{'T_sym':T_sym},{'kind':kind}) - @staticmethod - def _add_norm(x: Dict[str, Any], ord: Union[int, float, Literal['fro', 'nuc']]) -> Dict[str, Any]: - o = ord - if len(x['data'].shape) == 2: - axis: Union[int, Tuple[int, int]] = 1 - t = 'vector' - if o is None: o = 2 - elif len(x['data'].shape) == 3: - axis = (1,2) - t = 'tensor' - if o is None: o = 'fro' - else: - raise ValueError(f'invalid shape of {x["label"]}') - - return { - 'data': np.linalg.norm(x['data'],ord=o,axis=axis,keepdims=True), - 'label': f"|{x['label']}|_{o}", - 'meta': { - 'unit': x['meta']['unit'], - 'description': f"{o}-norm of {t} {x['label']} ({x['meta']['description']})", - 'creator': 'add_norm' - } - } def add_norm(self, x: str, ord: Union[None, int, float, Literal['fro', 'nuc']] = None): @@ -1031,22 +1014,32 @@ class Result: Order of the norm. inf means NumPy's inf object. For details refer to numpy.linalg.norm. """ - self._add_generic_pointwise(self._add_norm,{'x':x},{'ord':ord}) + def norm(x: Dict[str, Any], ord: Union[int, float, Literal['fro', 'nuc']]) -> Dict[str, Any]: + o = ord + if len(x['data'].shape) == 2: + axis: Union[int, Tuple[int, int]] = 1 + t = 'vector' + if o is None: o = 2 + elif len(x['data'].shape) == 3: + axis = (1,2) + t = 'tensor' + if o is None: o = 'fro' + else: + raise ValueError(f'invalid shape of {x["label"]}') + + return { + 'data': np.linalg.norm(x['data'],ord=o,axis=axis,keepdims=True), + 'label': f"|{x['label']}|_{o}", + 'meta': { + 'unit': x['meta']['unit'], + 'description': f"{o}-norm of {t} {x['label']} ({x['meta']['description']})", + 'creator': 'add_norm' + } + } + + self._add_generic_pointwise(norm,{'x':x},{'ord':ord}) - @staticmethod - def _add_stress_second_Piola_Kirchhoff(P: Dict[str, Any], F: Dict[str, Any]) -> Dict[str, Any]: - return { - 'data': mechanics.stress_second_Piola_Kirchhoff(P['data'],F['data']), - 'label': 'S', - 'meta': { - 'unit': P['meta']['unit'], - 'description': "second Piola-Kirchhoff stress calculated " - f"from {P['label']} ({P['meta']['description']})" - f" and {F['label']} ({F['meta']['description']})", - 'creator': 'add_stress_second_Piola_Kirchhoff' - } - } def add_stress_second_Piola_Kirchhoff(self, P: str = 'P', F: str = 'F'): @@ -1069,34 +1062,23 @@ class Result: is taken into account. """ - self._add_generic_pointwise(self._add_stress_second_Piola_Kirchhoff,{'P':P,'F':F}) + def stress_second_Piola_Kirchhoff(P: Dict[str, Any], F: Dict[str, Any]) -> Dict[str, Any]: + return { + 'data': mechanics.stress_second_Piola_Kirchhoff(P['data'],F['data']), + 'label': 'S', + 'meta': { + 'unit': P['meta']['unit'], + 'description': "second Piola-Kirchhoff stress calculated " + f"from {P['label']} ({P['meta']['description']})" + f" and {F['label']} ({F['meta']['description']})", + 'creator': 'add_stress_second_Piola_Kirchhoff' + } + } + + self._add_generic_pointwise(stress_second_Piola_Kirchhoff,{'P':P,'F':F}) - @staticmethod - def _add_pole(q: Dict[str, Any], - uvw: FloatSequence, - hkl: FloatSequence, - with_symmetry: bool, - normalize: bool) -> Dict[str, Any]: - c = q['meta']['c/a'] if 'c/a' in q['meta'] else 1 - brackets = ['[]','()','⟨⟩','{}'][(uvw is None)*1+with_symmetry*2] - label = 'p^' + '{}{} {} {}{}'.format(brackets[0], - *(uvw if uvw else hkl), - brackets[-1],) - ori = Orientation(q['data'],lattice=q['meta']['lattice'],a=1,c=c) - - return { - 'data': ori.to_pole(uvw=uvw,hkl=hkl,with_symmetry=with_symmetry,normalize=normalize), - 'label': label, - 'meta' : { - 'unit': '1', - 'description': f'{"normalized " if normalize else ""}lab frame vector along lattice ' \ - + ('direction' if uvw is not None else 'plane') \ - + ('s' if with_symmetry else ''), - 'creator': 'add_pole' - } - } def add_pole(self, q: str = 'O', *, @@ -1122,22 +1104,33 @@ class Result: Defaults to True. """ - self._add_generic_pointwise(self._add_pole, - {'q':q}, - {'uvw':uvw,'hkl':hkl,'with_symmetry':with_symmetry,'normalize':normalize}) + def pole(q: Dict[str, Any], + uvw: FloatSequence, + hkl: FloatSequence, + with_symmetry: bool, + normalize: bool) -> Dict[str, Any]: + c = q['meta']['c/a'] if 'c/a' in q['meta'] else 1 + brackets = ['[]','()','⟨⟩','{}'][(uvw is None)*1+with_symmetry*2] + label = 'p^' + '{}{} {} {}{}'.format(brackets[0], + *(uvw if uvw else hkl), + brackets[-1],) + ori = Orientation(q['data'],lattice=q['meta']['lattice'],a=1,c=c) + + return { + 'data': ori.to_pole(uvw=uvw,hkl=hkl,with_symmetry=with_symmetry,normalize=normalize), + 'label': label, + 'meta' : { + 'unit': '1', + 'description': f'{"normalized " if normalize else ""}lab frame vector along lattice ' \ + + ('direction' if uvw is not None else 'plane') \ + + ('s' if with_symmetry else ''), + 'creator': 'add_pole' + } + } + + self._add_generic_pointwise(pole,{'q':q},{'uvw':uvw,'hkl':hkl,'with_symmetry':with_symmetry,'normalize':normalize}) - @staticmethod - def _add_rotation(F: Dict[str, Any]) -> Dict[str, Any]: - return { - 'data': mechanics.rotation(F['data']).as_matrix(), - 'label': f"R({F['label']})", - 'meta': { - 'unit': F['meta']['unit'], - 'description': f"rotational part of {F['label']} ({F['meta']['description']})", - 'creator': 'add_rotation' - } - } def add_rotation(self, F: str): """ Add rotational part of a deformation gradient. @@ -1156,20 +1149,20 @@ class Result: >>> r.add_rotation('F') """ - self._add_generic_pointwise(self._add_rotation,{'F':F}) + def rotation(F: Dict[str, Any]) -> Dict[str, Any]: + return { + 'data': mechanics.rotation(F['data']).as_matrix(), + 'label': f"R({F['label']})", + 'meta': { + 'unit': F['meta']['unit'], + 'description': f"rotational part of {F['label']} ({F['meta']['description']})", + 'creator': 'add_rotation' + } + } + + self._add_generic_pointwise(rotation,{'F':F}) - @staticmethod - def _add_spherical(T: Dict[str, Any]) -> Dict[str, Any]: - return { - 'data': tensor.spherical(T['data'],False), - 'label': f"p_{T['label']}", - 'meta': { - 'unit': T['meta']['unit'], - 'description': f"spherical component of tensor {T['label']} ({T['meta']['description']})", - 'creator': 'add_spherical' - } - } def add_spherical(self, T: str): """ Add the spherical (hydrostatic) part of a tensor. @@ -1188,22 +1181,20 @@ class Result: >>> r.add_spherical('sigma') """ - self._add_generic_pointwise(self._add_spherical,{'T':T}) + def spherical(T: Dict[str, Any]) -> Dict[str, Any]: + return { + 'data': tensor.spherical(T['data'],False), + 'label': f"p_{T['label']}", + 'meta': { + 'unit': T['meta']['unit'], + 'description': f"spherical component of tensor {T['label']} ({T['meta']['description']})", + 'creator': 'add_spherical' + } + } + + self._add_generic_pointwise(spherical,{'T':T}) - @staticmethod - def _add_strain(F: Dict[str, Any], t: Literal['V', 'U'], m: float) -> Dict[str, Any]: - side = 'left' if t == 'V' else 'right' - return { - 'data': mechanics.strain(F['data'],t,m), - 'label': f"epsilon_{t}^{m}({F['label']})", - 'meta': { - 'unit': F['meta']['unit'], - 'description': f'Seth-Hill strain tensor of order {m} based on {side} stretch tensor '+\ - f"of {F['label']} ({F['meta']['description']})", - 'creator': 'add_strain' - } - } def add_strain(self, F: str = 'F', t: Literal['V', 'U'] = 'V', @@ -1264,21 +1255,22 @@ class Result: | https://de.wikipedia.org/wiki/Verzerrungstensor """ - self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m}) + def strain(F: Dict[str, Any], t: Literal['V', 'U'], m: float) -> Dict[str, Any]: + side = 'left' if t == 'V' else 'right' + return { + 'data': mechanics.strain(F['data'],t,m), + 'label': f"epsilon_{t}^{m}({F['label']})", + 'meta': { + 'unit': F['meta']['unit'], + 'description': f'Seth-Hill strain tensor of order {m} based on {side} stretch tensor '+\ + f"of {F['label']} ({F['meta']['description']})", + 'creator': 'add_strain' + } + } + + self._add_generic_pointwise(strain,{'F':F},{'t':t,'m':m}) - @staticmethod - def _add_stretch_tensor(F: Dict[str, Any], t: str) -> Dict[str, Any]: - return { - 'data': (mechanics.stretch_left if t.upper() == 'V' else mechanics.stretch_right)(F['data']), - 'label': f"{t}({F['label']})", - 'meta': { - 'unit': F['meta']['unit'], - 'description': f"{'left' if t.upper() == 'V' else 'right'} stretch tensor "\ - +f"of {F['label']} ({F['meta']['description']})", # noqa - 'creator': 'add_stretch_tensor' - } - } def add_stretch_tensor(self, F: str = 'F', t: Literal['V', 'U'] = 'V'): @@ -1294,20 +1286,21 @@ class Result: Defaults to 'V'. """ - self._add_generic_pointwise(self._add_stretch_tensor,{'F':F},{'t':t}) + def stretch_tensor(F: Dict[str, Any], t: str) -> Dict[str, Any]: + return { + 'data': (mechanics.stretch_left if t.upper() == 'V' else mechanics.stretch_right)(F['data']), + 'label': f"{t}({F['label']})", + 'meta': { + 'unit': F['meta']['unit'], + 'description': f"{'left' if t.upper() == 'V' else 'right'} stretch tensor "\ + +f"of {F['label']} ({F['meta']['description']})", # noqa + 'creator': 'add_stretch_tensor' + } + } + + self._add_generic_pointwise(stretch_tensor,{'F':F},{'t':t}) - @staticmethod - def _add_curl(f: Dict[str, Any], size: np.ndarray) -> Dict[str, Any]: - return { - 'data': grid_filters.curl(size,f['data']), - 'label': f"curl({f['label']})", - 'meta': { - 'unit': f['meta']['unit']+'/m', - 'description': f"curl of {f['label']} ({f['meta']['description']})", - 'creator': 'add_curl' - } - } def add_curl(self, f: str): """ Add curl of a field. @@ -1323,20 +1316,20 @@ class Result: i.e. fields resulting from the grid solver. """ - self._add_generic_grid(self._add_curl,{'f':f},{'size':self.size}) + def curl(f: Dict[str, Any], size: np.ndarray) -> Dict[str, Any]: + return { + 'data': grid_filters.curl(size,f['data']), + 'label': f"curl({f['label']})", + 'meta': { + 'unit': f['meta']['unit']+'/m', + 'description': f"curl of {f['label']} ({f['meta']['description']})", + 'creator': 'add_curl' + } + } + + self._add_generic_grid(curl,{'f':f},{'size':self.size}) - @staticmethod - def _add_divergence(f: Dict[str, Any], size: np.ndarray) -> Dict[str, Any]: - return { - 'data': grid_filters.divergence(size,f['data']), - 'label': f"divergence({f['label']})", - 'meta': { - 'unit': f['meta']['unit']+'/m', - 'description': f"divergence of {f['label']} ({f['meta']['description']})", - 'creator': 'add_divergence' - } - } def add_divergence(self, f: str): """ Add divergence of a field. @@ -1352,21 +1345,20 @@ class Result: i.e. fields resulting from the grid solver. """ - self._add_generic_grid(self._add_divergence,{'f':f},{'size':self.size}) + def divergence(f: Dict[str, Any], size: np.ndarray) -> Dict[str, Any]: + return { + 'data': grid_filters.divergence(size,f['data']), + 'label': f"divergence({f['label']})", + 'meta': { + 'unit': f['meta']['unit']+'/m', + 'description': f"divergence of {f['label']} ({f['meta']['description']})", + 'creator': 'add_divergence' + } + } + + self._add_generic_grid(divergence,{'f':f},{'size':self.size}) - @staticmethod - def _add_gradient(f: Dict[str, Any], size: np.ndarray) -> Dict[str, Any]: - return { - 'data': grid_filters.gradient(size,f['data'] if len(f['data'].shape) == 4 else \ - f['data'].reshape(f['data'].shape+(1,))), - 'label': f"gradient({f['label']})", - 'meta': { - 'unit': f['meta']['unit']+'/m', - 'description': f"gradient of {f['label']} ({f['meta']['description']})", - 'creator': 'add_gradient' - } - } def add_gradient(self, f: str): """ Add gradient of a field. @@ -1382,7 +1374,19 @@ class Result: i.e. fields resulting from the grid solver. """ - self._add_generic_grid(self._add_gradient,{'f':f},{'size':self.size}) + def gradient(f: Dict[str, Any], size: np.ndarray) -> Dict[str, Any]: + return { + 'data': grid_filters.gradient(size,f['data'] if len(f['data'].shape) == 4 else \ + f['data'].reshape(f['data'].shape+(1,))), + 'label': f"gradient({f['label']})", + 'meta': { + 'unit': f['meta']['unit']+'/m', + 'description': f"gradient of {f['label']} ({f['meta']['description']})", + 'creator': 'add_gradient' + } + } + + self._add_generic_grid(gradient,{'f':f},{'size':self.size}) def _add_generic_grid(self, @@ -1444,26 +1448,6 @@ class Result: f'damask.Result.{creator} v{damask.version}'.encode() - def _job_pointwise(self, - group: str, - callback: Callable, - datasets: Dict[str, str], - args: Dict[str, str]) -> List[Union[None, Any]]: - """Execute job for _add_generic_pointwise.""" - try: - datasets_in = {} - with h5py.File(self.fname,'r') as f: - for arg,label in datasets.items(): - loc = f[group+'/'+label] - datasets_in[arg]={'data' :loc[()], - 'label':label, - 'meta': {k:(v.decode() if not h5py3 and type(v) is bytes else v) \ - for k,v in loc.attrs.items()}} - r = callback(**datasets_in,**args) - return [group,r] - except Exception as err: - print(f'Error during calculation: {err}.') - return [None,None] def _add_generic_pointwise(self, @@ -1485,6 +1469,27 @@ class Result: Arguments parsed to func. """ + + def job_pointwise(group: str, + callback: Callable, + datasets: Dict[str, str], + args: Dict[str, str]) -> List[Union[None, Any]]: + """Execute job for _add_generic_pointwise.""" + try: + datasets_in = {} + with h5py.File(self.fname,'r') as f: + for arg,label in datasets.items(): + loc = f[group+'/'+label] + datasets_in[arg]={'data' :loc[()], + 'label':label, + 'meta': {k:(v.decode() if not h5py3 and type(v) is bytes else v) \ + for k,v in loc.attrs.items()}} + r = callback(**datasets_in,**args) + return [group,r] + except Exception as err: + print(f'Error during calculation: {err}.') + return [None,None] + groups = [] with h5py.File(self.fname,'r') as f: for inc in self.visible['increments']: @@ -1498,7 +1503,7 @@ class Result: print('No matching dataset found, no data was added.') return - default_arg = partial(self._job_pointwise,callback=func,datasets=datasets,args=args) + default_arg = partial(job_pointwise,callback=func,datasets=datasets,args=args) for grp in util.show_progress(groups): group, result = default_arg(grp) # type: ignore From ee4cfe4b24db49c832fbd27cac2f14159d7667b7 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 22 Sep 2023 10:58:46 +0200 Subject: [PATCH 14/22] new name 'prod' was introduced in numpy 1.7, 'product' will be removed in 2.0 --- python/tests/test_Result.py | 2 +- python/tests/test_grid_filters.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index a775e1c2d..8b92238ae 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -326,7 +326,7 @@ class TestResult: if shape == 'pseudo_scalar': default.add_calculation('#F#[:,0,0:1]','x','1','a pseudo scalar') if shape == 'scalar': default.add_calculation('#F#[:,0,0]','x','1','just a scalar') if shape == 'vector': default.add_calculation('#F#[:,:,1]','x','1','just a vector') - x = default.place('x').reshape((np.product(default.cells),-1)) + x = default.place('x').reshape((np.prod(default.cells),-1)) default.add_gradient('x') in_file = default.place('gradient(x)') in_memory = grid_filters.gradient(default.size,x.reshape(tuple(default.cells)+x.shape[1:])).reshape(in_file.shape) diff --git a/python/tests/test_grid_filters.py b/python/tests/test_grid_filters.py index 20575f2bd..d19bf03e9 100644 --- a/python/tests/test_grid_filters.py +++ b/python/tests/test_grid_filters.py @@ -398,7 +398,7 @@ class TestGridFilters: np.arange(cells[1]), np.arange(cells[2]),indexing='ij')).reshape(tuple(cells)+(3,),order='F') x,y,z = map(np.random.randint,cells) - assert grid_filters.ravel_index(indices)[x,y,z] == np.arange(0,np.product(cells)).reshape(cells,order='F')[x,y,z] + assert grid_filters.ravel_index(indices)[x,y,z] == np.arange(0,np.prod(cells)).reshape(cells,order='F')[x,y,z] def test_unravel_index(self): cells = np.random.randint(8,32,(3)) From d832d4cf17e1ecfa8a6c2932cae1c973478bfb6b Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 22 Sep 2023 14:11:25 +0200 Subject: [PATCH 15/22] improved documentation --- python/damask/_configmaterial.py | 22 +++++++++++++--------- python/damask/_grid.py | 14 +++++++++++--- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index 2eba905a6..f83c12147 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -106,11 +106,8 @@ class ConfigMaterial(Config): Load DREAM.3D (HDF5) file. Data in DREAM.3D files can be stored per cell ('CellData') - and/or per grain ('Grain Data'). Per default, cell-wise data - is assumed. - - damask.Grid.load_DREAM3D allows to get the corresponding geometry - for the grid solver. + and/or per grain ('Grain Data'). Per default, i.e. if + 'grain_data' is None, cell-wise data is assumed. Parameters ---------- @@ -140,15 +137,22 @@ class ConfigMaterial(Config): and grain- or cell-wise data. Defaults to None, in which case it is set as the path that contains _SIMPL_GEOMETRY/SPACING. - Notes - ----- - Homogenization and phase entries are emtpy and need to be defined separately. - Returns ------- loaded : damask.ConfigMaterial Material configuration from file. + Notes + ----- + damask.Grid.load_DREAM3D gives the corresponding geometry for + the grid solver. + + For cell-wise data, only unique combinations of + orientation and phase are considered. + + Homogenization and phase entries are emtpy and need to be + defined separately. + """ b = util.DREAM3D_base_group(fname) if base_group is None else base_group c = util.DREAM3D_cell_data_group(fname) if cell_data is None else cell_data diff --git a/python/damask/_grid.py b/python/damask/_grid.py index a0571279c..01ad3ad6f 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -358,10 +358,10 @@ class Grid: """ Load DREAM.3D (HDF5) file. - Data in DREAM.3D files can be stored per cell ('CellData') and/or - per grain ('Grain Data'). Per default, cell-wise data is assumed. + Data in DREAM.3D files can be stored per cell ('CellData') + and/or per grain ('Grain Data'). Per default, i.e. if + 'feature_IDs' is None, cell-wise data is assumed. - damask.ConfigMaterial.load_DREAM3D gives the corresponding material definition. Parameters ---------- @@ -392,6 +392,14 @@ class Grid: loaded : damask.Grid Grid-based geometry from file. + Notes + ----- + damask.ConfigMaterial.load_DREAM3D gives the corresponding + material definition. + + For cell-wise data, only unique combinations of + orientation and phase are considered. + """ b = util.DREAM3D_base_group(fname) if base_group is None else base_group c = util.DREAM3D_cell_data_group(fname) if cell_data is None else cell_data From 36d2ae1c2a2ed14e018384d437314374b1fd4627 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 22 Sep 2023 15:14:54 +0200 Subject: [PATCH 16/22] avoid repeated opening of files --- python/damask/util.py | 34 +++++++++++++++++++++------------- python/tests/test_util.py | 18 ++++++++++++------ 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/python/damask/util.py b/python/damask/util.py index d61ca221b..5e5495e43 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -698,7 +698,7 @@ def pass_on(keyword: str, return wrapper return decorator -def DREAM3D_base_group(fname: _Union[str, _Path]) -> str: +def DREAM3D_base_group(fname: _Union[str, _Path, _h5py.File]) -> str: """ Determine the base group of a DREAM.3D file. @@ -707,7 +707,7 @@ def DREAM3D_base_group(fname: _Union[str, _Path]) -> str: Parameters ---------- - fname : str or pathlib.Path + fname : str, pathlib.Path, or _h5py.File Filename of the DREAM.3D (HDF5) file. Returns @@ -716,15 +716,19 @@ def DREAM3D_base_group(fname: _Union[str, _Path]) -> str: Path to the base group. """ - with _h5py.File(_Path(fname).expanduser(),'r') as f: + def get_base_group(f: _h5py.File) -> str: base_group = f.visit(lambda path: path.rsplit('/',2)[0] if '_SIMPL_GEOMETRY/SPACING' in path else None) + if base_group is None: + raise ValueError(f'could not determine base group in file "{fname}"') + return base_group - if base_group is None: - raise ValueError(f'could not determine base group in file "{fname}"') + if isinstance(fname,_h5py.File): + return get_base_group(fname) - return base_group + with _h5py.File(_Path(fname).expanduser(),'r') as f: + return get_base_group(f) -def DREAM3D_cell_data_group(fname: _Union[str, _Path]) -> str: +def DREAM3D_cell_data_group(fname: _Union[str, _Path, _h5py.File]) -> str: """ Determine the cell data group of a DREAM.3D file. @@ -734,7 +738,7 @@ def DREAM3D_cell_data_group(fname: _Union[str, _Path]) -> str: Parameters ---------- - fname : str or pathlib.Path + fname : str, pathlib.Path, or h5py.File Filename of the DREAM.3D (HDF5) file. Returns @@ -743,17 +747,21 @@ def DREAM3D_cell_data_group(fname: _Union[str, _Path]) -> str: Path to the cell data group. """ - base_group = DREAM3D_base_group(fname) - with _h5py.File(_Path(fname).expanduser(),'r') as f: + def get_cell_data_group(f: _h5py.File) -> str: + base_group = DREAM3D_base_group(f) cells = tuple(f['/'.join([base_group,'_SIMPL_GEOMETRY','DIMENSIONS'])][()][::-1]) cell_data_group = f[base_group].visititems(lambda path,obj: path.split('/')[0] \ if isinstance(obj,_h5py._hl.dataset.Dataset) and _np.shape(obj)[:-1] == cells \ else None) + if cell_data_group is None: + raise ValueError(f'could not determine cell-data group in file "{fname}/{base_group}"') + return cell_data_group - if cell_data_group is None: - raise ValueError(f'could not determine cell-data group in file "{fname}/{base_group}"') + if isinstance(fname,_h5py.File): + return get_cell_data_group(fname) - return cell_data_group + with _h5py.File(_Path(fname).expanduser(),'r') as f: + return get_cell_data_group(f) def Bravais_to_Miller(*, diff --git a/python/tests/test_util.py b/python/tests/test_util.py index 1c765727c..28e90f9f3 100644 --- a/python/tests/test_util.py +++ b/python/tests/test_util.py @@ -146,21 +146,25 @@ class TestUtil: assert 'DAMASK' in style('DAMASK') @pytest.mark.parametrize('complete',[True,False]) - def test_D3D_base_group(self,tmp_path,complete): + @pytest.mark.parametrize('fhandle',[True,False]) + def test_D3D_base_group(self,tmp_path,complete,fhandle): base_group = ''.join(random.choices('DAMASK', k=10)) with h5py.File(tmp_path/'base_group.dream3d','w') as f: f.create_group('/'.join((base_group,'_SIMPL_GEOMETRY'))) if complete: f['/'.join((base_group,'_SIMPL_GEOMETRY'))].create_dataset('SPACING',data=np.ones(3)) + fname = tmp_path/'base_group.dream3d' + if fhandle: fname = h5py.File(fname) if complete: - assert base_group == util.DREAM3D_base_group(tmp_path/'base_group.dream3d') + assert base_group == util.DREAM3D_base_group(fname) else: with pytest.raises(ValueError): - util.DREAM3D_base_group(tmp_path/'base_group.dream3d') + util.DREAM3D_base_group(fname) @pytest.mark.parametrize('complete',[True,False]) - def test_D3D_cell_data_group(self,tmp_path,complete): + @pytest.mark.parametrize('fhandle',[True,False]) + def test_D3D_cell_data_group(self,tmp_path,complete,fhandle): base_group = ''.join(random.choices('DAMASK', k=10)) cell_data_group = ''.join(random.choices('KULeuven', k=10)) cells = np.random.randint(1,50,3) @@ -172,11 +176,13 @@ class TestUtil: if complete: f['/'.join((base_group,cell_data_group))].create_dataset('data',shape=np.append(cells,1)) + fname = tmp_path/'cell_data_group.dream3d' + if fhandle: fname = h5py.File(fname) if complete: - assert cell_data_group == util.DREAM3D_cell_data_group(tmp_path/'cell_data_group.dream3d') + assert cell_data_group == util.DREAM3D_cell_data_group(fname) else: with pytest.raises(ValueError): - util.DREAM3D_cell_data_group(tmp_path/'cell_data_group.dream3d') + util.DREAM3D_cell_data_group(fname) @pytest.mark.parametrize('full,reduced',[({}, {}), From 4a943ff844b90e9f402ea1bebeb429c4b2929c91 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 22 Sep 2023 15:15:52 +0200 Subject: [PATCH 17/22] ensure closed file --- python/damask/_configmaterial.py | 38 ++++++++++++++++---------------- python/damask/_grid.py | 30 ++++++++++++------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index f83c12147..20037737e 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -111,7 +111,7 @@ class ConfigMaterial(Config): Parameters ---------- - fname : str + fname : str or pathlib.Path Filename of the DREAM.3D (HDF5) file. grain_data : str Name of the group (folder) containing grain-wise data. Defaults @@ -154,26 +154,26 @@ class ConfigMaterial(Config): defined separately. """ - b = util.DREAM3D_base_group(fname) if base_group is None else base_group - c = util.DREAM3D_cell_data_group(fname) if cell_data is None else cell_data - f = h5py.File(fname,'r') + with h5py.File(fname, 'r') as f: + b = util.DREAM3D_base_group(f) if base_group is None else base_group + c = util.DREAM3D_cell_data_group(f) if cell_data is None else cell_data - if grain_data is None: - phase = f['/'.join([b,c,phases])][()].flatten() - O = Rotation.from_Euler_angles(f['/'.join([b,c,Euler_angles])]).as_quaternion().reshape(-1,4) # noqa - _,idx = np.unique(np.hstack([O,phase.reshape(-1,1)]),return_index=True,axis=0) - idx = np.sort(idx) - else: - phase = f['/'.join([b,grain_data,phases])][()] - O = Rotation.from_Euler_angles(f['/'.join([b,grain_data,Euler_angles])]).as_quaternion() # noqa - idx = np.arange(phase.size) + if grain_data is None: + phase = f['/'.join([b,c,phases])][()].flatten() + O = Rotation.from_Euler_angles(f['/'.join([b,c,Euler_angles])]).as_quaternion().reshape(-1,4) # noqa + _,idx = np.unique(np.hstack([O,phase.reshape(-1,1)]),return_index=True,axis=0) + idx = np.sort(idx) + else: + phase = f['/'.join([b,grain_data,phases])][()] + O = Rotation.from_Euler_angles(f['/'.join([b,grain_data,Euler_angles])]).as_quaternion() # noqa + idx = np.arange(phase.size) - if cell_ensemble_data is not None and phase_names is not None: - try: - names = np.array([s.decode() for s in f['/'.join([b,cell_ensemble_data,phase_names])]]) - phase = names[phase] - except KeyError: - pass + if cell_ensemble_data is not None and phase_names is not None: + try: + names = np.array([s.decode() for s in f['/'.join([b,cell_ensemble_data,phase_names])]]) + phase = names[phase] + except KeyError: + pass base_config = ConfigMaterial({'phase':{k if isinstance(k,int) else str(k): None for k in np.unique(phase)}, diff --git a/python/damask/_grid.py b/python/damask/_grid.py index 01ad3ad6f..a8df4f877 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -365,7 +365,7 @@ class Grid: Parameters ---------- - fname : str or or pathlib.Path + fname : str or pathlib.Path Filename of the DREAM.3D (HDF5) file. feature_IDs : str, optional Name of the dataset containing the mapping between cells and @@ -401,22 +401,22 @@ class Grid: orientation and phase are considered. """ - b = util.DREAM3D_base_group(fname) if base_group is None else base_group - c = util.DREAM3D_cell_data_group(fname) if cell_data is None else cell_data - f = h5py.File(fname, 'r') + with h5py.File(fname, 'r') as f: + b = util.DREAM3D_base_group(f) if base_group is None else base_group + c = util.DREAM3D_cell_data_group(f) if cell_data is None else cell_data - cells = f['/'.join([b,'_SIMPL_GEOMETRY','DIMENSIONS'])][()] - size = f['/'.join([b,'_SIMPL_GEOMETRY','SPACING'])] * cells - origin = f['/'.join([b,'_SIMPL_GEOMETRY','ORIGIN'])][()] + cells = f['/'.join([b,'_SIMPL_GEOMETRY','DIMENSIONS'])][()] + size = f['/'.join([b,'_SIMPL_GEOMETRY','SPACING'])] * cells + origin = f['/'.join([b,'_SIMPL_GEOMETRY','ORIGIN'])][()] - if feature_IDs is None: - phase = f['/'.join([b,c,phases])][()].reshape(-1,1) - O = Rotation.from_Euler_angles(f['/'.join([b,c,Euler_angles])]).as_quaternion().reshape(-1,4) # noqa - unique,unique_inverse = np.unique(np.hstack([O,phase]),return_inverse=True,axis=0) - ma = np.arange(cells.prod()) if len(unique) == cells.prod() else \ - np.arange(unique.size)[np.argsort(pd.unique(unique_inverse))][unique_inverse] - else: - ma = f['/'.join([b,c,feature_IDs])][()].flatten() + if feature_IDs is None: + phase = f['/'.join([b,c,phases])][()].reshape(-1,1) + O = Rotation.from_Euler_angles(f['/'.join([b,c,Euler_angles])]).as_quaternion().reshape(-1,4) # noqa + unique,unique_inverse = np.unique(np.hstack([O,phase]),return_inverse=True,axis=0) + ma = np.arange(cells.prod()) if len(unique) == cells.prod() else \ + np.arange(unique.size)[np.argsort(pd.unique(unique_inverse))][unique_inverse] + else: + ma = f['/'.join([b,c,feature_IDs])][()].flatten() return Grid(material = ma.reshape(cells,order='F'), size = size, From 8b421ba7a77123ea47019ddfd420075308e2acab Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 23 Sep 2023 06:20:25 +0200 Subject: [PATCH 18/22] simplified --- python/damask/_result.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index 9cef7093d..69662cd02 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -1473,8 +1473,7 @@ class Result: def job_pointwise(group: str, callback: Callable, datasets: Dict[str, str], - args: Dict[str, str]) -> List[Union[None, Any]]: - """Execute job for _add_generic_pointwise.""" + args: Dict[str, str]) -> Union[None, Any]: try: datasets_in = {} with h5py.File(self.fname,'r') as f: @@ -1484,11 +1483,10 @@ class Result: 'label':label, 'meta': {k:(v.decode() if not h5py3 and type(v) is bytes else v) \ for k,v in loc.attrs.items()}} - r = callback(**datasets_in,**args) - return [group,r] + return callback(**datasets_in,**args) except Exception as err: print(f'Error during calculation: {err}.') - return [None,None] + return None groups = [] with h5py.File(self.fname,'r') as f: @@ -1505,9 +1503,8 @@ class Result: default_arg = partial(job_pointwise,callback=func,datasets=datasets,args=args) - for grp in util.show_progress(groups): - group, result = default_arg(grp) # type: ignore - if not result: + for group in util.show_progress(groups): + if not (result := default_arg(group)): # type: ignore continue with h5py.File(self.fname, 'a') as f: try: From a99daf5e14c16c746c9e3daf70e5f6ee7bfbfa77 Mon Sep 17 00:00:00 2001 From: Test User Date: Sun, 24 Sep 2023 03:16:13 +0200 Subject: [PATCH 19/22] [skip ci] updated version information after successful test of v3.0.0-alpha7-817-g57d8a6c43 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 821be3454..08a0d8e9c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-alpha7-812-g96699f994 +3.0.0-alpha7-817-g57d8a6c43 From eff54f9566e6a21086e21308c98697cbc15ad63f Mon Sep 17 00:00:00 2001 From: Test User Date: Sun, 24 Sep 2023 23:43:25 +0200 Subject: [PATCH 20/22] [skip ci] updated version information after successful test of v3.0.0-alpha7-824-g5b6aeaf4b --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 08a0d8e9c..ea07b92eb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-alpha7-817-g57d8a6c43 +3.0.0-alpha7-824-g5b6aeaf4b From 28cb72ced0f39b8f61c77945bfa2f9b259af9fb3 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 25 Sep 2023 13:58:17 +0200 Subject: [PATCH 21/22] simplified --- python/damask/_result.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index 69662cd02..f36e1e83a 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -5,8 +5,8 @@ import copy import datetime import xml.etree.ElementTree as ET # noqa import xml.dom.minidom +import functools from pathlib import Path -from functools import partial from collections import defaultdict from collections.abc import Iterable from typing import Optional, Union, Callable, Any, Sequence, Literal, Dict, List, Tuple @@ -1501,10 +1501,9 @@ class Result: print('No matching dataset found, no data was added.') return - default_arg = partial(job_pointwise,callback=func,datasets=datasets,args=args) for group in util.show_progress(groups): - if not (result := default_arg(group)): # type: ignore + if not (result := job_pointwise(group, callback=func, datasets=datasets, args=args)): # type: ignore continue with h5py.File(self.fname, 'a') as f: try: @@ -2054,7 +2053,7 @@ class Result: cfg_dir = (Path.cwd() if target_dir is None else Path(target_dir)) with h5py.File(self.fname,'r') as f_in: - f_in['setup'].visititems(partial(export, - output=output, - cfg_dir=cfg_dir, - overwrite=overwrite)) + f_in['setup'].visititems(functools.partial(export, + output=output, + cfg_dir=cfg_dir, + overwrite=overwrite)) From 96ee2af27986df9000f98e1283967a095a3a3691 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 25 Sep 2023 21:01:01 +0200 Subject: [PATCH 22/22] [skip ci] updated version information after successful test of v3.0.0-alpha7-837-gc3d3ea658 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ea07b92eb..c4779575c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-alpha7-824-g5b6aeaf4b +3.0.0-alpha7-837-gc3d3ea658