diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index bbb682e82..0a48279af 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -239,7 +239,9 @@ class Orientation(Rotation): """ matching_type = all([hasattr(other,attr) and getattr(self,attr) == getattr(other,attr) for attr in ['family','lattice','parameters']]) - return np.logical_and(super().__eq__(other),matching_type) + s = self if self.family is None else self.reduced + o = other if other.family is None else other.reduced + return np.logical_and(super(__class__,s).__eq__(o),matching_type) def __ne__(self,other): """ @@ -254,6 +256,59 @@ class Orientation(Rotation): return np.logical_not(self==other) + def isclose(self,other,rtol=1e-5,atol=1e-8,equal_nan=True): + """ + Report where values are approximately equal to corresponding ones of other Orientation. + + Parameters + ---------- + other : Orientation + Orientation to compare against. + rtol : float, optional + Relative tolerance of equality. + atol : float, optional + Absolute tolerance of equality. + equal_nan : bool, optional + Consider matching NaN values as equal. Defaults to True. + + Returns + ------- + mask : numpy.ndarray bool + Mask indicating where corresponding orientations are close. + + """ + matching_type = all([hasattr(other,attr) and getattr(self,attr) == getattr(other,attr) + for attr in ['family','lattice','parameters']]) + s = self if self.family is None else self.reduced + o = other if other.family is None else other.reduced + return np.logical_and(super(__class__,s).isclose(o),matching_type) + + + + def allclose(self,other,rtol=1e-5,atol=1e-8,equal_nan=True): + """ + Test whether all values are approximately equal to corresponding ones of other Orientation. + + Parameters + ---------- + other : Orientation + Orientation to compare against. + rtol : float, optional + Relative tolerance of equality. + atol : float, optional + Absolute tolerance of equality. + equal_nan : bool, optional + Consider matching NaN values as equal. Defaults to True. + + Returns + ------- + answer : bool + Whether all values are close between both orientations. + + """ + return np.all(self.isclose(other,rtol,atol,equal_nan)) + + def __mul__(self,other): """ Compose this orientation with other. diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index a067209b2..fff53fc73 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -103,29 +103,20 @@ class Rotation: """ Equal to other. - Equality is determined taking limited floating point precision into account. - See numpy.allclose for details. - Parameters ---------- other : Rotation Rotation to check for equality. """ - s = self.quaternion - o = other.quaternion - if self.shape == () == other.shape: - return np.allclose(s,o) or (np.isclose(s[0],0.0) and np.allclose(s,-1.0*o)) - else: - return np.all(np.isclose(s,o),-1) + np.all(np.isclose(s,-1.0*o),-1) * np.isclose(s[...,0],0.0) + return np.logical_or(np.all(self.quaternion == other.quaternion,axis=-1), + np.all(self.quaternion == -1.0*other.quaternion,axis=-1)) + def __ne__(self,other): """ Not equal to other. - Equality is determined taking limited floating point precision into - account. See numpy.allclose for details. - Parameters ---------- other : Rotation @@ -135,6 +126,57 @@ class Rotation: return np.logical_not(self==other) + def isclose(self,other,rtol=1e-5,atol=1e-8,equal_nan=True): + """ + Report where values are approximately equal to corresponding ones of other Rotation. + + Parameters + ---------- + other : Rotation + Rotation to compare against. + rtol : float, optional + Relative tolerance of equality. + atol : float, optional + Absolute tolerance of equality. + equal_nan : bool, optional + Consider matching NaN values as equal. Defaults to True. + + Returns + ------- + mask : numpy.ndarray bool + Mask indicating where corresponding rotations are close. + + """ + s = self.quaternion + o = other.quaternion + return np.logical_or(np.all(np.isclose(s, o,rtol,atol,equal_nan),axis=-1), + np.all(np.isclose(s,-1.0*o,rtol,atol,equal_nan),axis=-1)) + + + def allclose(self,other,rtol=1e-5,atol=1e-8,equal_nan=True): + """ + Test whether all values are approximately equal to corresponding ones of other Rotation. + + Parameters + ---------- + other : Rotation + Rotation to compare against. + rtol : float, optional + Relative tolerance of equality. + atol : float, optional + Absolute tolerance of equality. + equal_nan : bool, optional + Consider matching NaN values as equal. Defaults to True. + + Returns + ------- + answer : bool + Whether all values are close between both rotations. + + """ + return np.all(self.isclose(other,rtol,atol,equal_nan)) + + def __array__(self): """Initializer for numpy.""" return self.quaternion diff --git a/python/tests/test_ConfigMaterial.py b/python/tests/test_ConfigMaterial.py index cf4a8ab9d..98a557ae8 100644 --- a/python/tests/test_ConfigMaterial.py +++ b/python/tests/test_ConfigMaterial.py @@ -140,6 +140,5 @@ class TestConfigMaterial: if update: cur.save(ref_path/'measured.material_yaml') for i,m in enumerate(ref['material']): - assert Rotation(m['constituents'][0]['O']) == \ - Rotation(cur['material'][i]['constituents'][0]['O']) + assert Rotation(m['constituents'][0]['O']).isclose(Rotation(cur['material'][i]['constituents'][0]['O'])) assert cur.is_valid and cur['phase'] == ref['phase'] and cur['homogenization'] == ref['homogenization'] diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 40d4d0116..6f3503b73 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -222,7 +222,7 @@ class TestOrientation: blend = util.shapeblender(o.shape,p.shape) for loc in np.random.randint(0,blend,(10,len(blend))): assert o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])]) \ - == o.disorientation(p)[tuple(loc)] + .isclose(o.disorientation(p)[tuple(loc)]) @pytest.mark.parametrize('lattice',Orientation.crystal_families) def test_disorientation360(self,lattice): diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index b28a849c5..9d0f26bfc 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -960,7 +960,7 @@ class TestRotation: if axis_angle[3] > np.pi: axis_angle[3] -= 2.*np.pi axis_angle *= -1 - assert R**pwr == Rotation.from_axis_angle(axis_angle) + assert (R**pwr).isclose(Rotation.from_axis_angle(axis_angle)) def test_rotate_inverse(self): R = Rotation.from_random() @@ -1027,7 +1027,7 @@ class TestRotation: def test_invariant(self): R = Rotation.from_random() - assert R/R == R*R**(-1) == Rotation() + assert (R/R).isclose(R*R**(-1)) and (R/R).isclose(Rotation()) @pytest.mark.parametrize('item',[np.ones(3),np.ones((3,3)), np.ones((3,3,3,3))]) def test_apply(self,item):