From ccf62ede52108e998064dc9d18aca0c0ebc73d02 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 11:32:18 +0200 Subject: [PATCH 01/33] bugfix for Cubochoric forward and backward mappings are different --- python/damask/_Lambert.py | 14 ++++++------- src/Lambert.f90 | 42 +++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/python/damask/_Lambert.py b/python/damask/_Lambert.py index b3f159ff6..7e46255dc 100644 --- a/python/damask/_Lambert.py +++ b/python/damask/_Lambert.py @@ -60,7 +60,7 @@ def cube_to_ball(cube): else: # get pyramide and scale by grid parameter ratio p = _get_order(cube) - XYZ = cube[p] * sc + XYZ = cube[p[0]] * sc # intercept all the points along the z-axis if np.allclose(XYZ[0:2],0.0,rtol=0.0,atol=1.0e-300): @@ -82,7 +82,7 @@ def cube_to_ball(cube): ball = np.array([ T[order[1]] * q, T[order[0]] * q, np.sqrt(6.0/np.pi) * XYZ[2] - c ]) # reverse the coordinates back to the regular order according to the original pyramid number - ball = ball[p] + ball = ball[p[1]] return ball @@ -110,7 +110,7 @@ def ball_to_cube(ball): cube = np.zeros(3) else: p = _get_order(ball) - xyz3 = ball[p] + xyz3 = ball[p[0]] # inverse M_3 xyz2 = xyz3[0:2] * np.sqrt( 2.0*rs/(rs+np.abs(xyz3[2])) ) @@ -132,7 +132,7 @@ def ball_to_cube(ball): # inverse M_1 cube = np.array([ Tinv[0], Tinv[1], (-1.0 if xyz3[2] < 0.0 else 1.0) * rs / np.sqrt(6.0/np.pi) ]) /sc # reverse the coordinates back to the regular order according to the original pyramid number - cube = cube[p] + cube = cube[p[1]] return cube @@ -157,10 +157,10 @@ def _get_order(xyz): """ if (abs(xyz[0])<= xyz[2]) and (abs(xyz[1])<= xyz[2]) or \ (abs(xyz[0])<=-xyz[2]) and (abs(xyz[1])<=-xyz[2]): - return [0,1,2] + return [[0,1,2],[0,1,2]] elif (abs(xyz[2])<= xyz[0]) and (abs(xyz[1])<= xyz[0]) or \ (abs(xyz[2])<=-xyz[0]) and (abs(xyz[1])<=-xyz[0]): - return [1,2,0] + return [[1,2,0],[2,0,1]] elif (abs(xyz[0])<= xyz[1]) and (abs(xyz[2])<= xyz[1]) or \ (abs(xyz[0])<=-xyz[1]) and (abs(xyz[2])<=-xyz[1]): - return [2,0,1] + return [[2,0,1],[1,2,0]] diff --git a/src/Lambert.f90 b/src/Lambert.f90 index d7d3e48df..932fe221b 100644 --- a/src/Lambert.f90 +++ b/src/Lambert.f90 @@ -70,13 +70,13 @@ contains !-------------------------------------------------------------------------- pure function Lambert_CubeToBall(cube) result(ball) - real(pReal), intent(in), dimension(3) :: cube - real(pReal), dimension(3) :: ball, LamXYZ, XYZ - real(pReal), dimension(2) :: T - real(pReal) :: c, s, q - real(pReal), parameter :: eps = 1.0e-8_pReal - integer, dimension(3) :: p - integer, dimension(2) :: order + real(pReal), intent(in), dimension(3) :: cube + real(pReal), dimension(3) :: ball, LamXYZ, XYZ + real(pReal), dimension(2) :: T + real(pReal) :: c, s, q + real(pReal), parameter :: eps = 1.0e-8_pReal + integer, dimension(3,2) :: p + integer, dimension(2) :: order if (maxval(abs(cube)) > AP/2.0+eps) then ball = IEEE_value(cube,IEEE_positive_inf) @@ -89,7 +89,7 @@ pure function Lambert_CubeToBall(cube) result(ball) else center ! get pyramide and scale by grid parameter ratio p = GetPyramidOrder(cube) - XYZ = cube(p) * sc + XYZ = cube(p(:,1)) * sc ! intercept all the points along the z-axis special: if (all(dEq0(XYZ(1:2)))) then @@ -112,7 +112,7 @@ pure function Lambert_CubeToBall(cube) result(ball) endif special ! reverse the coordinates back to order according to the original pyramid number - ball = LamXYZ(p) + ball = LamXYZ(p(:,2)) endif center @@ -126,11 +126,11 @@ end function Lambert_CubeToBall !-------------------------------------------------------------------------- pure function Lambert_BallToCube(xyz) result(cube) - real(pReal), intent(in), dimension(3) :: xyz - real(pReal), dimension(3) :: cube, xyz1, xyz3 - real(pReal), dimension(2) :: Tinv, xyz2 - real(pReal) :: rs, qxy, q2, sq2, q, tt - integer, dimension(3) :: p + real(pReal), intent(in), dimension(3) :: xyz + real(pReal), dimension(3) :: cube, xyz1, xyz3 + real(pReal), dimension(2) :: Tinv, xyz2 + real(pReal) :: rs, qxy, q2, sq2, q, tt + integer, dimension(3,2) :: p rs = norm2(xyz) if (rs > R1) then @@ -142,7 +142,7 @@ pure function Lambert_BallToCube(xyz) result(cube) cube = 0.0_pReal else center p = GetPyramidOrder(xyz) - xyz3 = xyz(p) + xyz3 = xyz(p(:,1)) ! inverse M_3 xyz2 = xyz3(1:2) * sqrt( 2.0*rs/(rs+abs(xyz3(3))) ) @@ -166,7 +166,7 @@ pure function Lambert_BallToCube(xyz) result(cube) xyz1 = [ Tinv(1), Tinv(2), sign(1.0_pReal,xyz3(3)) * rs / pref ] /sc ! reverse the coordinates back to order according to the original pyramid number - cube = xyz1(p) + cube = xyz1(p(:,2)) endif center @@ -180,18 +180,18 @@ end function Lambert_BallToCube !-------------------------------------------------------------------------- pure function GetPyramidOrder(xyz) - real(pReal),intent(in),dimension(3) :: xyz - integer, dimension(3) :: GetPyramidOrder + real(pReal),intent(in),dimension(3) :: xyz + integer, dimension(3,2) :: GetPyramidOrder if (((abs(xyz(1)) <= xyz(3)).and.(abs(xyz(2)) <= xyz(3))) .or. & ((abs(xyz(1)) <= -xyz(3)).and.(abs(xyz(2)) <= -xyz(3)))) then - GetPyramidOrder = [1,2,3] + GetPyramidOrder = reshape([[1,2,3],[1,2,3]],[3,2]) else if (((abs(xyz(3)) <= xyz(1)).and.(abs(xyz(2)) <= xyz(1))) .or. & ((abs(xyz(3)) <= -xyz(1)).and.(abs(xyz(2)) <= -xyz(1)))) then - GetPyramidOrder = [2,3,1] + GetPyramidOrder = reshape([[2,3,1],[3,1,2]],[3,2]) else if (((abs(xyz(1)) <= xyz(2)).and.(abs(xyz(3)) <= xyz(2))) .or. & ((abs(xyz(1)) <= -xyz(2)).and.(abs(xyz(3)) <= -xyz(2)))) then - GetPyramidOrder = [3,1,2] + GetPyramidOrder = reshape([[3,1,2],[2,3,1]],[3,2]) else GetPyramidOrder = -1 ! should be impossible, but might simplify debugging end if From 4e06e9a410a8c0fe224e127aba4a13b73523a68b Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 11:52:26 +0200 Subject: [PATCH 02/33] improved numerical stability for corner cases --- python/damask/_Lambert.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/python/damask/_Lambert.py b/python/damask/_Lambert.py index 7e46255dc..26bc67b84 100644 --- a/python/damask/_Lambert.py +++ b/python/damask/_Lambert.py @@ -51,19 +51,20 @@ def cube_to_ball(cube): https://doi.org/10.1088/0965-0393/22/7/075013 """ - if np.abs(np.max(cube))>np.pi**(2./3.) * 0.5: + cube_ = np.clip(cube,None,np.pi**(2./3.) * 0.5) if np.isclose(np.abs(np.max(cube)),np.pi**(2./3.) * 0.5) else cube + if np.abs(np.max(cube_))>np.pi**(2./3.) * 0.5: raise ValueError # transform to the sphere grid via the curved square, and intercept the zero point - if np.allclose(cube,0.0,rtol=0.0,atol=1.0e-300): + if np.allclose(cube_,0.0,rtol=0.0,atol=1.0e-16): ball = np.zeros(3) else: # get pyramide and scale by grid parameter ratio - p = _get_order(cube) - XYZ = cube[p[0]] * sc + p = _get_order(cube_) + XYZ = cube_[p[0]] * sc # intercept all the points along the z-axis - if np.allclose(XYZ[0:2],0.0,rtol=0.0,atol=1.0e-300): + if np.allclose(XYZ[0:2],0.0,rtol=0.0,atol=1.0e-16): ball = np.array([0.0, 0.0, np.sqrt(6.0/np.pi) * XYZ[2]]) else: order = [1,0] if np.abs(XYZ[1]) <= np.abs(XYZ[0]) else [0,1] @@ -102,15 +103,16 @@ def ball_to_cube(ball): https://doi.org/10.1088/0965-0393/22/7/075013 """ - rs = np.linalg.norm(ball) + ball_ = ball/np.linalg.norm(ball) if np.isclose(np.linalg.norm(ball),R1) else ball + rs = np.linalg.norm(ball_) if rs > R1: raise ValueError - if np.allclose(ball,0.0,rtol=0.0,atol=1.0e-300): + if np.allclose(ball_,0.0,rtol=0.0,atol=1.0e-16): cube = np.zeros(3) else: - p = _get_order(ball) - xyz3 = ball[p[0]] + p = _get_order(ball_) + xyz3 = ball_[p[0]] # inverse M_3 xyz2 = xyz3[0:2] * np.sqrt( 2.0*rs/(rs+np.abs(xyz3[2])) ) @@ -118,7 +120,7 @@ def ball_to_cube(ball): # inverse M_2 qxy = np.sum(xyz2**2) - if np.isclose(qxy,0.0,rtol=0.0,atol=1.0e-300): + if np.isclose(qxy,0.0,rtol=0.0,atol=1.0e-16): Tinv = np.zeros(2) else: q2 = qxy + np.max(np.abs(xyz2))**2 From b6f5548d8ab27a9d7284bfbaff5be4ab0cfa319c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 12:23:28 +0200 Subject: [PATCH 03/33] correct normalization at the corners --- python/damask/_Lambert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/_Lambert.py b/python/damask/_Lambert.py index 26bc67b84..d9ad55f6e 100644 --- a/python/damask/_Lambert.py +++ b/python/damask/_Lambert.py @@ -103,7 +103,7 @@ def ball_to_cube(ball): https://doi.org/10.1088/0965-0393/22/7/075013 """ - ball_ = ball/np.linalg.norm(ball) if np.isclose(np.linalg.norm(ball),R1) else ball + ball_ = ball/np.linalg.norm(ball)*R1 if np.isclose(np.linalg.norm(ball),R1) else ball rs = np.linalg.norm(ball_) if rs > R1: raise ValueError From 8f88480790eb5680950400cf13186903b298a485 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 12:59:34 +0200 Subject: [PATCH 04/33] better readable error messages --- python/damask/_Lambert.py | 4 ++-- python/damask/_rotation.py | 25 ++++++++++++------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/python/damask/_Lambert.py b/python/damask/_Lambert.py index d9ad55f6e..133c33cca 100644 --- a/python/damask/_Lambert.py +++ b/python/damask/_Lambert.py @@ -53,7 +53,7 @@ def cube_to_ball(cube): """ cube_ = np.clip(cube,None,np.pi**(2./3.) * 0.5) if np.isclose(np.abs(np.max(cube)),np.pi**(2./3.) * 0.5) else cube if np.abs(np.max(cube_))>np.pi**(2./3.) * 0.5: - raise ValueError + raise ValueError('Coordinate outside of the cube: {} {} {}.'.format(*cube)) # transform to the sphere grid via the curved square, and intercept the zero point if np.allclose(cube_,0.0,rtol=0.0,atol=1.0e-16): @@ -106,7 +106,7 @@ def ball_to_cube(ball): ball_ = ball/np.linalg.norm(ball)*R1 if np.isclose(np.linalg.norm(ball),R1) else ball rs = np.linalg.norm(ball_) if rs > R1: - raise ValueError + raise ValueError('Coordinate outside of the sphere: {} {} {}.'.format(*ball)) if np.allclose(ball_,0.0,rtol=0.0,atol=1.0e-16): cube = np.zeros(3) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index bea7aa5e6..1a52df4ba 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -262,9 +262,9 @@ class Rotation: if acceptHomomorph: qu *= -1. else: - raise ValueError('Quaternion has negative first component.\n{}'.format(qu[0])) + raise ValueError('Quaternion has negative first component: {}.'.format(qu[0])) if not np.isclose(np.linalg.norm(qu), 1.0): - raise ValueError('Quaternion is not of unit length.\n{} {} {} {}'.format(*qu)) + raise ValueError('Quaternion is not of unit length: {} {} {} {}.'.format(*qu)) return Rotation(qu) @@ -276,7 +276,7 @@ class Rotation: else np.array(eulers,dtype=float) eu = np.radians(eu) if degrees else eu if np.any(eu < 0.0) or np.any(eu > 2.0*np.pi) or eu[1] > np.pi: - raise ValueError('Euler angles outside of [0..2π],[0..π],[0..2π].\n{} {} {}.'.format(*eu)) + raise ValueError('Euler angles outside of [0..2π],[0..π],[0..2π]: {} {} {}.'.format(*eu)) return Rotation(Rotation.eu2qu(eu)) @@ -292,9 +292,9 @@ class Rotation: if degrees: ax[ 3] = np.radians(ax[3]) if normalise: ax[0:3] /= np.linalg.norm(ax[0:3]) if ax[3] < 0.0 or ax[3] > np.pi: - raise ValueError('Axis angle rotation angle outside of [0..π].\n{}'.format(ax[3])) + raise ValueError('Axis angle rotation angle outside of [0..π]: {}.'.format(ax[3])) if not np.isclose(np.linalg.norm(ax[0:3]), 1.0): - raise ValueError('Axis angle rotation axis is not of unit length.\n{} {} {}'.format(*ax[0:3])) + raise ValueError('Axis angle rotation axis is not of unit length: {} {} {}.'.format(*ax[0:3])) return Rotation(Rotation.ax2qu(ax)) @@ -312,11 +312,11 @@ class Rotation: (U,S,Vh) = np.linalg.svd(om) # singular value decomposition om = np.dot(U,Vh) if not np.isclose(np.linalg.det(om),1.0): - raise ValueError('matrix is not a proper rotation.\n{}'.format(om)) + raise ValueError('matrix is not a proper rotation: {}.'.format(om)) if not np.isclose(np.dot(om[0],om[1]), 0.0) \ or not np.isclose(np.dot(om[1],om[2]), 0.0) \ or not np.isclose(np.dot(om[2],om[0]), 0.0): - raise ValueError('matrix is not orthogonal.\n{}'.format(om)) + raise ValueError('matrix is not orthogonal: {}.'.format(om)) return Rotation(Rotation.om2qu(om)) @@ -336,9 +336,9 @@ class Rotation: if P > 0: ro[0:3] *= -1 # convert from P=1 to P=-1 if normalise: ro[0:3] /= np.linalg.norm(ro[0:3]) if not np.isclose(np.linalg.norm(ro[0:3]), 1.0): - raise ValueError('Rodrigues rotation axis is not of unit length.\n{} {} {}'.format(*ro[0:3])) + raise ValueError('Rodrigues rotation axis is not of unit length: {} {} {}.'.format(*ro[0:3])) if ro[3] < 0.0: - raise ValueError('Rodrigues rotation angle not positive.\n{}'.format(ro[3])) + raise ValueError('Rodrigues rotation angle not positive: {}.'.format(ro[3])) return Rotation(Rotation.ro2qu(ro)) @@ -383,7 +383,7 @@ class Rotation: """ if not all(isinstance(item, Rotation) for item in rotations): - raise TypeError("Only instances of Rotation can be averaged.") + raise TypeError('Only instances of Rotation can be averaged.') N = len(rotations) if not weights: @@ -503,11 +503,10 @@ class Rotation: @staticmethod def qu2ho(qu): """Quaternion to homochoric vector.""" - omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) - - if iszero(omega): + if np.isclose(qu[0],1.0): ho = np.array([ 0.0, 0.0, 0.0 ]) else: + omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) ho = np.array([qu[1], qu[2], qu[3]]) f = 0.75 * ( omega - np.sin(omega) ) ho = ho/np.linalg.norm(ho) * f**(1./3.) From 10d5b2e791451931f91551f0ba686c7dce1d9952 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 13:41:46 +0200 Subject: [PATCH 05/33] testing some special cases --- python/tests/test_Rotation.py | 64 ++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index c545a7172..a7fa47302 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -4,13 +4,39 @@ import pytest import numpy as np from damask import Rotation - + n = 1000 @pytest.fixture def default(): """A set of n random rotations.""" - return [Rotation.fromRandom() for r in range(n)] + specials =[np.array([ 1.0, 0.0, 0.0, 0.0]), + #----------------------------------------------- + np.array([ 1.0, 1.0, 0.0, 0.0])*np.sqrt(2.)*.5, + np.array([ 1.0, 0.0, 1.0, 0.0])*np.sqrt(2.)*.5, + np.array([ 1.0, 0.0, 0.0, 1.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 1.0, 1.0, 0.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 1.0, 0.0, 1.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 0.0, 1.0, 1.0])*np.sqrt(2.)*.5, + #----------------------------------------------- + np.array([ 1.0,-1.0, 0.0, 0.0])*np.sqrt(2.)*.5, + np.array([ 1.0, 0.0,-1.0, 0.0])*np.sqrt(2.)*.5, + np.array([ 1.0, 0.0, 0.0,-1.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 1.0,-1.0, 0.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 1.0, 0.0,-1.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 0.0, 1.0,-1.0])*np.sqrt(2.)*.5, + #----------------------------------------------- + np.array([ 0.0, 1.0,-1.0, 0.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 1.0, 0.0,-1.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 0.0, 1.0,-1.0])*np.sqrt(2.)*.5, + #----------------------------------------------- + np.array([ 0.0,-1.0,-1.0, 0.0])*np.sqrt(2.)*.5, + np.array([ 0.0,-1.0, 0.0,-1.0])*np.sqrt(2.)*.5, + np.array([ 0.0, 0.0,-1.0,-1.0])*np.sqrt(2.)*.5, + #----------------------------------------------- + ] + return [Rotation.fromQuaternion(s) for s in specials] + \ + [Rotation.fromRandom() for r in range(n-len(specials))] @pytest.fixture def reference_dir(reference_dir_base): @@ -22,35 +48,41 @@ class TestRotation: def test_Eulers(self,default): for rot in default: - assert np.allclose(rot.asQuaternion(), - Rotation.fromEulers(rot.asEulers()).asQuaternion()) + c = Rotation.fromEulers(rot.asEulers()) + ok = np.allclose(rot.asQuaternion(),c.asQuaternion()) + if np.isclose(rot.asQuaternion()[0],0.0,atol=1.e-13,rtol=0.0): + ok = ok or np.allclose(rot.asQuaternion(),c.asQuaternion()*-1.) + assert ok def test_AxisAngle(self,default): for rot in default: - assert np.allclose(rot.asEulers(), - Rotation.fromAxisAngle(rot.asAxisAngle()).asEulers()) + c = Rotation.fromAxisAngle(rot.asAxisAngle()) + assert np.allclose(rot.asEulers(),c.asEulers()) def test_Matrix(self,default): for rot in default: - assert np.allclose(rot.asAxisAngle(), - Rotation.fromMatrix(rot.asMatrix()).asAxisAngle()) + c = Rotation.fromMatrix(rot.asMatrix()) + ok = np.allclose(rot.asAxisAngle(),c.asAxisAngle()) + if np.isclose(rot.asAxisAngle()[3],np.pi): + ok = ok or np.allclose(rot.asAxisAngle(),c.asAxisAngle()*np.array([-1.,-1.,-1.,1.])) + assert ok def test_Rodriques(self,default): for rot in default: - assert np.allclose(rot.asMatrix(), - Rotation.fromRodrigues(rot.asRodrigues()).asMatrix()) + c = Rotation.fromRodrigues(rot.asRodrigues()) + assert np.allclose(rot.asMatrix(),c.asMatrix()) def test_Homochoric(self,default): for rot in default: - assert np.allclose(rot.asRodrigues(), - Rotation.fromHomochoric(rot.asHomochoric()).asRodrigues(),rtol=1.e-4) + c = Rotation.fromHomochoric(rot.asHomochoric()) + assert np.allclose(np.clip(rot.asRodrigues(),None,1.e9),np.clip(c.asRodrigues(),None,1.e9)) def test_Cubochoric(self,default): for rot in default: - assert np.allclose(rot.asHomochoric(), - Rotation.fromCubochoric(rot.asCubochoric()).asHomochoric()) + c = Rotation.fromCubochoric(rot.asCubochoric()) + assert np.allclose(rot.asHomochoric(),c.asHomochoric()) def test_Quaternion(self,default): for rot in default: - assert np.allclose(rot.asCubochoric(), - Rotation.fromQuaternion(rot.asQuaternion()).asCubochoric()) + c = Rotation.fromQuaternion(rot.asQuaternion()) + assert np.allclose(rot.asCubochoric(),c.asCubochoric()) From f365ae104d7a6648166f0a3d46905d2470df1d82 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 17:55:05 +0200 Subject: [PATCH 06/33] adjusting tolerances --- python/damask/_Lambert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/damask/_Lambert.py b/python/damask/_Lambert.py index 133c33cca..a62f1922a 100644 --- a/python/damask/_Lambert.py +++ b/python/damask/_Lambert.py @@ -51,7 +51,7 @@ def cube_to_ball(cube): https://doi.org/10.1088/0965-0393/22/7/075013 """ - cube_ = np.clip(cube,None,np.pi**(2./3.) * 0.5) if np.isclose(np.abs(np.max(cube)),np.pi**(2./3.) * 0.5) else cube + cube_ = np.clip(cube,None,np.pi**(2./3.) * 0.5) if np.isclose(np.abs(np.max(cube)),np.pi**(2./3.) * 0.5,atol=1e-6) else cube if np.abs(np.max(cube_))>np.pi**(2./3.) * 0.5: raise ValueError('Coordinate outside of the cube: {} {} {}.'.format(*cube)) @@ -103,9 +103,9 @@ def ball_to_cube(ball): https://doi.org/10.1088/0965-0393/22/7/075013 """ - ball_ = ball/np.linalg.norm(ball)*R1 if np.isclose(np.linalg.norm(ball),R1) else ball + ball_ = ball/np.linalg.norm(ball)*R1 if np.isclose(np.linalg.norm(ball),R1,atol=1e-6) else ball rs = np.linalg.norm(ball_) - if rs > R1: + if rs > R1 and not np.isclose(rs,R1): raise ValueError('Coordinate outside of the sphere: {} {} {}.'.format(*ball)) if np.allclose(ball_,0.0,rtol=0.0,atol=1.0e-16): From 1ba01ba0db73831cc5f6453456b073812e0d2e68 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 18:15:50 +0200 Subject: [PATCH 07/33] adjusting tolerances --- python/damask/_rotation.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 1a52df4ba..1325c3b3e 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -459,16 +459,16 @@ class Rotation: q03 = qu[0]**2+qu[3]**2 q12 = qu[1]**2+qu[2]**2 chi = np.sqrt(q03*q12) - - if iszero(chi): - eu = np.array([np.arctan2(-P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) if iszero(q12) else \ - np.array([np.arctan2(2.0*qu[1]*qu[2],qu[1]**2-qu[2]**2), np.pi, 0.0]) + if np.abs(chi)< 1.e-6: + eu = np.array([np.arctan2(-P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) if np.abs(q12)< 1.e-6 else \ + np.array([np.arctan2( 2.0*qu[1]*qu[2],qu[1]**2-qu[2]**2), np.pi, 0.0]) else: eu = np.array([np.arctan2((-P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]-qu[2]*qu[3])*chi ), np.arctan2( 2.0*chi, q03-q12 ), np.arctan2(( P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]+qu[2]*qu[3])*chi )]) # reduce Euler angles to definition range, i.e a lower limit of 0.0 + eu[np.abs(eu)<1.e-6] = 0.0 eu = np.where(eu<0, (eu+2.0*np.pi)%np.array([2.0*np.pi,np.pi,2.0*np.pi]),eu) return eu @@ -481,7 +481,7 @@ class Rotation: """ if iszero(qu[1]**2+qu[2]**2+qu[3]**2): # set axis to [001] if the angle is 0/360 ax = [ 0.0, 0.0, 1.0, 0.0 ] - elif not iszero(qu[0]): + elif np.abs(qu[0]) > 1.e-6: s = np.sign(qu[0])/np.sqrt(qu[1]**2+qu[2]**2+qu[3]**2) omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) ax = [ qu[1]*s, qu[2]*s, qu[3]*s, omega ] @@ -498,6 +498,7 @@ class Rotation: s = np.linalg.norm([qu[1],qu[2],qu[3]]) ro = [0.0,0.0,P,0.0] if iszero(s) else \ [ qu[1]/s, qu[2]/s, qu[3]/s, np.tan(np.arccos(np.clip(qu[0],-1.0,1.0)))] + return np.array(ro) @staticmethod @@ -531,7 +532,7 @@ class Rotation: @staticmethod def om2eu(om): """Rotation matrix to Bunge-Euler angles.""" - if abs(om[2,2]) < 1.0: + if not np.isclose(np.abs(om[2,2]),1.0,1.e-4): zeta = 1.0/np.sqrt(1.0-om[2,2]**2) eu = np.array([np.arctan2(om[2,0]*zeta,-om[2,1]*zeta), np.arccos(om[2,2]), @@ -540,6 +541,7 @@ class Rotation: eu = np.array([np.arctan2( om[0,1],om[0,0]), np.pi*0.5*(1-om[2,2]),0.0]) # following the paper, not the reference implementation # reduce Euler angles to definition range, i.e a lower limit of 0.0 + eu[np.abs(eu)<1.e-6] = 0.0 eu = np.where(eu<0, (eu+2.0*np.pi)%np.array([2.0*np.pi,np.pi,2.0*np.pi]),eu) return eu @@ -552,7 +554,7 @@ class Rotation: t = 0.5*(om.trace() -1.0) ax[3] = np.arccos(np.clip(t,-1.0,1.0)) - if iszero(ax[3]): + if np.abs(ax[3])<1.e-6: ax = [ 0.0, 0.0, 1.0, 0.0] else: w,vr = np.linalg.eig(om) @@ -560,7 +562,7 @@ class Rotation: i = np.where(np.isclose(w,1.0+0.0j))[0][0] ax[0:3] = np.real(vr[0:3,i]) diagDelta = np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) - ax[0:3] = np.where(iszero(diagDelta), ax[0:3],np.abs(ax[0:3])*np.sign(-P*diagDelta)) + ax[0:3] = np.where(np.abs(diagDelta)<1.e-6, ax[0:3],np.abs(ax[0:3])*np.sign(-P*diagDelta)) return np.array(ax) @staticmethod @@ -603,7 +605,7 @@ class Rotation: [-c[0]*s[2]-s[0]*c[2]*c[1], -s[0]*s[2]+c[0]*c[2]*c[1], +c[2]*s[1]], [+s[0]*s[1], -c[0]*s[1], +c[1] ]]) - om[np.where(iszero(om))] = 0.0 + om[np.abs(om)<1.e-6] = 0.0 return om @staticmethod @@ -616,7 +618,7 @@ class Rotation: alpha = np.pi if iszero(np.cos(sigma)) else \ 2.0*np.arctan(tau/np.cos(sigma)) - if iszero(alpha): + if np.abs(alpha)<1.e-6: ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) else: ax = -P/tau * np.array([ t*np.cos(delta), t*np.sin(delta), np.sin(sigma) ]) # passive axis angle pair so a minus sign in front @@ -651,7 +653,7 @@ class Rotation: @staticmethod def ax2qu(ax): """Axis angle pair to quaternion.""" - if iszero(ax[3]): + if np.abs(ax[3])<1.e-6: qu = np.array([ 1.0, 0.0, 0.0, 0.0 ]) else: c = np.cos(ax[3]*0.5) @@ -681,7 +683,7 @@ class Rotation: @staticmethod def ax2ro(ax): """Axis angle pair to Rodrigues-Frank vector.""" - if iszero(ax[3]): + if np.abs(ax[3])<1.e-6: ro = [ 0.0, 0.0, P, 0.0 ] else: ro = [ax[0], ax[1], ax[2]] From 3cd8f3d9a0641e46333493e9c5550882553934b0 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 18:38:57 +0200 Subject: [PATCH 08/33] testing special orientations with scatter --- python/tests/test_Rotation.py | 139 ++++++++++++++++++++++++---------- 1 file changed, 101 insertions(+), 38 deletions(-) diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index a7fa47302..1f69f2025 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -6,35 +6,77 @@ import numpy as np from damask import Rotation n = 1000 +atol=1.e-5 +scatter=1.e-9 @pytest.fixture def default(): """A set of n random rotations.""" - specials =[np.array([ 1.0, 0.0, 0.0, 0.0]), + specials = np.array( + [np.array([ 1.0, 0.0, 0.0, 0.0]), #----------------------------------------------- - np.array([ 1.0, 1.0, 0.0, 0.0])*np.sqrt(2.)*.5, - np.array([ 1.0, 0.0, 1.0, 0.0])*np.sqrt(2.)*.5, - np.array([ 1.0, 0.0, 0.0, 1.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 1.0, 1.0, 0.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 1.0, 0.0, 1.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 0.0, 1.0, 1.0])*np.sqrt(2.)*.5, + np.array([0.0, 1.0, 0.0, 0.0]), + np.array([0.0, 0.0, 1.0, 0.0]), + np.array([0.0, 0.0, 0.0, 1.0]), + np.array([0.0,-1.0, 0.0, 0.0]), + np.array([0.0, 0.0,-1.0, 0.0]), + np.array([0.0, 0.0, 0.0,-1.0]), #----------------------------------------------- - np.array([ 1.0,-1.0, 0.0, 0.0])*np.sqrt(2.)*.5, - np.array([ 1.0, 0.0,-1.0, 0.0])*np.sqrt(2.)*.5, - np.array([ 1.0, 0.0, 0.0,-1.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 1.0,-1.0, 0.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 1.0, 0.0,-1.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 0.0, 1.0,-1.0])*np.sqrt(2.)*.5, + np.array([1.0, 1.0, 0.0, 0.0])/np.sqrt(2.), + np.array([1.0, 0.0, 1.0, 0.0])/np.sqrt(2.), + np.array([1.0, 0.0, 0.0, 1.0])/np.sqrt(2.), + np.array([0.0, 1.0, 1.0, 0.0])/np.sqrt(2.), + np.array([0.0, 1.0, 0.0, 1.0])/np.sqrt(2.), + np.array([0.0, 0.0, 1.0, 1.0])/np.sqrt(2.), #----------------------------------------------- - np.array([ 0.0, 1.0,-1.0, 0.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 1.0, 0.0,-1.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 0.0, 1.0,-1.0])*np.sqrt(2.)*.5, + np.array([1.0,-1.0, 0.0, 0.0])/np.sqrt(2.), + np.array([1.0, 0.0,-1.0, 0.0])/np.sqrt(2.), + np.array([1.0, 0.0, 0.0,-1.0])/np.sqrt(2.), + np.array([0.0, 1.0,-1.0, 0.0])/np.sqrt(2.), + np.array([0.0, 1.0, 0.0,-1.0])/np.sqrt(2.), + np.array([0.0, 0.0, 1.0,-1.0])/np.sqrt(2.), #----------------------------------------------- - np.array([ 0.0,-1.0,-1.0, 0.0])*np.sqrt(2.)*.5, - np.array([ 0.0,-1.0, 0.0,-1.0])*np.sqrt(2.)*.5, - np.array([ 0.0, 0.0,-1.0,-1.0])*np.sqrt(2.)*.5, + np.array([0.0, 1.0,-1.0, 0.0])/np.sqrt(2.), + np.array([0.0, 1.0, 0.0,-1.0])/np.sqrt(2.), + np.array([0.0, 0.0, 1.0,-1.0])/np.sqrt(2.), #----------------------------------------------- - ] + np.array([0.0,-1.0,-1.0, 0.0])/np.sqrt(2.), + np.array([0.0,-1.0, 0.0,-1.0])/np.sqrt(2.), + np.array([0.0, 0.0,-1.0,-1.0])/np.sqrt(2.), + #----------------------------------------------- + np.array([1.0, 1.0, 1.0, 0.0])/np.sqrt(3.), + np.array([1.0, 1.0, 0.0, 1.0])/np.sqrt(3.), + np.array([1.0, 0.0, 1.0, 1.0])/np.sqrt(3.), + np.array([1.0,-1.0, 1.0, 0.0])/np.sqrt(3.), + np.array([1.0,-1.0, 0.0, 1.0])/np.sqrt(3.), + np.array([1.0, 0.0,-1.0, 1.0])/np.sqrt(3.), + np.array([1.0, 1.0,-1.0, 0.0])/np.sqrt(3.), + np.array([1.0, 1.0, 0.0,-1.0])/np.sqrt(3.), + np.array([1.0, 0.0, 1.0,-1.0])/np.sqrt(3.), + np.array([1.0,-1.0,-1.0, 0.0])/np.sqrt(3.), + np.array([1.0,-1.0, 0.0,-1.0])/np.sqrt(3.), + np.array([1.0, 0.0,-1.0,-1.0])/np.sqrt(3.), + #----------------------------------------------- + np.array([0.0, 1.0, 1.0, 1.0])/np.sqrt(3.), + np.array([0.0, 1.0,-1.0, 1.0])/np.sqrt(3.), + np.array([0.0, 1.0, 1.0,-1.0])/np.sqrt(3.), + np.array([0.0,-1.0, 1.0, 1.0])/np.sqrt(3.), + np.array([0.0,-1.0,-1.0, 1.0])/np.sqrt(3.), + np.array([0.0,-1.0, 1.0,-1.0])/np.sqrt(3.), + np.array([0.0,-1.0,-1.0,-1.0])/np.sqrt(3.), + #----------------------------------------------- + np.array([1.0, 1.0, 1.0, 1.0])/2., + np.array([1.0,-1.0, 1.0, 1.0])/2., + np.array([1.0, 1.0,-1.0, 1.0])/2., + np.array([1.0, 1.0, 1.0,-1.0])/2., + np.array([1.0,-1.0,-1.0, 1.0])/2., + np.array([1.0,-1.0, 1.0,-1.0])/2., + np.array([1.0, 1.0,-1.0,-1.0])/2., + np.array([1.0,-1.0,-1.0,-1.0])/2., + ]) + specials += np.broadcast_to(np.random.rand(4)*scatter,specials.shape) + specials /= np.linalg.norm(specials,axis=1).reshape(-1,1) + specials[specials[:,0]<0]*=-1 return [Rotation.fromQuaternion(s) for s in specials] + \ [Rotation.fromRandom() for r in range(n-len(specials))] @@ -48,41 +90,62 @@ class TestRotation: def test_Eulers(self,default): for rot in default: - c = Rotation.fromEulers(rot.asEulers()) - ok = np.allclose(rot.asQuaternion(),c.asQuaternion()) - if np.isclose(rot.asQuaternion()[0],0.0,atol=1.e-13,rtol=0.0): - ok = ok or np.allclose(rot.asQuaternion(),c.asQuaternion()*-1.) + m = rot.asQuaternion() + o = Rotation.fromEulers(rot.asEulers()).asQuaternion() + ok = np.allclose(m,o,atol=atol) + if np.isclose(rot.asQuaternion()[0],0.0,atol=atol): + ok = ok or np.allclose(m*-1.,o,atol=atol) + print(m,o,rot.asQuaternion()) assert ok def test_AxisAngle(self,default): for rot in default: - c = Rotation.fromAxisAngle(rot.asAxisAngle()) - assert np.allclose(rot.asEulers(),c.asEulers()) + m = rot.asEulers() + o = Rotation.fromAxisAngle(rot.asAxisAngle()).asEulers() + u = np.array([np.pi*2,np.pi,np.pi*2]) + ok = np.allclose(m,o,atol=atol) + ok = ok or np.allclose(np.where(np.isclose(m,u),m-u,m),np.where(np.isclose(o,u),o-u,o),atol=atol) + if np.isclose(m[1],0.0,atol=atol) or np.isclose(m[1],np.pi,atol=atol): + sum_phi = np.unwrap([m[0]+m[2],o[0]+o[2]]) + ok = ok or np.isclose(sum_phi[0],sum_phi[1],atol=atol) + print(m,o,rot.asQuaternion()) + assert ok def test_Matrix(self,default): for rot in default: - c = Rotation.fromMatrix(rot.asMatrix()) - ok = np.allclose(rot.asAxisAngle(),c.asAxisAngle()) - if np.isclose(rot.asAxisAngle()[3],np.pi): - ok = ok or np.allclose(rot.asAxisAngle(),c.asAxisAngle()*np.array([-1.,-1.,-1.,1.])) + m = rot.asAxisAngle() + o = Rotation.fromAxisAngle(rot.asAxisAngle()).asAxisAngle() + ok = np.allclose(m,o,atol=atol) + if np.isclose(m[3],np.pi,atol=atol): + ok = ok or np.allclose(m*np.array([-1.,-1.,-1.,1.]),o,atol=atol) + print(m,o,rot.asQuaternion()) assert ok def test_Rodriques(self,default): for rot in default: - c = Rotation.fromRodrigues(rot.asRodrigues()) - assert np.allclose(rot.asMatrix(),c.asMatrix()) + m = rot.asMatrix() + o = Rotation.fromRodrigues(rot.asRodrigues()).asMatrix() + print(m,o) + assert np.allclose(m,o,atol=atol) def test_Homochoric(self,default): for rot in default: - c = Rotation.fromHomochoric(rot.asHomochoric()) - assert np.allclose(np.clip(rot.asRodrigues(),None,1.e9),np.clip(c.asRodrigues(),None,1.e9)) + m = rot.asRodrigues() + o = Rotation.fromHomochoric(rot.asHomochoric()).asRodrigues() + ok = np.allclose(np.clip(m,None,1.e9),np.clip(o,None,1.e9),atol=atol) + print(m,o,rot.asQuaternion()) + ok = ok or np.isclose(m[3],0.0,atol=atol) def test_Cubochoric(self,default): for rot in default: - c = Rotation.fromCubochoric(rot.asCubochoric()) - assert np.allclose(rot.asHomochoric(),c.asHomochoric()) + m = rot.asHomochoric() + o = Rotation.fromCubochoric(rot.asCubochoric()).asHomochoric() + print(m,o,rot.asQuaternion()) + assert np.allclose(m,o,atol=atol*1e2) def test_Quaternion(self,default): for rot in default: - c = Rotation.fromQuaternion(rot.asQuaternion()) - assert np.allclose(rot.asCubochoric(),c.asCubochoric()) + m = rot.asCubochoric() + o = Rotation.fromQuaternion(rot.asQuaternion()).asCubochoric() + print(m,o,rot.asQuaternion()) + assert np.allclose(m,o,atol=atol) From 59e0041fd7a5f323f5656ca0ecff0b5e6aad4de5 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 19:30:50 +0200 Subject: [PATCH 09/33] more scatter, slightly reduced tolerance --- python/tests/test_Rotation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 1f69f2025..fc88e4130 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -6,8 +6,8 @@ import numpy as np from damask import Rotation n = 1000 -atol=1.e-5 -scatter=1.e-9 +atol=1.e-4 +scatter=1.e-2 @pytest.fixture def default(): From 2a063b3bb5888c82d134406e9796723fe10734dc Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 20:23:05 +0200 Subject: [PATCH 10/33] relaxed tolerance not needed --- python/tests/test_Rotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index fc88e4130..cec0ce22c 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -141,7 +141,7 @@ class TestRotation: m = rot.asHomochoric() o = Rotation.fromCubochoric(rot.asCubochoric()).asHomochoric() print(m,o,rot.asQuaternion()) - assert np.allclose(m,o,atol=atol*1e2) + assert np.allclose(m,o,atol=atol) def test_Quaternion(self,default): for rot in default: From 62ddfe098cebef167fa757e632f8238959e891a6 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 20:30:36 +0200 Subject: [PATCH 11/33] fixed test --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 62bd5ede5..377e4f97a 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 62bd5ede5260cd4e0e3d1c3930c474c1e045aeef +Subproject commit 377e4f97a31ca1aa39a0645430c82bed40158001 From 464620b796d89d2318b06fdde7530207a6e3f783 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 20:50:42 +0200 Subject: [PATCH 12/33] vectorized conversion from ax(is angle) --- python/damask/_rotation.py | 87 +++++++++++++++++++++++++---------- python/tests/test_Rotation.py | 14 +++++- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 1325c3b3e..a2d26949d 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -653,27 +653,50 @@ class Rotation: @staticmethod def ax2qu(ax): """Axis angle pair to quaternion.""" - if np.abs(ax[3])<1.e-6: - qu = np.array([ 1.0, 0.0, 0.0, 0.0 ]) + if len(ax.shape) == 1: + if np.abs(ax[3])<1.e-6: + qu = np.array([ 1.0, 0.0, 0.0, 0.0 ]) + else: + c = np.cos(ax[3]*0.5) + s = np.sin(ax[3]*0.5) + qu = np.array([ c, ax[0]*s, ax[1]*s, ax[2]*s ]) + return qu else: - c = np.cos(ax[3]*0.5) - s = np.sin(ax[3]*0.5) - qu = np.array([ c, ax[0]*s, ax[1]*s, ax[2]*s ]) - return qu + c = np.cos(ax[...,3:4]*.5) + s = np.sin(ax[...,3:4]*.5) + qu = np.where(np.abs(ax[...,3:4])<1.e-12, + [1.0, 0.0, 0.0, 0.0], + np.block([c, ax[...,:3]*s])) + return qu @staticmethod def ax2om(ax): """Axis angle pair to rotation matrix.""" - c = np.cos(ax[3]) - s = np.sin(ax[3]) - omc = 1.0-c - om=np.diag(ax[0:3]**2*omc + c) + if len(ax.shape) == 1: + c = np.cos(ax[3]) + s = np.sin(ax[3]) + omc = 1.0-c + om=np.diag(ax[0:3]**2*omc + c) - for idx in [[0,1,2],[1,2,0],[2,0,1]]: - q = omc*ax[idx[0]] * ax[idx[1]] - om[idx[0],idx[1]] = q + s*ax[idx[2]] - om[idx[1],idx[0]] = q - s*ax[idx[2]] - return om if P < 0.0 else om.T + for idx in [[0,1,2],[1,2,0],[2,0,1]]: + q = omc*ax[idx[0]] * ax[idx[1]] + om[idx[0],idx[1]] = q + s*ax[idx[2]] + om[idx[1],idx[0]] = q - s*ax[idx[2]] + return om if P < 0.0 else om.T + else: + c = np.cos(ax[...,3:4]) + s = np.sin(ax[...,3:4]) + omc = 1. -c + ax = np.block([c+omc*ax[...,0:1]**2, + omc*ax[...,0:1]*ax[...,1:2] + s*ax[...,2:3], + omc*ax[...,0:1]*ax[...,2:3] - s*ax[...,1:2], + omc*ax[...,0:1]*ax[...,1:2] - s*ax[...,2:3], + c+omc*ax[...,1:2]**2, + omc*ax[...,1:2]*ax[...,2:3] + s*ax[...,0:1], + omc*ax[...,0:1]*ax[...,2:3] + s*ax[...,1:2], + omc*ax[...,1:2]*ax[...,2:3] - s*ax[...,0:1], + c+omc*ax[...,2:3]**2]).reshape(ax.shape[:-1]+(3,3)) + return ax @staticmethod def ax2eu(ax): @@ -683,21 +706,35 @@ class Rotation: @staticmethod def ax2ro(ax): """Axis angle pair to Rodrigues-Frank vector.""" - if np.abs(ax[3])<1.e-6: - ro = [ 0.0, 0.0, P, 0.0 ] + if len(ax.shape) == 1: + if np.abs(ax[3])<1.e-6: + ro = [ 0.0, 0.0, P, 0.0 ] + else: + ro = [ax[0], ax[1], ax[2]] + # 180 degree case + ro += [np.inf] if np.isclose(ax[3],np.pi,atol=1.0e-15,rtol=0.0) else \ + [np.tan(ax[3]*0.5)] + return np.array(ro) else: - ro = [ax[0], ax[1], ax[2]] - # 180 degree case - ro += [np.inf] if np.isclose(ax[3],np.pi,atol=1.0e-15,rtol=0.0) else \ - [np.tan(ax[3]*0.5)] - return np.array(ro) + ro = np.block([ax[...,:3], + np.where(np.isclose(ax[...,3:4],np.pi,atol=1.e-15,rtol=.0), + np.inf, + np.tan(ax[...,3:4]*0.5)) + ]) + ro[np.abs(ax[...,3])<1.e-6] = [.0,.0,P,.0] + return ro @staticmethod def ax2ho(ax): """Axis angle pair to homochoric vector.""" - f = (0.75 * ( ax[3] - np.sin(ax[3]) ))**(1.0/3.0) - ho = ax[0:3] * f - return ho + if len(ax.shape) == 1: + f = (0.75 * ( ax[3] - np.sin(ax[3]) ))**(1.0/3.0) + ho = ax[0:3] * f + return ho + else: + f = (0.75 * ( ax[...,3:4] - np.sin(ax[...,3:4]) ))**(1.0/3.0) + ho = ax[...,:3] * f + return ho @staticmethod def ax2cu(ax): diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index cec0ce22c..13c474ca7 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -78,7 +78,7 @@ def default(): specials /= np.linalg.norm(specials,axis=1).reshape(-1,1) specials[specials[:,0]<0]*=-1 return [Rotation.fromQuaternion(s) for s in specials] + \ - [Rotation.fromRandom() for r in range(n-len(specials))] + [Rotation.fromRandom() for _ in range(n-len(specials))] @pytest.fixture def reference_dir(reference_dir_base): @@ -149,3 +149,15 @@ class TestRotation: o = Rotation.fromQuaternion(rot.asQuaternion()).asCubochoric() print(m,o,rot.asQuaternion()) assert np.allclose(m,o,atol=atol) + + @pytest.mark.parametrize('conversion',[Rotation.ax2qu, + Rotation.ax2om, + Rotation.ax2ro, + Rotation.ax2ho, + ]) + def test_axisAngle_vectorization(self,default,conversion): + ax = np.array([rot.asAxisAngle() for rot in default]) + dev_null = conversion(ax.reshape(ax.shape[0]//2,-1,4)) + co = conversion(ax) + for a,c in zip(ax,co): + assert np.allclose(conversion(a),c) From da30fb8396e384d1012370df9023d722b1410514 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 8 Apr 2020 23:11:48 +0200 Subject: [PATCH 13/33] qu(aternion) and eu(ler) vectorized and tested --- python/damask/_rotation.py | 265 ++++++++++++++++++++++++---------- python/tests/test_Rotation.py | 22 +++ 2 files changed, 208 insertions(+), 79 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index a2d26949d..9296f57b4 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -441,32 +441,68 @@ class Rotation: #---------- Quaternion ---------- @staticmethod def qu2om(qu): - """Quaternion to rotation matrix.""" - qq = qu[0]**2-(qu[1]**2 + qu[2]**2 + qu[3]**2) - om = np.diag(qq + 2.0*np.array([qu[1],qu[2],qu[3]])**2) + if len(qu.shape) == 1: + """Quaternion to rotation matrix.""" + qq = qu[0]**2-(qu[1]**2 + qu[2]**2 + qu[3]**2) + om = np.diag(qq + 2.0*np.array([qu[1],qu[2],qu[3]])**2) - om[1,0] = 2.0*(qu[2]*qu[1]+qu[0]*qu[3]) - om[0,1] = 2.0*(qu[1]*qu[2]-qu[0]*qu[3]) - om[2,1] = 2.0*(qu[3]*qu[2]+qu[0]*qu[1]) - om[1,2] = 2.0*(qu[2]*qu[3]-qu[0]*qu[1]) - om[0,2] = 2.0*(qu[1]*qu[3]+qu[0]*qu[2]) - om[2,0] = 2.0*(qu[3]*qu[1]-qu[0]*qu[2]) - return om if P > 0.0 else om.T + om[1,0] = 2.0*(qu[2]*qu[1]+qu[0]*qu[3]) + om[0,1] = 2.0*(qu[1]*qu[2]-qu[0]*qu[3]) + om[2,1] = 2.0*(qu[3]*qu[2]+qu[0]*qu[1]) + om[1,2] = 2.0*(qu[2]*qu[3]-qu[0]*qu[1]) + om[0,2] = 2.0*(qu[1]*qu[3]+qu[0]*qu[2]) + om[2,0] = 2.0*(qu[3]*qu[1]-qu[0]*qu[2]) + return om if P > 0.0 else om.T + else: + qq = qu[...,0:1]**2-(qu[...,1:2]**2 + qu[...,2:3]**2 + qu[...,3:4]**2) + om = np.block([qq + 2.0*qu[...,1:2]**2, + 2.0*(qu[...,2:3]*qu[...,1:2]+qu[...,0:1]*qu[...,3:4]), + 2.0*(qu[...,3:4]*qu[...,1:2]-qu[...,0:1]*qu[...,2:3]), + 2.0*(qu[...,1:2]*qu[...,2:3]-qu[...,0:1]*qu[...,3:4]), + qq + 2.0*qu[...,2:3]**2, + 2.0*(qu[...,3:4]*qu[...,2:3]+qu[...,0:1]*qu[...,1:2]), + 2.0*(qu[...,1:2]*qu[...,3:4]+qu[...,0:1]*qu[...,2:3]), + 2.0*(qu[...,2:3]*qu[...,3:4]-qu[...,0:1]*qu[...,1:2]), + qq + 2.0*qu[...,3:4]**2, + ]).reshape(qu.shape[:-1]+(3,3)) + return om # TODO: TRANSPOSE FOR P = 1 @staticmethod def qu2eu(qu): """Quaternion to Bunge-Euler angles.""" - q03 = qu[0]**2+qu[3]**2 - q12 = qu[1]**2+qu[2]**2 - chi = np.sqrt(q03*q12) - if np.abs(chi)< 1.e-6: - eu = np.array([np.arctan2(-P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) if np.abs(q12)< 1.e-6 else \ - np.array([np.arctan2( 2.0*qu[1]*qu[2],qu[1]**2-qu[2]**2), np.pi, 0.0]) + if len(qu.shape) == 1: + q03 = qu[0]**2+qu[3]**2 + q12 = qu[1]**2+qu[2]**2 + chi = np.sqrt(q03*q12) + if np.abs(q03)< 1.e-6: + eu = np.array([np.arctan2(-P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) + elif np.abs(q12)< 1.e-6: + eu = np.array([np.arctan2( 2.0*qu[1]*qu[2],qu[1]**2-qu[2]**2), np.pi, 0.0]) + else: + eu = np.array([np.arctan2((-P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]-qu[2]*qu[3])*chi ), + np.arctan2( 2.0*chi, q03-q12 ), + np.arctan2(( P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]+qu[2]*qu[3])*chi )]) else: - eu = np.array([np.arctan2((-P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]-qu[2]*qu[3])*chi ), - np.arctan2( 2.0*chi, q03-q12 ), - np.arctan2(( P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]+qu[2]*qu[3])*chi )]) + q02 = qu[...,0:1]*qu[...,2:3] + q13 = qu[...,1:2]*qu[...,3:4] + q01 = qu[...,0:1]*qu[...,1:2] + q23 = qu[...,2:3]*qu[...,3:4] + q03_s = qu[...,0:1]**2+qu[...,3:4]**2 + q12_s = qu[...,1:2]**2+qu[...,2:3]**2 + chi = np.sqrt(q03_s*q12_s) + eu = np.where(np.abs(q12_s) < 1.0e-6, + np.block([np.arctan2(-P*2.0*qu[...,0:1]*qu[...,3:4],qu[...,0:1]**2-qu[...,3:4]**2), + np.zeros(qu.shape[:-1]+(2,))]), + np.block([np.arctan2((-P*q02+q13)*chi, (-P*q01-q23)*chi), + np.arctan2( 2.0*chi, q03_s-q12_s ), + np.arctan2(( P*q02+q13)*chi, (-P*q01+q23)*chi)]) + ) + eu = np.where(np.abs(q03_s) < 1.0e-6, + np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2), + np.ones( qu.shape[:-1]+(1,))*np.pi, + np.zeros(qu.shape[:-1]+(1,))]), + eu) # TODO: Where not needed # reduce Euler angles to definition range, i.e a lower limit of 0.0 eu[np.abs(eu)<1.e-6] = 0.0 eu = np.where(eu<0, (eu+2.0*np.pi)%np.array([2.0*np.pi,np.pi,2.0*np.pi]),eu) @@ -479,38 +515,66 @@ class Rotation: Modified version of the original formulation, should be numerically more stable """ - if iszero(qu[1]**2+qu[2]**2+qu[3]**2): # set axis to [001] if the angle is 0/360 - ax = [ 0.0, 0.0, 1.0, 0.0 ] - elif np.abs(qu[0]) > 1.e-6: - s = np.sign(qu[0])/np.sqrt(qu[1]**2+qu[2]**2+qu[3]**2) - omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) - ax = [ qu[1]*s, qu[2]*s, qu[3]*s, omega ] + if len(qu.shape) == 1: + if iszero(np.sum(qu[1:4]**2)): # set axis to [001] if the angle is 0/360 + ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) + elif np.abs(qu[0]) > 1.e-6: + s = np.sign(qu[0])/np.sqrt(qu[1]**2+qu[2]**2+qu[3]**2) + omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) + ax = ax = np.array([ qu[1]*s, qu[2]*s, qu[3]*s, omega ]) + else: + ax = ax = np.array([ qu[1], qu[2], qu[3], np.pi]) else: - ax = [ qu[1], qu[2], qu[3], np.pi] - return np.array(ax) + with np.errstate(divide='ignore'): + s = np.sign(qu[...,0:1])/np.sqrt(qu[...,1:2]**2+qu[...,2:3]**2+qu[...,3:4]**2) + omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) + + ax = np.where(qu[...,0:1] < 1.0e-6, + np.block([qu[...,1:4],np.ones(qu.shape[:-1]+(1,))*np.pi]), + np.block([qu[...,1:4]*s,omega])) + ax = np.where(np.expand_dims(np.sum(np.abs(qu[:,1:4])**2,axis=-1) < 1.0e-6,-1), + [0.0, 0.0, 1.0, 0.0], ax) # TODO: Where not needed + return ax + @staticmethod def qu2ro(qu): """Quaternion to Rodrigues-Frank vector.""" - if iszero(qu[0]): - ro = [qu[1], qu[2], qu[3], np.inf] + if len(qu.shape) == 1: + if iszero(qu[0]): + ro = np.array([qu[1], qu[2], qu[3], np.inf]) + else: + s = np.linalg.norm([qu[1],qu[2],qu[3]]) + ro = np.array([0.0,0.0,P,0.0] if iszero(s) else \ + [ qu[1]/s, qu[2]/s, qu[3]/s, np.tan(np.arccos(np.clip(qu[0],-1.0,1.0)))]) else: - s = np.linalg.norm([qu[1],qu[2],qu[3]]) - ro = [0.0,0.0,P,0.0] if iszero(s) else \ - [ qu[1]/s, qu[2]/s, qu[3]/s, np.tan(np.arccos(np.clip(qu[0],-1.0,1.0)))] - - return np.array(ro) + s = np.expand_dims(np.linalg.norm(qu[...,1:4],axis=1),-1) + ro = np.where(np.abs(s) < 1.0e-12, + [0.0,0.0,P,0.0], + np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s, + np.tan(np.arccos(np.clip(qu[:,0:1],-1.0,1.0))) + ]) + ) + ro = np.where(np.abs(qu[...,0:1]) < 1.0e-12, + np.block([qu[...,1:2], qu[...,2:3], qu[...,3:4], np.ones(qu.shape[:-1]+(1,))*np.inf]),ro) # TODO: Where not needed + return ro @staticmethod def qu2ho(qu): """Quaternion to homochoric vector.""" - if np.isclose(qu[0],1.0): - ho = np.array([ 0.0, 0.0, 0.0 ]) + if len(qu.shape) == 1: + if np.isclose(qu[0],1.0): + ho = np.zeros(3) + else: + omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) + ho = np.array([qu[1], qu[2], qu[3]]) + f = 0.75 * ( omega - np.sin(omega) ) + ho = ho/np.linalg.norm(ho) * f**(1./3.) else: - omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) - ho = np.array([qu[1], qu[2], qu[3]]) - f = 0.75 * ( omega - np.sin(omega) ) - ho = ho/np.linalg.norm(ho) * f**(1./3.) + omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) + ho = np.where(np.abs(omega) < 1.0e-12, + np.zeros(3), + qu[...,1:4]/np.linalg.norm(qu[...,1:4],axis=1).reshape(qu.shape[:-1]+(1,)) * (0.75*(omega - np.sin(omega)))**(1./3.)) return ho @staticmethod @@ -555,7 +619,7 @@ class Rotation: ax[3] = np.arccos(np.clip(t,-1.0,1.0)) if np.abs(ax[3])<1.e-6: - ax = [ 0.0, 0.0, 1.0, 0.0] + ax = np.array([ 0.0, 0.0, 1.0, 0.0]) else: w,vr = np.linalg.eig(om) # next, find the eigenvalue (1,0j) @@ -563,7 +627,7 @@ class Rotation: ax[0:3] = np.real(vr[0:3,i]) diagDelta = np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) ax[0:3] = np.where(np.abs(diagDelta)<1.e-6, ax[0:3],np.abs(ax[0:3])*np.sign(-P*diagDelta)) - return np.array(ax) + return ax @staticmethod def om2ro(om): @@ -585,57 +649,102 @@ class Rotation: @staticmethod def eu2qu(eu): """Bunge-Euler angles to quaternion.""" - ee = 0.5*eu - cPhi = np.cos(ee[1]) - sPhi = np.sin(ee[1]) - qu = np.array([ cPhi*np.cos(ee[0]+ee[2]), - -P*sPhi*np.cos(ee[0]-ee[2]), - -P*sPhi*np.sin(ee[0]-ee[2]), - -P*cPhi*np.sin(ee[0]+ee[2]) ]) - if qu[0] < 0.0: qu*=-1 + if len(eu.shape) == 1: + ee = 0.5*eu + cPhi = np.cos(ee[1]) + sPhi = np.sin(ee[1]) + qu = np.array([ cPhi*np.cos(ee[0]+ee[2]), + -P*sPhi*np.cos(ee[0]-ee[2]), + -P*sPhi*np.sin(ee[0]-ee[2]), + -P*cPhi*np.sin(ee[0]+ee[2]) ]) + if qu[0] < 0.0: qu*=-1 + else: + ee = 0.5*eu + cPhi = np.cos(ee[...,1:2]) + sPhi = np.sin(ee[...,1:2]) + qu = np.block([ cPhi*np.cos(ee[...,0:1]+ee[...,2:3]), + -P*sPhi*np.cos(ee[...,0:1]-ee[...,2:3]), + -P*sPhi*np.sin(ee[...,0:1]-ee[...,2:3]), + -P*cPhi*np.sin(ee[...,0:1]+ee[...,2:3])]) + qu[qu[...,0]<0.0]*=-1 return qu + @staticmethod def eu2om(eu): """Bunge-Euler angles to rotation matrix.""" - c = np.cos(eu) - s = np.sin(eu) + if len(eu.shape) == 1: + c = np.cos(eu) + s = np.sin(eu) - om = np.array([[+c[0]*c[2]-s[0]*s[2]*c[1], +s[0]*c[2]+c[0]*s[2]*c[1], +s[2]*s[1]], - [-c[0]*s[2]-s[0]*c[2]*c[1], -s[0]*s[2]+c[0]*c[2]*c[1], +c[2]*s[1]], - [+s[0]*s[1], -c[0]*s[1], +c[1] ]]) - - om[np.abs(om)<1.e-6] = 0.0 + om = np.array([[+c[0]*c[2]-s[0]*s[2]*c[1], +s[0]*c[2]+c[0]*s[2]*c[1], +s[2]*s[1]], + [-c[0]*s[2]-s[0]*c[2]*c[1], -s[0]*s[2]+c[0]*c[2]*c[1], +c[2]*s[1]], + [+s[0]*s[1], -c[0]*s[1], +c[1] ]]) + else: + c = np.cos(eu) + s = np.sin(eu) + om = np.block([+c[...,0:1]*c[...,2:3]-s[...,0:1]*s[...,2:3]*c[...,1:2], + +s[...,0:1]*c[...,2:3]+c[...,0:1]*s[...,2:3]*c[...,1:2], + +s[...,2:3]*s[...,1:2], + -c[...,0:1]*s[...,2:3]-s[...,0:1]*c[...,2:3]*c[...,1:2], + -s[...,0:1]*s[...,2:3]+c[...,0:1]*c[...,2:3]*c[...,1:2], + +c[...,2:3]*s[...,1:2], + +s[...,0:1]*s[...,1:2], + -c[...,0:1]*s[...,1:2], + +c[...,1:2] + ]).reshape(eu.shape[:-1]+(3,3)) + om[np.abs(om)<1.e-12] = 0.0 return om @staticmethod def eu2ax(eu): """Bunge-Euler angles to axis angle pair.""" - t = np.tan(eu[1]*0.5) - sigma = 0.5*(eu[0]+eu[2]) - delta = 0.5*(eu[0]-eu[2]) - tau = np.linalg.norm([t,np.sin(sigma)]) - alpha = np.pi if iszero(np.cos(sigma)) else \ - 2.0*np.arctan(tau/np.cos(sigma)) + if len(eu.shape) == 1: + t = np.tan(eu[1]*0.5) + sigma = 0.5*(eu[0]+eu[2]) + delta = 0.5*(eu[0]-eu[2]) + tau = np.linalg.norm([t,np.sin(sigma)]) + alpha = np.pi if iszero(np.cos(sigma)) else \ + 2.0*np.arctan(tau/np.cos(sigma)) - if np.abs(alpha)<1.e-6: - ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) + if np.abs(alpha)<1.e-6: + ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) + else: + ax = -P/tau * np.array([ t*np.cos(delta), t*np.sin(delta), np.sin(sigma) ]) # passive axis angle pair so a minus sign in front + ax = np.append(ax,alpha) + if alpha < 0.0: ax *= -1.0 # ensure alpha is positive else: - ax = -P/tau * np.array([ t*np.cos(delta), t*np.sin(delta), np.sin(sigma) ]) # passive axis angle pair so a minus sign in front - ax = np.append(ax,alpha) - if alpha < 0.0: ax *= -1.0 # ensure alpha is positive + t = np.tan(eu[...,1:2]*0.5) + sigma = 0.5*(eu[...,0:1]+eu[...,2:3]) + delta = 0.5*(eu[...,0:1]-eu[...,2:3]) + tau = np.linalg.norm(np.block([t,np.sin(sigma)]),axis=-1).reshape(-1,1) + alpha = np.where(np.abs(np.cos(sigma))<1.e-12,np.pi,2.0*np.arctan(tau/np.cos(sigma))) + ax = np.where(np.broadcast_to(np.abs(alpha)<1.0e-12,eu.shape[:-1]+(4,)), + [0.0,0.0,1.0,0.0], + np.block([-P/tau*t*np.cos(delta), + -P/tau*t*np.sin(delta), + -P/tau* np.sin(sigma), + alpha + ])) + ax[(alpha<0.0).squeeze()] *=-1 return ax @staticmethod def eu2ro(eu): """Bunge-Euler angles to Rodrigues-Frank vector.""" - ro = Rotation.eu2ax(eu) # convert to axis angle pair representation - if ro[3] >= np.pi: # Differs from original implementation. check convention 5 - ro[3] = np.inf - elif iszero(ro[3]): - ro = np.array([ 0.0, 0.0, P, 0.0 ]) + if len(eu.shape) == 1: + ro = Rotation.eu2ax(eu) # convert to axis angle pair representation + if ro[3] >= np.pi: # Differs from original implementation. check convention 5 + ro[3] = np.inf + elif iszero(ro[3]): + ro = np.array([ 0.0, 0.0, P, 0.0 ]) + else: + ro[3] = np.tan(ro[3]*0.5) else: - ro[3] = np.tan(ro[3]*0.5) + ax = Rotation.eu2ax(eu) + ro = np.block([ax[:,:3],np.tan(ax[:,3:4]*.5)]) + ro[ax[:,3]>=np.pi,3] = np.inf + ro[np.abs(ax[:,3])<1.e-16] = [ 0.0, 0.0, P, 0.0 ] return ro @staticmethod @@ -664,9 +773,7 @@ class Rotation: else: c = np.cos(ax[...,3:4]*.5) s = np.sin(ax[...,3:4]*.5) - qu = np.where(np.abs(ax[...,3:4])<1.e-12, - [1.0, 0.0, 0.0, 0.0], - np.block([c, ax[...,:3]*s])) + qu = np.where(np.abs(ax[...,3:4])<1.e-12,[1.0, 0.0, 0.0, 0.0],np.block([c, ax[...,:3]*s])) return qu @staticmethod @@ -687,7 +794,7 @@ class Rotation: c = np.cos(ax[...,3:4]) s = np.sin(ax[...,3:4]) omc = 1. -c - ax = np.block([c+omc*ax[...,0:1]**2, + om = np.block([c+omc*ax[...,0:1]**2, omc*ax[...,0:1]*ax[...,1:2] + s*ax[...,2:3], omc*ax[...,0:1]*ax[...,2:3] - s*ax[...,1:2], omc*ax[...,0:1]*ax[...,1:2] - s*ax[...,2:3], @@ -696,7 +803,7 @@ class Rotation: omc*ax[...,0:1]*ax[...,2:3] + s*ax[...,1:2], omc*ax[...,1:2]*ax[...,2:3] - s*ax[...,0:1], c+omc*ax[...,2:3]**2]).reshape(ax.shape[:-1]+(3,3)) - return ax + return om # TODO: TRANSPOSE FOR P = 1 @staticmethod def ax2eu(ax): diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 13c474ca7..eaf614b4e 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -150,6 +150,28 @@ class TestRotation: print(m,o,rot.asQuaternion()) assert np.allclose(m,o,atol=atol) + @pytest.mark.parametrize('conversion',[Rotation.qu2om, + Rotation.qu2eu, + Rotation.qu2ax, + Rotation.qu2ro, + Rotation.qu2ho]) + def test_quaternion_vectorization(self,default,conversion): + qu = np.array([rot.asQuaternion() for rot in default]) + co = conversion(qu) + for q,c in zip(qu,co): + assert np.allclose(conversion(q),c) + + @pytest.mark.parametrize('conversion',[Rotation.eu2qu, + Rotation.eu2om, + Rotation.eu2ax, + Rotation.eu2ro, + ]) + def test_Euler_vectorization(self,default,conversion): + qu = np.array([rot.asEulers() for rot in default]) + co = conversion(qu) + for q,c in zip(qu,co): + assert np.allclose(conversion(q),c) + @pytest.mark.parametrize('conversion',[Rotation.ax2qu, Rotation.ax2om, Rotation.ax2ro, From 43e7639f778156f1c97c87dfd4001e4dd507e66f Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 9 Apr 2020 00:47:43 +0200 Subject: [PATCH 14/33] WIP: implementing orientation matrix conversions --- python/damask/_rotation.py | 24 ++++++++++++++++-------- python/tests/test_Rotation.py | 8 ++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 9296f57b4..06a400fad 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -596,19 +596,27 @@ class Rotation: @staticmethod def om2eu(om): """Rotation matrix to Bunge-Euler angles.""" - if not np.isclose(np.abs(om[2,2]),1.0,1.e-4): - zeta = 1.0/np.sqrt(1.0-om[2,2]**2) - eu = np.array([np.arctan2(om[2,0]*zeta,-om[2,1]*zeta), - np.arccos(om[2,2]), - np.arctan2(om[0,2]*zeta, om[1,2]*zeta)]) + if len(om.shape) == 2: + if not np.isclose(np.abs(om[2,2]),1.0,1.e-4): + zeta = 1.0/np.sqrt(1.0-om[2,2]**2) + eu = np.array([np.arctan2(om[2,0]*zeta,-om[2,1]*zeta), + np.arccos(om[2,2]), + np.arctan2(om[0,2]*zeta, om[1,2]*zeta)]) + else: + eu = np.array([np.arctan2( om[0,1],om[0,0]), np.pi*0.5*(1-om[2,2]),0.0]) # following the paper, not the reference implementation else: - eu = np.array([np.arctan2( om[0,1],om[0,0]), np.pi*0.5*(1-om[2,2]),0.0]) # following the paper, not the reference implementation - - # reduce Euler angles to definition range, i.e a lower limit of 0.0 + with np.errstate(divide='ignore'): + zeta = 1.0/np.sqrt(1.0-om[...,2,2:3]**2) + eu = np.block([np.arctan2(om[...,2,0:1]*zeta,-om[...,2,1:2]*zeta), + np.arccos(om[...,2,2:3]), + np.arctan2(om[...,0,2:3]*zeta,+om[...,1,2:3]*zeta) + ]) + # TODO Special case not implemented! eu[np.abs(eu)<1.e-6] = 0.0 eu = np.where(eu<0, (eu+2.0*np.pi)%np.array([2.0*np.pi,np.pi,2.0*np.pi]),eu) return eu + @staticmethod def om2ax(om): """Rotation matrix to axis angle pair.""" diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index eaf614b4e..448145c45 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -161,6 +161,14 @@ class TestRotation: for q,c in zip(qu,co): assert np.allclose(conversion(q),c) + @pytest.mark.parametrize('conversion',[Rotation.om2eu, + ]) + def test_matrix_vectorization(self,default,conversion): + qu = np.array([rot.asMatrix() for rot in default]) + co = conversion(qu) + for q,c in zip(qu,co): + assert np.allclose(conversion(q),c) + @pytest.mark.parametrize('conversion',[Rotation.eu2qu, Rotation.eu2om, Rotation.eu2ax, From cbfde73a29dbfe71d65860314a60fba119ad9687 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 9 Apr 2020 07:40:20 +0200 Subject: [PATCH 15/33] more testing and related fixes --- python/damask/_rotation.py | 108 +++++++++++++++++++--------------- python/tests/test_Rotation.py | 10 ++-- 2 files changed, 67 insertions(+), 51 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 06a400fad..b6c5ff0c7 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -474,7 +474,7 @@ class Rotation: q03 = qu[0]**2+qu[3]**2 q12 = qu[1]**2+qu[2]**2 chi = np.sqrt(q03*q12) - if np.abs(q03)< 1.e-6: + if np.abs(chi)< 1.e-6: eu = np.array([np.arctan2(-P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) elif np.abs(q12)< 1.e-6: eu = np.array([np.arctan2( 2.0*qu[1]*qu[2],qu[1]**2-qu[2]**2), np.pi, 0.0]) @@ -491,14 +491,14 @@ class Rotation: q12_s = qu[...,1:2]**2+qu[...,2:3]**2 chi = np.sqrt(q03_s*q12_s) - eu = np.where(np.abs(q12_s) < 1.0e-6, + eu = np.where(np.abs(chi) < 1.0e-6, np.block([np.arctan2(-P*2.0*qu[...,0:1]*qu[...,3:4],qu[...,0:1]**2-qu[...,3:4]**2), np.zeros(qu.shape[:-1]+(2,))]), np.block([np.arctan2((-P*q02+q13)*chi, (-P*q01-q23)*chi), np.arctan2( 2.0*chi, q03_s-q12_s ), np.arctan2(( P*q02+q13)*chi, (-P*q01+q23)*chi)]) ) - eu = np.where(np.abs(q03_s) < 1.0e-6, + eu = np.where(np.logical_and(np.abs(q03_s) < 1.0e-6, np.abs(chi) > 1.0e-6), np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2), np.ones( qu.shape[:-1]+(1,))*np.pi, np.zeros(qu.shape[:-1]+(1,))]), @@ -525,18 +525,15 @@ class Rotation: else: ax = ax = np.array([ qu[1], qu[2], qu[3], np.pi]) else: - with np.errstate(divide='ignore'): + with np.errstate(invalid='ignore',divide='ignore'): s = np.sign(qu[...,0:1])/np.sqrt(qu[...,1:2]**2+qu[...,2:3]**2+qu[...,3:4]**2) - omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) - + omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) + ax = np.where(np.expand_dims(np.sum(np.abs(qu[:,1:4])**2,axis=-1) < 1.0e-6,-1), + [0.0, 0.0, 1.0, 0.0], np.block([qu[...,1:4]*s,omega])) ax = np.where(qu[...,0:1] < 1.0e-6, - np.block([qu[...,1:4],np.ones(qu.shape[:-1]+(1,))*np.pi]), - np.block([qu[...,1:4]*s,omega])) - ax = np.where(np.expand_dims(np.sum(np.abs(qu[:,1:4])**2,axis=-1) < 1.0e-6,-1), - [0.0, 0.0, 1.0, 0.0], ax) # TODO: Where not needed + np.block([qu[...,1:4],np.ones(qu.shape[:-1]+(1,))*np.pi]),ax) # TODO: Where not needed return ax - @staticmethod def qu2ro(qu): """Quaternion to Rodrigues-Frank vector.""" @@ -548,12 +545,13 @@ class Rotation: ro = np.array([0.0,0.0,P,0.0] if iszero(s) else \ [ qu[1]/s, qu[2]/s, qu[3]/s, np.tan(np.arccos(np.clip(qu[0],-1.0,1.0)))]) else: - s = np.expand_dims(np.linalg.norm(qu[...,1:4],axis=1),-1) - ro = np.where(np.abs(s) < 1.0e-12, - [0.0,0.0,P,0.0], - np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s, - np.tan(np.arccos(np.clip(qu[:,0:1],-1.0,1.0))) - ]) + with np.errstate(invalid='ignore',divide='ignore'): + s = np.expand_dims(np.linalg.norm(qu[...,1:4],axis=1),-1) + ro = np.where(np.abs(s) < 1.0e-12, + [0.0,0.0,P,0.0], + np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s, + np.tan(np.arccos(np.clip(qu[:,0:1],-1.0,1.0))) + ]) ) ro = np.where(np.abs(qu[...,0:1]) < 1.0e-12, np.block([qu[...,1:2], qu[...,2:3], qu[...,3:4], np.ones(qu.shape[:-1]+(1,))*np.inf]),ro) # TODO: Where not needed @@ -571,10 +569,12 @@ class Rotation: f = 0.75 * ( omega - np.sin(omega) ) ho = ho/np.linalg.norm(ho) * f**(1./3.) else: - omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) - ho = np.where(np.abs(omega) < 1.0e-12, - np.zeros(3), - qu[...,1:4]/np.linalg.norm(qu[...,1:4],axis=1).reshape(qu.shape[:-1]+(1,)) * (0.75*(omega - np.sin(omega)))**(1./3.)) + with np.errstate(invalid='ignore'): + omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) + ho = np.where(np.abs(omega) < 1.0e-12, + np.zeros(3), + qu[...,1:4]/np.linalg.norm(qu[...,1:4],axis=1).reshape(qu.shape[:-1]+(1,)) \ + * (0.75*(omega - np.sin(omega)))**(1./3.)) return ho @staticmethod @@ -605,13 +605,18 @@ class Rotation: else: eu = np.array([np.arctan2( om[0,1],om[0,0]), np.pi*0.5*(1-om[2,2]),0.0]) # following the paper, not the reference implementation else: - with np.errstate(divide='ignore'): - zeta = 1.0/np.sqrt(1.0-om[...,2,2:3]**2) - eu = np.block([np.arctan2(om[...,2,0:1]*zeta,-om[...,2,1:2]*zeta), - np.arccos(om[...,2,2:3]), - np.arctan2(om[...,0,2:3]*zeta,+om[...,1,2:3]*zeta) - ]) - # TODO Special case not implemented! + with np.errstate(invalid='ignore',divide='ignore'): + zeta = 1.0/np.sqrt(1.0-om[...,2,2:3]**2) + eu = np.where(np.isclose(np.abs(om[...,2,2:3]),1.0,1e-4), + np.block([np.arctan2(om[...,0,1:2],om[...,0,0:1]), + np.pi*0.5*(1-om[...,2,2:3]), + np.zeros(om.shape[:-2]+(1,)), + ]), + np.block([np.arctan2(om[...,2,0:1]*zeta,-om[...,2,1:2]*zeta), + np.arccos(om[...,2,2:3]), + np.arctan2(om[...,0,2:3]*zeta,+om[...,1,2:3]*zeta) + ]) + ) eu[np.abs(eu)<1.e-6] = 0.0 eu = np.where(eu<0, (eu+2.0*np.pi)%np.array([2.0*np.pi,np.pi,2.0*np.pi]),eu) return eu @@ -620,22 +625,30 @@ class Rotation: @staticmethod def om2ax(om): """Rotation matrix to axis angle pair.""" - ax=np.empty(4) + if len(om.shape) == 2: + ax=np.empty(4) - # first get the rotation angle - t = 0.5*(om.trace() -1.0) - ax[3] = np.arccos(np.clip(t,-1.0,1.0)) + # first get the rotation angle + t = 0.5*(om.trace() -1.0) + ax[3] = np.arccos(np.clip(t,-1.0,1.0)) - if np.abs(ax[3])<1.e-6: - ax = np.array([ 0.0, 0.0, 1.0, 0.0]) + if np.abs(ax[3])<1.e-6: + ax = np.array([ 0.0, 0.0, 1.0, 0.0]) + else: + w,vr = np.linalg.eig(om) + # next, find the eigenvalue (1,0j) + i = np.where(np.isclose(w,1.0+0.0j))[0][0] + ax[0:3] = np.real(vr[0:3,i]) + diagDelta = np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) + ax[0:3] = np.where(np.abs(diagDelta)<1.e-6, ax[0:3],np.abs(ax[0:3])*np.sign(-P*diagDelta)) + return ax else: + diag_delta = np.block([om[...,1,2:3]-om[...,2,1:2], + om[...,2,0:1]-om[...,0,2:3], + om[...,0,1:2]-om[...,1,0:1] + ]) w,vr = np.linalg.eig(om) - # next, find the eigenvalue (1,0j) - i = np.where(np.isclose(w,1.0+0.0j))[0][0] - ax[0:3] = np.real(vr[0:3,i]) - diagDelta = np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) - ax[0:3] = np.where(np.abs(diagDelta)<1.e-6, ax[0:3],np.abs(ax[0:3])*np.sign(-P*diagDelta)) - return ax + # TODO ------------------ @staticmethod def om2ro(om): @@ -727,13 +740,14 @@ class Rotation: delta = 0.5*(eu[...,0:1]-eu[...,2:3]) tau = np.linalg.norm(np.block([t,np.sin(sigma)]),axis=-1).reshape(-1,1) alpha = np.where(np.abs(np.cos(sigma))<1.e-12,np.pi,2.0*np.arctan(tau/np.cos(sigma))) - ax = np.where(np.broadcast_to(np.abs(alpha)<1.0e-12,eu.shape[:-1]+(4,)), - [0.0,0.0,1.0,0.0], - np.block([-P/tau*t*np.cos(delta), - -P/tau*t*np.sin(delta), - -P/tau* np.sin(sigma), - alpha - ])) + with np.errstate(invalid='ignore',divide='ignore'): + ax = np.where(np.broadcast_to(np.abs(alpha)<1.0e-12,eu.shape[:-1]+(4,)), + [0.0,0.0,1.0,0.0], + np.block([-P/tau*t*np.cos(delta), + -P/tau*t*np.sin(delta), + -P/tau* np.sin(sigma), + alpha + ])) ax[(alpha<0.0).squeeze()] *=-1 return ax diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 448145c45..875a05d99 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -5,7 +5,7 @@ import numpy as np from damask import Rotation -n = 1000 +n = 1100 atol=1.e-4 scatter=1.e-2 @@ -74,10 +74,12 @@ def default(): np.array([1.0, 1.0,-1.0,-1.0])/2., np.array([1.0,-1.0,-1.0,-1.0])/2., ]) - specials += np.broadcast_to(np.random.rand(4)*scatter,specials.shape) - specials /= np.linalg.norm(specials,axis=1).reshape(-1,1) - specials[specials[:,0]<0]*=-1 + specials_scatter = specials + np.broadcast_to(np.random.rand(4)*scatter,specials.shape) + specials_scatter /= np.linalg.norm(specials_scatter,axis=1).reshape(-1,1) + specials_scatter[specials_scatter[:,0]<0]*=-1 + return [Rotation.fromQuaternion(s) for s in specials] + \ + [Rotation.fromQuaternion(s) for s in specials_scatter] + \ [Rotation.fromRandom() for _ in range(n-len(specials))] @pytest.fixture From b025c1838eebad6ac53d4bda0cfaf8a8bc73a8fa Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 9 Apr 2020 12:52:12 +0200 Subject: [PATCH 16/33] simplified --- python/damask/_rotation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index b6c5ff0c7..97f494947 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -528,7 +528,7 @@ class Rotation: with np.errstate(invalid='ignore',divide='ignore'): s = np.sign(qu[...,0:1])/np.sqrt(qu[...,1:2]**2+qu[...,2:3]**2+qu[...,3:4]**2) omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) - ax = np.where(np.expand_dims(np.sum(np.abs(qu[:,1:4])**2,axis=-1) < 1.0e-6,-1), + ax = np.where(np.sum(np.abs(qu[:,1:4])**2,axis=-1,keepdims=True) < 1.0e-6, [0.0, 0.0, 1.0, 0.0], np.block([qu[...,1:4]*s,omega])) ax = np.where(qu[...,0:1] < 1.0e-6, np.block([qu[...,1:4],np.ones(qu.shape[:-1]+(1,))*np.pi]),ax) # TODO: Where not needed @@ -541,12 +541,12 @@ class Rotation: if iszero(qu[0]): ro = np.array([qu[1], qu[2], qu[3], np.inf]) else: - s = np.linalg.norm([qu[1],qu[2],qu[3]]) + s = np.linalg.norm(qu[1:4]) ro = np.array([0.0,0.0,P,0.0] if iszero(s) else \ [ qu[1]/s, qu[2]/s, qu[3]/s, np.tan(np.arccos(np.clip(qu[0],-1.0,1.0)))]) else: with np.errstate(invalid='ignore',divide='ignore'): - s = np.expand_dims(np.linalg.norm(qu[...,1:4],axis=1),-1) + s = np.linalg.norm(qu[...,1:4],axis=-1,keepdims=True) ro = np.where(np.abs(s) < 1.0e-12, [0.0,0.0,P,0.0], np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s, @@ -573,7 +573,7 @@ class Rotation: omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) ho = np.where(np.abs(omega) < 1.0e-12, np.zeros(3), - qu[...,1:4]/np.linalg.norm(qu[...,1:4],axis=1).reshape(qu.shape[:-1]+(1,)) \ + qu[...,1:4]/np.linalg.norm(qu[...,1:4],axis=-1,keepdims=True) \ * (0.75*(omega - np.sin(omega)))**(1./3.)) return ho @@ -738,7 +738,7 @@ class Rotation: t = np.tan(eu[...,1:2]*0.5) sigma = 0.5*(eu[...,0:1]+eu[...,2:3]) delta = 0.5*(eu[...,0:1]-eu[...,2:3]) - tau = np.linalg.norm(np.block([t,np.sin(sigma)]),axis=-1).reshape(-1,1) + tau = np.linalg.norm(np.block([t,np.sin(sigma)]),axis=-1,keepdims=True) alpha = np.where(np.abs(np.cos(sigma))<1.e-12,np.pi,2.0*np.arctan(tau/np.cos(sigma))) with np.errstate(invalid='ignore',divide='ignore'): ax = np.where(np.broadcast_to(np.abs(alpha)<1.0e-12,eu.shape[:-1]+(4,)), From e502573e05a6071ce1dd74e9249a66a31a3d6910 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 9 Apr 2020 14:20:43 +0200 Subject: [PATCH 17/33] polishing --- python/damask/_rotation.py | 16 +++++++++++++--- python/tests/test_Rotation.py | 30 +++++++++++++++++++----------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 97f494947..4756e94c2 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -640,15 +640,25 @@ class Rotation: i = np.where(np.isclose(w,1.0+0.0j))[0][0] ax[0:3] = np.real(vr[0:3,i]) diagDelta = np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) - ax[0:3] = np.where(np.abs(diagDelta)<1.e-6, ax[0:3],np.abs(ax[0:3])*np.sign(-P*diagDelta)) - return ax + ax[0:3] = np.where(np.abs(diagDelta)<0, ax[0:3],np.abs(ax[0:3])*np.sign(-P*diagDelta)) else: diag_delta = np.block([om[...,1,2:3]-om[...,2,1:2], om[...,2,0:1]-om[...,0,2:3], om[...,0,1:2]-om[...,1,0:1] ]) + t = 0.5*(om.trace(axis2=-2,axis1=-1) -1.0).reshape(om.shape[:-2]+(1,)) w,vr = np.linalg.eig(om) - # TODO ------------------ + # mask duplicated real eigenvalues + w[np.isclose(w[...,0],1.0+0.0j),1:] = 0. + w[np.isclose(w[...,1],1.0+0.0j),2:] = 0. + ax = np.where(np.abs(diag_delta)<0, + np.real(vr[np.isclose(w,1.0+0.0j)]).reshape(om.shape[:-2]+(3,)), + np.real(vr[np.isclose(w,1.0+0.0j)]).reshape(om.shape[:-2]+(3,)) \ + * np.abs(diag_delta)*np.sign(-P*diag_delta)) + ax = np.block([ax,np.arccos(np.clip(t,-1.0,1.0))]) + ax[np.abs(ax[...,3])<1.e-6] = [ 0.0, 0.0, 1.0, 0.0] + return ax + @staticmethod def om2ro(om): diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 875a05d99..6bb0f5160 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -80,7 +80,7 @@ def default(): return [Rotation.fromQuaternion(s) for s in specials] + \ [Rotation.fromQuaternion(s) for s in specials_scatter] + \ - [Rotation.fromRandom() for _ in range(n-len(specials))] + [Rotation.fromRandom() for _ in range(n-len(specials)-len(specials_scatter))] @pytest.fixture def reference_dir(reference_dir_base): @@ -159,17 +159,22 @@ class TestRotation: Rotation.qu2ho]) def test_quaternion_vectorization(self,default,conversion): qu = np.array([rot.asQuaternion() for rot in default]) + #dev_null = conversion(qu.reshape(qu.shape[0]//2,-1,4)) co = conversion(qu) for q,c in zip(qu,co): - assert np.allclose(conversion(q),c) + print(q,c) + assert np.allclose(conversion(q),c) @pytest.mark.parametrize('conversion',[Rotation.om2eu, + #Rotation.om2ax, ]) def test_matrix_vectorization(self,default,conversion): - qu = np.array([rot.asMatrix() for rot in default]) - co = conversion(qu) - for q,c in zip(qu,co): - assert np.allclose(conversion(q),c) + om = np.array([rot.asMatrix() for rot in default]) + #dev_null = conversion(om.reshape(om.shape[0]//2,-1,3,3)) + co = conversion(om) + for o,c in zip(om,co): + print(o,c) + assert np.allclose(conversion(o),c) @pytest.mark.parametrize('conversion',[Rotation.eu2qu, Rotation.eu2om, @@ -177,10 +182,12 @@ class TestRotation: Rotation.eu2ro, ]) def test_Euler_vectorization(self,default,conversion): - qu = np.array([rot.asEulers() for rot in default]) - co = conversion(qu) - for q,c in zip(qu,co): - assert np.allclose(conversion(q),c) + eu = np.array([rot.asEulers() for rot in default]) + #dev_null = conversion(eu.reshape(eu.shape[0]//2,-1,3)) + co = conversion(eu) + for e,c in zip(eu,co): + print(e,c) + assert np.allclose(conversion(e),c) @pytest.mark.parametrize('conversion',[Rotation.ax2qu, Rotation.ax2om, @@ -192,4 +199,5 @@ class TestRotation: dev_null = conversion(ax.reshape(ax.shape[0]//2,-1,4)) co = conversion(ax) for a,c in zip(ax,co): - assert np.allclose(conversion(a),c) + print(a,c) + assert np.allclose(conversion(a),c) From bab3581b1194c74e5ca3c217db8d2ad1e78b0531 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 9 Apr 2020 15:01:01 +0200 Subject: [PATCH 18/33] need to transpose eigenvectors to find the correct one --- python/damask/_rotation.py | 22 ++++++++++++---------- python/tests/test_Rotation.py | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 4756e94c2..3336eb6c0 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -631,7 +631,6 @@ class Rotation: # first get the rotation angle t = 0.5*(om.trace() -1.0) ax[3] = np.arccos(np.clip(t,-1.0,1.0)) - if np.abs(ax[3])<1.e-6: ax = np.array([ 0.0, 0.0, 1.0, 0.0]) else: @@ -639,22 +638,25 @@ class Rotation: # next, find the eigenvalue (1,0j) i = np.where(np.isclose(w,1.0+0.0j))[0][0] ax[0:3] = np.real(vr[0:3,i]) - diagDelta = np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) - ax[0:3] = np.where(np.abs(diagDelta)<0, ax[0:3],np.abs(ax[0:3])*np.sign(-P*diagDelta)) + diagDelta = -P*np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) + diagDelta[np.abs(diagDelta)<1.e-6] = 1.0 + ax[0:3] = np.where(np.abs(diagDelta)<0, ax[0:3],np.abs(ax[0:3])*np.sign(diagDelta)) else: - diag_delta = np.block([om[...,1,2:3]-om[...,2,1:2], - om[...,2,0:1]-om[...,0,2:3], - om[...,0,1:2]-om[...,1,0:1] - ]) + diag_delta = -P*np.block([om[...,1,2:3]-om[...,2,1:2], + om[...,2,0:1]-om[...,0,2:3], + om[...,0,1:2]-om[...,1,0:1] + ]) + diag_delta[np.abs(diag_delta)<1.e-6] = 1.0 t = 0.5*(om.trace(axis2=-2,axis1=-1) -1.0).reshape(om.shape[:-2]+(1,)) w,vr = np.linalg.eig(om) # mask duplicated real eigenvalues w[np.isclose(w[...,0],1.0+0.0j),1:] = 0. w[np.isclose(w[...,1],1.0+0.0j),2:] = 0. + vr = np.swapaxes(vr,-1,-2) ax = np.where(np.abs(diag_delta)<0, - np.real(vr[np.isclose(w,1.0+0.0j)]).reshape(om.shape[:-2]+(3,)), - np.real(vr[np.isclose(w,1.0+0.0j)]).reshape(om.shape[:-2]+(3,)) \ - * np.abs(diag_delta)*np.sign(-P*diag_delta)) + np.real(vr[np.isclose(w,1.0+0.0j)]).reshape(om.shape[:-2]+(3,)), + np.abs(np.real(vr[np.isclose(w,1.0+0.0j)]).reshape(om.shape[:-2]+(3,))) \ + *np.sign(diag_delta)) ax = np.block([ax,np.arccos(np.clip(t,-1.0,1.0))]) ax[np.abs(ax[...,3])<1.e-6] = [ 0.0, 0.0, 1.0, 0.0] return ax diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 6bb0f5160..02d561b66 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -166,7 +166,7 @@ class TestRotation: assert np.allclose(conversion(q),c) @pytest.mark.parametrize('conversion',[Rotation.om2eu, - #Rotation.om2ax, + Rotation.om2ax, ]) def test_matrix_vectorization(self,default,conversion): om = np.array([rot.asMatrix() for rot in default]) From 3bfa2d679cb0f95998ed9746d46fa21e8f2dc9ad Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 11:36:37 +0200 Subject: [PATCH 19/33] simpler/correct logic for eu2om --- python/damask/_rotation.py | 24 ++++++++++++------------ src/rotations.f90 | 11 +++++------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 3336eb6c0..e58de982f 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -474,9 +474,9 @@ class Rotation: q03 = qu[0]**2+qu[3]**2 q12 = qu[1]**2+qu[2]**2 chi = np.sqrt(q03*q12) - if np.abs(chi)< 1.e-6: + if np.abs(q12) < 1.e-6: eu = np.array([np.arctan2(-P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) - elif np.abs(q12)< 1.e-6: + elif np.abs(q03) < 1.e-6: eu = np.array([np.arctan2( 2.0*qu[1]*qu[2],qu[1]**2-qu[2]**2), np.pi, 0.0]) else: eu = np.array([np.arctan2((-P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]-qu[2]*qu[3])*chi ), @@ -491,14 +491,14 @@ class Rotation: q12_s = qu[...,1:2]**2+qu[...,2:3]**2 chi = np.sqrt(q03_s*q12_s) - eu = np.where(np.abs(chi) < 1.0e-6, + eu = np.where(np.abs(q12_s) < 1.0e-6, np.block([np.arctan2(-P*2.0*qu[...,0:1]*qu[...,3:4],qu[...,0:1]**2-qu[...,3:4]**2), np.zeros(qu.shape[:-1]+(2,))]), np.block([np.arctan2((-P*q02+q13)*chi, (-P*q01-q23)*chi), np.arctan2( 2.0*chi, q03_s-q12_s ), np.arctan2(( P*q02+q13)*chi, (-P*q01+q23)*chi)]) ) - eu = np.where(np.logical_and(np.abs(q03_s) < 1.0e-6, np.abs(chi) > 1.0e-6), + eu = np.where(np.logical_and(np.abs(q03_s) < 1.0e-6, np.abs(q12_s) >= 1.0e-6), np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2), np.ones( qu.shape[:-1]+(1,))*np.pi, np.zeros(qu.shape[:-1]+(1,))]), @@ -550,7 +550,7 @@ class Rotation: ro = np.where(np.abs(s) < 1.0e-12, [0.0,0.0,P,0.0], np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s, - np.tan(np.arccos(np.clip(qu[:,0:1],-1.0,1.0))) + np.tan(np.arccos(np.clip(qu[...,0:1],-1.0,1.0))) ]) ) ro = np.where(np.abs(qu[...,0:1]) < 1.0e-12, @@ -686,10 +686,10 @@ class Rotation: ee = 0.5*eu cPhi = np.cos(ee[1]) sPhi = np.sin(ee[1]) - qu = np.array([ cPhi*np.cos(ee[0]+ee[2]), - -P*sPhi*np.cos(ee[0]-ee[2]), - -P*sPhi*np.sin(ee[0]-ee[2]), - -P*cPhi*np.sin(ee[0]+ee[2]) ]) + qu = np.array([ cPhi*np.cos(ee[0]+ee[2]), + -P*sPhi*np.cos(ee[0]-ee[2]), + -P*sPhi*np.sin(ee[0]-ee[2]), + -P*cPhi*np.sin(ee[0]+ee[2]) ]) if qu[0] < 0.0: qu*=-1 else: ee = 0.5*eu @@ -776,9 +776,9 @@ class Rotation: ro[3] = np.tan(ro[3]*0.5) else: ax = Rotation.eu2ax(eu) - ro = np.block([ax[:,:3],np.tan(ax[:,3:4]*.5)]) - ro[ax[:,3]>=np.pi,3] = np.inf - ro[np.abs(ax[:,3])<1.e-16] = [ 0.0, 0.0, P, 0.0 ] + ro = np.block([ax[...,:3],np.tan(ax[...,3:4]*.5)]) + ro[ax[...,3]>=np.pi,3] = np.inf + ro[np.abs(ax[...,3])<1.e-16] = [ 0.0, 0.0, P, 0.0 ] return ro @staticmethod diff --git a/src/rotations.f90 b/src/rotations.f90 index 7ce366f74..1630326ad 100644 --- a/src/rotations.f90 +++ b/src/rotations.f90 @@ -432,18 +432,17 @@ pure function qu2eu(qu) result(eu) real(pReal), intent(in), dimension(4) :: qu real(pReal), dimension(3) :: eu - real(pReal) :: q12, q03, chi, chiInv + real(pReal) :: q12, q03, chi q03 = qu(1)**2+qu(4)**2 q12 = qu(2)**2+qu(3)**2 chi = sqrt(q03*q12) - degenerated: if (dEq0(chi)) then - eu = merge([atan2(-P*2.0_pReal*qu(1)*qu(4),qu(1)**2-qu(4)**2), 0.0_pReal, 0.0_pReal], & - [atan2( 2.0_pReal*qu(2)*qu(3),qu(2)**2-qu(3)**2), PI, 0.0_pReal], & - dEq0(q12)) + degenerated: if (dEq0(q12)) then + eu = [atan2(-P*2.0_pReal*qu(1)*qu(4),qu(1)**2-qu(4)**2), 0.0_pReal, 0.0_pReal] + elseif (dEq0(q03)) then + eu = [atan2( 2.0_pReal*qu(2)*qu(3),qu(2)**2-qu(3)**2), PI, 0.0_pReal] else degenerated - chiInv = 1.0_pReal/chi eu = [atan2((-P*qu(1)*qu(3)+qu(2)*qu(4))*chi, (-P*qu(1)*qu(2)-qu(3)*qu(4))*chi ), & atan2( 2.0_pReal*chi, q03-q12 ), & atan2(( P*qu(1)*qu(3)+qu(2)*qu(4))*chi, (-P*qu(1)*qu(2)+qu(3)*qu(4))*chi )] From 4e759d6c9869bea71229ee8a0ec50d0926024c1f Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 12:37:21 +0200 Subject: [PATCH 20/33] more tests for orientation conversion ensure that all parameters are within range and check if multidimensional arrays at least run --- python/damask/_Lambert.py | 2 +- python/tests/test_Rotation.py | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/python/damask/_Lambert.py b/python/damask/_Lambert.py index a62f1922a..59abe7f64 100644 --- a/python/damask/_Lambert.py +++ b/python/damask/_Lambert.py @@ -105,7 +105,7 @@ def ball_to_cube(ball): """ ball_ = ball/np.linalg.norm(ball)*R1 if np.isclose(np.linalg.norm(ball),R1,atol=1e-6) else ball rs = np.linalg.norm(ball_) - if rs > R1 and not np.isclose(rs,R1): + if rs > R1+1.e-9: raise ValueError('Coordinate outside of the sphere: {} {} {}.'.format(*ball)) if np.allclose(ball_,0.0,rtol=0.0,atol=1.0e-16): diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 02d561b66..b3d3d881d 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -98,7 +98,7 @@ class TestRotation: if np.isclose(rot.asQuaternion()[0],0.0,atol=atol): ok = ok or np.allclose(m*-1.,o,atol=atol) print(m,o,rot.asQuaternion()) - assert ok + assert ok and np.isclose(np.linalg.norm(o),1.0) def test_AxisAngle(self,default): for rot in default: @@ -111,7 +111,7 @@ class TestRotation: sum_phi = np.unwrap([m[0]+m[2],o[0]+o[2]]) ok = ok or np.isclose(sum_phi[0],sum_phi[1],atol=atol) print(m,o,rot.asQuaternion()) - assert ok + assert ok and (np.zeros(3)-1.e-9 <= o).all() and (o <= np.array([np.pi*2.,np.pi,np.pi*2.])+1.e-9).all() def test_Matrix(self,default): for rot in default: @@ -121,36 +121,41 @@ class TestRotation: if np.isclose(m[3],np.pi,atol=atol): ok = ok or np.allclose(m*np.array([-1.,-1.,-1.,1.]),o,atol=atol) print(m,o,rot.asQuaternion()) - assert ok + assert ok and np.isclose(np.linalg.norm(o[:3]),1.0) and o[3]<=np.pi++1.e-9 def test_Rodriques(self,default): for rot in default: m = rot.asMatrix() o = Rotation.fromRodrigues(rot.asRodrigues()).asMatrix() + ok = np.allclose(m,o,atol=atol) print(m,o) - assert np.allclose(m,o,atol=atol) + assert ok and np.isclose(np.linalg.det(o),1.0) def test_Homochoric(self,default): + cutoff = np.tan(np.pi*.5*(1.-1e-4)) for rot in default: m = rot.asRodrigues() o = Rotation.fromHomochoric(rot.asHomochoric()).asRodrigues() - ok = np.allclose(np.clip(m,None,1.e9),np.clip(o,None,1.e9),atol=atol) - print(m,o,rot.asQuaternion()) + ok = np.allclose(np.clip(m,None,cutoff),np.clip(o,None,cutoff),atol=atol) ok = ok or np.isclose(m[3],0.0,atol=atol) + print(m,o,rot.asQuaternion()) + assert ok and np.isclose(np.linalg.norm(o[:3]),1.0) def test_Cubochoric(self,default): for rot in default: m = rot.asHomochoric() o = Rotation.fromCubochoric(rot.asCubochoric()).asHomochoric() + ok = np.allclose(m,o,atol=atol) print(m,o,rot.asQuaternion()) - assert np.allclose(m,o,atol=atol) + assert ok and np.linalg.norm(o) < (3.*np.pi/4.)**(1./3.) + 1.e-9 def test_Quaternion(self,default): for rot in default: m = rot.asCubochoric() o = Rotation.fromQuaternion(rot.asQuaternion()).asCubochoric() + ok = np.allclose(m,o,atol=atol) print(m,o,rot.asQuaternion()) - assert np.allclose(m,o,atol=atol) + assert ok and o.max() < np.pi**(2./3.)*0.5+1.e-9 @pytest.mark.parametrize('conversion',[Rotation.qu2om, Rotation.qu2eu, @@ -159,7 +164,7 @@ class TestRotation: Rotation.qu2ho]) def test_quaternion_vectorization(self,default,conversion): qu = np.array([rot.asQuaternion() for rot in default]) - #dev_null = conversion(qu.reshape(qu.shape[0]//2,-1,4)) + dev_null = conversion(qu.reshape(qu.shape[0]//2,-1,4)) co = conversion(qu) for q,c in zip(qu,co): print(q,c) @@ -170,7 +175,7 @@ class TestRotation: ]) def test_matrix_vectorization(self,default,conversion): om = np.array([rot.asMatrix() for rot in default]) - #dev_null = conversion(om.reshape(om.shape[0]//2,-1,3,3)) + dev_null = conversion(om.reshape(om.shape[0]//2,-1,3,3)) co = conversion(om) for o,c in zip(om,co): print(o,c) @@ -183,7 +188,7 @@ class TestRotation: ]) def test_Euler_vectorization(self,default,conversion): eu = np.array([rot.asEulers() for rot in default]) - #dev_null = conversion(eu.reshape(eu.shape[0]//2,-1,3)) + dev_null = conversion(eu.reshape(eu.shape[0]//2,-1,3)) co = conversion(eu) for e,c in zip(eu,co): print(e,c) From 99655c9f61060547ff76cfde2121e324f5587cb9 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 13:57:05 +0200 Subject: [PATCH 21/33] more vectorized functions --- python/damask/_rotation.py | 45 ++++++++++++++++++++++------------- python/tests/test_Rotation.py | 32 ++++++++++++++++++++----- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index e58de982f..56c5d0593 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -767,8 +767,8 @@ class Rotation: def eu2ro(eu): """Bunge-Euler angles to Rodrigues-Frank vector.""" if len(eu.shape) == 1: - ro = Rotation.eu2ax(eu) # convert to axis angle pair representation - if ro[3] >= np.pi: # Differs from original implementation. check convention 5 + ro = Rotation.eu2ax(eu) # convert to axis angle pair representation + if ro[3] >= np.pi: # Differs from original implementation. check convention 5 ro[3] = np.inf elif iszero(ro[3]): ro = np.array([ 0.0, 0.0, P, 0.0 ]) @@ -902,27 +902,38 @@ class Rotation: @staticmethod def ro2ax(ro): """Rodrigues-Frank vector to axis angle pair.""" - ta = ro[3] - - if iszero(ta): - ax = [ 0.0, 0.0, 1.0, 0.0 ] - elif not np.isfinite(ta): - ax = [ ro[0], ro[1], ro[2], np.pi ] + if len(ro.shape) == 1: + if np.abs(ro[3]) < 1.e-6: + ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) + elif not np.isfinite(ro[3]): + ax = np.array([ ro[0], ro[1], ro[2], np.pi ]) + else: + angle = 2.0*np.arctan(ro[3]) + ta = np.linalg.norm(ro[0:3]) + ax = np.array([ ro[0]*ta, ro[1]*ta, ro[2]*ta, angle ]) else: - angle = 2.0*np.arctan(ta) - ta = 1.0/np.linalg.norm(ro[0:3]) - ax = [ ro[0]/ta, ro[1]/ta, ro[2]/ta, angle ] - return np.array(ax) + with np.errstate(invalid='ignore',divide='ignore'): + ax = np.where(np.isfinite(ro[...,3:4]), + np.block([ro[...,0:3]*np.linalg.norm(ro[...,0:3],axis=-1,keepdims=True),2.*np.arctan(ro[...,3:4])]), + np.block([ro[...,0:3],np.broadcast_to(np.pi,ro[...,3:4].shape)])) + ax[np.abs(ro[...,3]) < 1.e-6] = np.array([ 0.0, 0.0, 1.0, 0.0 ]) + return ax @staticmethod def ro2ho(ro): """Rodrigues-Frank vector to homochoric vector.""" - if iszero(np.sum(ro[0:3]**2.0)): - ho = [ 0.0, 0.0, 0.0 ] + if len(ro.shape) == 1: + if np.sum(ro[0:3]**2.0) < 1.e-6: + ho = np.zeros(3) + else: + f = 2.0*np.arctan(ro[3]) -np.sin(2.0*np.arctan(ro[3])) if np.isfinite(ro[3]) else np.pi + ho = ro[0:3] * (0.75*f)**(1.0/3.0) else: - f = 2.0*np.arctan(ro[3]) -np.sin(2.0*np.arctan(ro[3])) if np.isfinite(ro[3]) else np.pi - ho = ro[0:3] * (0.75*f)**(1.0/3.0) - return np.array(ho) + f = np.where(np.isfinite(ro[...,3:4]),2.0*np.arctan(ro[...,3:4]) -np.sin(2.0*np.arctan(ro[...,3:4])),np.pi) + ho = np.where(np.broadcast_to(np.sum(ro[...,0:3]**2.0,axis=-1,keepdims=True) < 1.e-6,ro[...,0:3].shape), + np.zeros(3), ro[...,0:3]* (0.75*f)**(1.0/3.0)) + + return ho @staticmethod def ro2cu(ro): diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index b3d3d881d..544e088c7 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -123,7 +123,7 @@ class TestRotation: print(m,o,rot.asQuaternion()) assert ok and np.isclose(np.linalg.norm(o[:3]),1.0) and o[3]<=np.pi++1.e-9 - def test_Rodriques(self,default): + def test_Rodrigues(self,default): for rot in default: m = rot.asMatrix() o = Rotation.fromRodrigues(rot.asRodrigues()).asMatrix() @@ -164,18 +164,21 @@ class TestRotation: Rotation.qu2ho]) def test_quaternion_vectorization(self,default,conversion): qu = np.array([rot.asQuaternion() for rot in default]) - dev_null = conversion(qu.reshape(qu.shape[0]//2,-1,4)) + conversion(qu.reshape(qu.shape[0]//2,-1,4)) co = conversion(qu) for q,c in zip(qu,co): print(q,c) assert np.allclose(conversion(q),c) - @pytest.mark.parametrize('conversion',[Rotation.om2eu, + @pytest.mark.parametrize('conversion',[Rotation.om2qu, + Rotation.om2eu, Rotation.om2ax, + Rotation.om2ro, + Rotation.om2ho, ]) def test_matrix_vectorization(self,default,conversion): om = np.array([rot.asMatrix() for rot in default]) - dev_null = conversion(om.reshape(om.shape[0]//2,-1,3,3)) + conversion(om.reshape(om.shape[0]//2,-1,3,3)) co = conversion(om) for o,c in zip(om,co): print(o,c) @@ -185,10 +188,11 @@ class TestRotation: Rotation.eu2om, Rotation.eu2ax, Rotation.eu2ro, + Rotation.eu2ho, ]) def test_Euler_vectorization(self,default,conversion): eu = np.array([rot.asEulers() for rot in default]) - dev_null = conversion(eu.reshape(eu.shape[0]//2,-1,3)) + conversion(eu.reshape(eu.shape[0]//2,-1,3)) co = conversion(eu) for e,c in zip(eu,co): print(e,c) @@ -196,13 +200,29 @@ class TestRotation: @pytest.mark.parametrize('conversion',[Rotation.ax2qu, Rotation.ax2om, + Rotation.ax2eu, Rotation.ax2ro, Rotation.ax2ho, ]) def test_axisAngle_vectorization(self,default,conversion): ax = np.array([rot.asAxisAngle() for rot in default]) - dev_null = conversion(ax.reshape(ax.shape[0]//2,-1,4)) + conversion(ax.reshape(ax.shape[0]//2,-1,4)) co = conversion(ax) for a,c in zip(ax,co): print(a,c) assert np.allclose(conversion(a),c) + + + @pytest.mark.parametrize('conversion',[Rotation.ro2qu, + Rotation.ro2om, + Rotation.ro2eu, + Rotation.ro2ax, + Rotation.ro2ho, + ]) + def test_Rodrigues_vectorization(self,default,conversion): + ro = np.array([rot.asRodrigues() for rot in default]) + conversion(ro.reshape(ro.shape[0]//2,-1,4)) + co = conversion(ro) + for r,c in zip(ro,co): + print(r,c) + assert np.allclose(conversion(r),c) From cb9daccdd7eb21808b9940815f9d56b0af35835a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 16:14:40 +0200 Subject: [PATCH 22/33] homochoric representation vectorized --- python/damask/_rotation.py | 28 ++++++++++++++++++++-------- python/tests/test_Rotation.py | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 56c5d0593..e63bd1205 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -968,19 +968,31 @@ class Rotation: +0.0001703481934140054, -0.00012062065004116828, +0.000059719705868660826, -0.00001980756723965647, +0.000003953714684212874, -0.00000036555001439719544]) - # normalize h and store the magnitude - hmag_squared = np.sum(ho**2.) - if iszero(hmag_squared): - ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) - else: - hm = hmag_squared + if len(ho.shape) == 1: + # normalize h and store the magnitude + hmag_squared = np.sum(ho**2.) + if iszero(hmag_squared): + ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) + else: + hm = hmag_squared - # convert the magnitude to the rotation angle + # convert the magnitude to the rotation angle + s = tfit[0] + tfit[1] * hmag_squared + for i in range(2,16): + hm *= hmag_squared + s += tfit[i] * hm + ax = np.append(ho/np.sqrt(hmag_squared),2.0*np.arccos(np.clip(s,-1.0,1.0))) + else: + hmag_squared = np.sum(ho**2.,axis=-1,keepdims=True) + hm = hmag_squared.copy() s = tfit[0] + tfit[1] * hmag_squared for i in range(2,16): hm *= hmag_squared s += tfit[i] * hm - ax = np.append(ho/np.sqrt(hmag_squared),2.0*np.arccos(np.clip(s,-1.0,1.0))) + with np.errstate(invalid='ignore',divide='ignore'): + ax = np.where(np.broadcast_to(np.abs(hmag_squared)<1.e-6,ho.shape[:-1]+(4,)), + np.array([ 0.0, 0.0, 1.0, 0.0 ]), + np.block([ho/np.sqrt(hmag_squared),2.0*np.arccos(np.clip(s,-1.0,1.0))])) return ax @staticmethod diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 544e088c7..2ac819f4c 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -226,3 +226,17 @@ class TestRotation: for r,c in zip(ro,co): print(r,c) assert np.allclose(conversion(r),c) + + @pytest.mark.parametrize('conversion',[Rotation.ho2qu, + Rotation.ho2om, + Rotation.ho2eu, + Rotation.ho2ax, + Rotation.ho2ro, + ]) + def test_homochoric_vectorization(self,default,conversion): + ho = np.array([rot.asHomochoric() for rot in default]) + conversion(ho.reshape(ho.shape[0]//2,-1,3)) + co = conversion(ho) + for h,c in zip(ho,co): + print(h,c) + assert np.allclose(conversion(h),c) From 6e6f512c385bb3158cbaea08b885b1c9dd236f8e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 17:05:52 +0200 Subject: [PATCH 23/33] fixed test --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 377e4f97a..85e4cb8e5 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 377e4f97a31ca1aa39a0645430c82bed40158001 +Subproject commit 85e4cb8e5b1bd437a6649457457cfd67b64e5a51 From 51104bfc13650f1637a4a675038face3cdb659ea Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 17:19:12 +0200 Subject: [PATCH 24/33] do not transpose for the standard case --- python/damask/_rotation.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index e63bd1205..a20db5aa8 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -446,13 +446,13 @@ class Rotation: qq = qu[0]**2-(qu[1]**2 + qu[2]**2 + qu[3]**2) om = np.diag(qq + 2.0*np.array([qu[1],qu[2],qu[3]])**2) - om[1,0] = 2.0*(qu[2]*qu[1]+qu[0]*qu[3]) - om[0,1] = 2.0*(qu[1]*qu[2]-qu[0]*qu[3]) - om[2,1] = 2.0*(qu[3]*qu[2]+qu[0]*qu[1]) - om[1,2] = 2.0*(qu[2]*qu[3]-qu[0]*qu[1]) - om[0,2] = 2.0*(qu[1]*qu[3]+qu[0]*qu[2]) - om[2,0] = 2.0*(qu[3]*qu[1]-qu[0]*qu[2]) - return om if P > 0.0 else om.T + om[0,1] = 2.0*(qu[2]*qu[1]+qu[0]*qu[3]) + om[1,0] = 2.0*(qu[1]*qu[2]-qu[0]*qu[3]) + om[1,2] = 2.0*(qu[3]*qu[2]+qu[0]*qu[1]) + om[2,1] = 2.0*(qu[2]*qu[3]-qu[0]*qu[1]) + om[2,0] = 2.0*(qu[1]*qu[3]+qu[0]*qu[2]) + om[0,2] = 2.0*(qu[3]*qu[1]-qu[0]*qu[2]) + return om if P < 0.0 else om.T else: qq = qu[...,0:1]**2-(qu[...,1:2]**2 + qu[...,2:3]**2 + qu[...,3:4]**2) om = np.block([qq + 2.0*qu[...,1:2]**2, From fac33ec408b94ab9a193cbc9176a90f1dcdd6b63 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 17:23:54 +0200 Subject: [PATCH 25/33] polishing --- python/damask/_rotation.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index a20db5aa8..42b097a78 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -452,7 +452,6 @@ class Rotation: om[2,1] = 2.0*(qu[2]*qu[3]-qu[0]*qu[1]) om[2,0] = 2.0*(qu[1]*qu[3]+qu[0]*qu[2]) om[0,2] = 2.0*(qu[3]*qu[1]-qu[0]*qu[2]) - return om if P < 0.0 else om.T else: qq = qu[...,0:1]**2-(qu[...,1:2]**2 + qu[...,2:3]**2 + qu[...,3:4]**2) om = np.block([qq + 2.0*qu[...,1:2]**2, @@ -465,7 +464,7 @@ class Rotation: 2.0*(qu[...,2:3]*qu[...,3:4]-qu[...,0:1]*qu[...,1:2]), qq + 2.0*qu[...,3:4]**2, ]).reshape(qu.shape[:-1]+(3,3)) - return om # TODO: TRANSPOSE FOR P = 1 + return om if P < 0.0 else np.swapaxes(om,(-1,-2)) @staticmethod def qu2eu(qu): @@ -823,7 +822,6 @@ class Rotation: q = omc*ax[idx[0]] * ax[idx[1]] om[idx[0],idx[1]] = q + s*ax[idx[2]] om[idx[1],idx[0]] = q - s*ax[idx[2]] - return om if P < 0.0 else om.T else: c = np.cos(ax[...,3:4]) s = np.sin(ax[...,3:4]) @@ -837,7 +835,7 @@ class Rotation: omc*ax[...,0:1]*ax[...,2:3] + s*ax[...,1:2], omc*ax[...,1:2]*ax[...,2:3] - s*ax[...,0:1], c+omc*ax[...,2:3]**2]).reshape(ax.shape[:-1]+(3,3)) - return om # TODO: TRANSPOSE FOR P = 1 + return om if P < 0.0 else np.swapaxes(om,(-1,-2)) @staticmethod def ax2eu(ax): @@ -989,7 +987,7 @@ class Rotation: for i in range(2,16): hm *= hmag_squared s += tfit[i] * hm - with np.errstate(invalid='ignore',divide='ignore'): + with np.errstate(invalid='ignore'): ax = np.where(np.broadcast_to(np.abs(hmag_squared)<1.e-6,ho.shape[:-1]+(4,)), np.array([ 0.0, 0.0, 1.0, 0.0 ]), np.block([ho/np.sqrt(hmag_squared),2.0*np.arccos(np.clip(s,-1.0,1.0))])) From 296a75d452741386ff1c5710eadae60ddfcf130d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 17:58:15 +0200 Subject: [PATCH 26/33] where not needed --- python/damask/_rotation.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 42b097a78..e728ab6ed 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -501,7 +501,7 @@ class Rotation: np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2), np.ones( qu.shape[:-1]+(1,))*np.pi, np.zeros(qu.shape[:-1]+(1,))]), - eu) # TODO: Where not needed + eu) # TODO: Where can be nested # reduce Euler angles to definition range, i.e a lower limit of 0.0 eu[np.abs(eu)<1.e-6] = 0.0 eu = np.where(eu<0, (eu+2.0*np.pi)%np.array([2.0*np.pi,np.pi,2.0*np.pi]),eu) @@ -515,7 +515,7 @@ class Rotation: Modified version of the original formulation, should be numerically more stable """ if len(qu.shape) == 1: - if iszero(np.sum(qu[1:4]**2)): # set axis to [001] if the angle is 0/360 + if np.abs(np.sum(qu[1:4]**2)) < 1.e-6: # set axis to [001] if the angle is 0/360 ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) elif np.abs(qu[0]) > 1.e-6: s = np.sign(qu[0])/np.sqrt(qu[1]**2+qu[2]**2+qu[3]**2) @@ -546,14 +546,13 @@ class Rotation: else: with np.errstate(invalid='ignore',divide='ignore'): s = np.linalg.norm(qu[...,1:4],axis=-1,keepdims=True) - ro = np.where(np.abs(s) < 1.0e-12, - [0.0,0.0,P,0.0], + ro = np.where(np.broadcast_to(np.abs(qu[...,0:1]) < 1.0e-12,qu.shape), + np.block([qu[...,1:2], qu[...,2:3], qu[...,3:4], np.ones(qu.shape[:-1]+(1,))*np.inf]), np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s, np.tan(np.arccos(np.clip(qu[...,0:1],-1.0,1.0))) ]) ) - ro = np.where(np.abs(qu[...,0:1]) < 1.0e-12, - np.block([qu[...,1:2], qu[...,2:3], qu[...,3:4], np.ones(qu.shape[:-1]+(1,))*np.inf]),ro) # TODO: Where not needed + ro[np.abs(s).squeeze(-1) < 1.0e-12] = [0.0,0.0,P,0.0] return ro @staticmethod @@ -1033,4 +1032,7 @@ class Rotation: @staticmethod def cu2ho(cu): """Cubochoric vector to homochoric vector.""" - return cube_to_ball(cu) + if len(cu.shape) == 1: + return cube_to_ball(cu) + else: + raise NotImplementedError From 8c61f67e3453249f02b40ef82ea8ad97781cc395 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 18:14:15 +0200 Subject: [PATCH 27/33] cleaning --- python/damask/_rotation.py | 10 +++++----- python/tests/test_Rotation.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index e728ab6ed..4c164376c 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -517,7 +517,7 @@ class Rotation: if len(qu.shape) == 1: if np.abs(np.sum(qu[1:4]**2)) < 1.e-6: # set axis to [001] if the angle is 0/360 ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) - elif np.abs(qu[0]) > 1.e-6: + elif qu[0] > 1.e-6: s = np.sign(qu[0])/np.sqrt(qu[1]**2+qu[2]**2+qu[3]**2) omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) ax = ax = np.array([ qu[1]*s, qu[2]*s, qu[3]*s, omega ]) @@ -527,10 +527,10 @@ class Rotation: with np.errstate(invalid='ignore',divide='ignore'): s = np.sign(qu[...,0:1])/np.sqrt(qu[...,1:2]**2+qu[...,2:3]**2+qu[...,3:4]**2) omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) - ax = np.where(np.sum(np.abs(qu[:,1:4])**2,axis=-1,keepdims=True) < 1.0e-6, - [0.0, 0.0, 1.0, 0.0], np.block([qu[...,1:4]*s,omega])) - ax = np.where(qu[...,0:1] < 1.0e-6, - np.block([qu[...,1:4],np.ones(qu.shape[:-1]+(1,))*np.pi]),ax) # TODO: Where not needed + ax = np.where(np.broadcast_to(qu[...,0:1] < 1.0e-6,qu.shape), + np.block([qu[...,1:4],np.ones(qu.shape[:-1]+(1,))*np.pi]), + np.block([qu[...,1:4]*s,omega])) + ax[np.sum(np.abs(qu[...,1:4])**2,axis=-1) < 1.0e-6,] = [0.0, 0.0, 1.0, 0.0] return ax @staticmethod diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 2ac819f4c..0007a2aec 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -167,7 +167,7 @@ class TestRotation: conversion(qu.reshape(qu.shape[0]//2,-1,4)) co = conversion(qu) for q,c in zip(qu,co): - print(q,c) + #print(q,c) assert np.allclose(conversion(q),c) @pytest.mark.parametrize('conversion',[Rotation.om2qu, From c57f96cd6eb3e369f5ce8bd4a8b3fab2ca2d51dd Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 18:32:06 +0200 Subject: [PATCH 28/33] also missing --- python/damask/_rotation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 4c164376c..b2a08d77b 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -1000,7 +1000,10 @@ class Rotation: @staticmethod def ho2cu(ho): """Homochoric vector to cubochoric vector.""" - return ball_to_cube(ho) + if len(ho.shape) == 1: + return ball_to_cube(ho) + else: + raise NotImplementedError #---------- Cubochoric ---------- From c0c37fe6a5c95cf823011b0a3adb54c389c83eef Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 20:42:46 +0200 Subject: [PATCH 29/33] polishing --- python/damask/_rotation.py | 8 ++++---- python/tests/test_Rotation.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index b2a08d77b..c4f0b3c23 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -502,7 +502,7 @@ class Rotation: np.ones( qu.shape[:-1]+(1,))*np.pi, np.zeros(qu.shape[:-1]+(1,))]), eu) # TODO: Where can be nested - # reduce Euler angles to definition range, i.e a lower limit of 0.0 + # reduce Euler angles to definition range eu[np.abs(eu)<1.e-6] = 0.0 eu = np.where(eu<0, (eu+2.0*np.pi)%np.array([2.0*np.pi,np.pi,2.0*np.pi]),eu) return eu @@ -530,7 +530,7 @@ class Rotation: ax = np.where(np.broadcast_to(qu[...,0:1] < 1.0e-6,qu.shape), np.block([qu[...,1:4],np.ones(qu.shape[:-1]+(1,))*np.pi]), np.block([qu[...,1:4]*s,omega])) - ax[np.sum(np.abs(qu[...,1:4])**2,axis=-1) < 1.0e-6,] = [0.0, 0.0, 1.0, 0.0] + ax[np.sum(np.abs(qu[...,1:4])**2,axis=-1) < 1.0e-6,] = [0.0, 0.0, 1.0, 0.0] return ax @staticmethod @@ -559,10 +559,10 @@ class Rotation: def qu2ho(qu): """Quaternion to homochoric vector.""" if len(qu.shape) == 1: - if np.isclose(qu[0],1.0): + omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) + if np.abs(omega) < 1.0e-12: ho = np.zeros(3) else: - omega = 2.0 * np.arccos(np.clip(qu[0],-1.0,1.0)) ho = np.array([qu[1], qu[2], qu[3]]) f = 0.75 * ( omega - np.sin(omega) ) ho = ho/np.linalg.norm(ho) * f**(1./3.) diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index 0007a2aec..2ac819f4c 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -167,7 +167,7 @@ class TestRotation: conversion(qu.reshape(qu.shape[0]//2,-1,4)) co = conversion(qu) for q,c in zip(qu,co): - #print(q,c) + print(q,c) assert np.allclose(conversion(q),c) @pytest.mark.parametrize('conversion',[Rotation.om2qu, From f41a47ce8b223210a81ece0fa549816d7e8b6b39 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 11 Apr 2020 23:27:25 +0200 Subject: [PATCH 30/33] polishing and slightly stricter tolerances --- python/damask/_rotation.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index c4f0b3c23..e83c5fd1d 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -473,9 +473,9 @@ class Rotation: q03 = qu[0]**2+qu[3]**2 q12 = qu[1]**2+qu[2]**2 chi = np.sqrt(q03*q12) - if np.abs(q12) < 1.e-6: + if np.abs(q12) < 1.e-8: eu = np.array([np.arctan2(-P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) - elif np.abs(q03) < 1.e-6: + elif np.abs(q03) < 1.e-8: eu = np.array([np.arctan2( 2.0*qu[1]*qu[2],qu[1]**2-qu[2]**2), np.pi, 0.0]) else: eu = np.array([np.arctan2((-P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]-qu[2]*qu[3])*chi ), @@ -490,18 +490,18 @@ class Rotation: q12_s = qu[...,1:2]**2+qu[...,2:3]**2 chi = np.sqrt(q03_s*q12_s) - eu = np.where(np.abs(q12_s) < 1.0e-6, - np.block([np.arctan2(-P*2.0*qu[...,0:1]*qu[...,3:4],qu[...,0:1]**2-qu[...,3:4]**2), - np.zeros(qu.shape[:-1]+(2,))]), - np.block([np.arctan2((-P*q02+q13)*chi, (-P*q01-q23)*chi), - np.arctan2( 2.0*chi, q03_s-q12_s ), - np.arctan2(( P*q02+q13)*chi, (-P*q01+q23)*chi)]) + eu = np.where(np.abs(q12_s) < 1.0e-8, + np.block([np.arctan2(-P*2.0*qu[...,0:1]*qu[...,3:4],qu[...,0:1]**2-qu[...,3:4]**2), + np.zeros(qu.shape[:-1]+(2,))]), + np.where(np.abs(q03_s) < 1.0e-8, + np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2), + np.broadcast_to(np.pi,qu.shape[:-1]+(1,)), + np.zeros(qu.shape[:-1]+(1,))]), + np.block([np.arctan2((-P*q02+q13)*chi, (-P*q01-q23)*chi), + np.arctan2( 2.0*chi, q03_s-q12_s ), + np.arctan2(( P*q02+q13)*chi, (-P*q01+q23)*chi)]) + ) ) - eu = np.where(np.logical_and(np.abs(q03_s) < 1.0e-6, np.abs(q12_s) >= 1.0e-6), - np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2), - np.ones( qu.shape[:-1]+(1,))*np.pi, - np.zeros(qu.shape[:-1]+(1,))]), - eu) # TODO: Where can be nested # reduce Euler angles to definition range eu[np.abs(eu)<1.e-6] = 0.0 eu = np.where(eu<0, (eu+2.0*np.pi)%np.array([2.0*np.pi,np.pi,2.0*np.pi]),eu) @@ -528,7 +528,7 @@ class Rotation: s = np.sign(qu[...,0:1])/np.sqrt(qu[...,1:2]**2+qu[...,2:3]**2+qu[...,3:4]**2) omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0)) ax = np.where(np.broadcast_to(qu[...,0:1] < 1.0e-6,qu.shape), - np.block([qu[...,1:4],np.ones(qu.shape[:-1]+(1,))*np.pi]), + np.block([qu[...,1:4],np.broadcast_to(np.pi,qu.shape[:-1]+(1,))]), np.block([qu[...,1:4]*s,omega])) ax[np.sum(np.abs(qu[...,1:4])**2,axis=-1) < 1.0e-6,] = [0.0, 0.0, 1.0, 0.0] return ax @@ -547,7 +547,7 @@ class Rotation: with np.errstate(invalid='ignore',divide='ignore'): s = np.linalg.norm(qu[...,1:4],axis=-1,keepdims=True) ro = np.where(np.broadcast_to(np.abs(qu[...,0:1]) < 1.0e-12,qu.shape), - np.block([qu[...,1:2], qu[...,2:3], qu[...,3:4], np.ones(qu.shape[:-1]+(1,))*np.inf]), + np.block([qu[...,1:2], qu[...,2:3], qu[...,3:4], np.broadcast_to(np.inf,qu.shape[:-1]+(1,))]), np.block([qu[...,1:2]/s,qu[...,2:3]/s,qu[...,3:4]/s, np.tan(np.arccos(np.clip(qu[...,0:1],-1.0,1.0))) ]) @@ -805,7 +805,7 @@ class Rotation: else: c = np.cos(ax[...,3:4]*.5) s = np.sin(ax[...,3:4]*.5) - qu = np.where(np.abs(ax[...,3:4])<1.e-12,[1.0, 0.0, 0.0, 0.0],np.block([c, ax[...,:3]*s])) + qu = np.where(np.abs(ax[...,3:4])<1.e-6,[1.0, 0.0, 0.0, 0.0],np.block([c, ax[...,:3]*s])) return qu @staticmethod @@ -988,7 +988,7 @@ class Rotation: s += tfit[i] * hm with np.errstate(invalid='ignore'): ax = np.where(np.broadcast_to(np.abs(hmag_squared)<1.e-6,ho.shape[:-1]+(4,)), - np.array([ 0.0, 0.0, 1.0, 0.0 ]), + [ 0.0, 0.0, 1.0, 0.0 ], np.block([ho/np.sqrt(hmag_squared),2.0*np.arccos(np.clip(s,-1.0,1.0))])) return ax From 8aa95776d60b9dfc52ffe8b0a65759a7480288b5 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 12 Apr 2020 00:17:48 +0200 Subject: [PATCH 31/33] change to test not needed --- PRIVATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PRIVATE b/PRIVATE index 85e4cb8e5..232a094c7 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 85e4cb8e5b1bd437a6649457457cfd67b64e5a51 +Subproject commit 232a094c715bcbbd1c6652c4dc4a4a50d402b82f From 04fbc38a4b8b27f49a3c982b5946cb2da33f60a1 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 12 Apr 2020 01:19:11 +0200 Subject: [PATCH 32/33] keep namespace clean and avoid overwriting in fromXXX functions --- python/damask/_rotation.py | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index e83c5fd1d..b68ce4d8e 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -2,7 +2,7 @@ import numpy as np from ._Lambert import ball_to_cube, cube_to_ball -P = -1 +_P = -1 def iszero(a): return np.isclose(a,0.0,atol=1.0e-12,rtol=0.0) @@ -89,7 +89,7 @@ class Rotation: other_q = other.quaternion[0] other_p = other.quaternion[1:] R = self.__class__(np.append(self_q*other_q - np.dot(self_p,other_p), - self_q*other_p + other_q*self_p + P * np.cross(self_p,other_p))) + self_q*other_p + other_q*self_p + _P * np.cross(self_p,other_p))) return R.standardize() elif isinstance(other, (tuple,np.ndarray)): if isinstance(other,tuple) or other.shape == (3,): # rotate a single (3)-vector or meshgrid @@ -97,7 +97,7 @@ class Rotation: B = 2.0 * ( self.quaternion[1]*other[0] + self.quaternion[2]*other[1] + self.quaternion[3]*other[2]) - C = 2.0 * P*self.quaternion[0] + C = 2.0 * _P*self.quaternion[0] return np.array([ A*other[0] + B*self.quaternion[1] + C*(self.quaternion[2]*other[2] - self.quaternion[3]*other[1]), @@ -464,7 +464,7 @@ class Rotation: 2.0*(qu[...,2:3]*qu[...,3:4]-qu[...,0:1]*qu[...,1:2]), qq + 2.0*qu[...,3:4]**2, ]).reshape(qu.shape[:-1]+(3,3)) - return om if P < 0.0 else np.swapaxes(om,(-1,-2)) + return om if _P < 0.0 else np.swapaxes(om,(-1,-2)) @staticmethod def qu2eu(qu): @@ -474,13 +474,13 @@ class Rotation: q12 = qu[1]**2+qu[2]**2 chi = np.sqrt(q03*q12) if np.abs(q12) < 1.e-8: - eu = np.array([np.arctan2(-P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) + eu = np.array([np.arctan2(-_P*2.0*qu[0]*qu[3],qu[0]**2-qu[3]**2), 0.0, 0.0]) elif np.abs(q03) < 1.e-8: eu = np.array([np.arctan2( 2.0*qu[1]*qu[2],qu[1]**2-qu[2]**2), np.pi, 0.0]) else: - eu = np.array([np.arctan2((-P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]-qu[2]*qu[3])*chi ), + eu = np.array([np.arctan2((-_P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-_P*qu[0]*qu[1]-qu[2]*qu[3])*chi ), np.arctan2( 2.0*chi, q03-q12 ), - np.arctan2(( P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-P*qu[0]*qu[1]+qu[2]*qu[3])*chi )]) + np.arctan2(( _P*qu[0]*qu[2]+qu[1]*qu[3])*chi, (-_P*qu[0]*qu[1]+qu[2]*qu[3])*chi )]) else: q02 = qu[...,0:1]*qu[...,2:3] q13 = qu[...,1:2]*qu[...,3:4] @@ -491,15 +491,15 @@ class Rotation: chi = np.sqrt(q03_s*q12_s) eu = np.where(np.abs(q12_s) < 1.0e-8, - np.block([np.arctan2(-P*2.0*qu[...,0:1]*qu[...,3:4],qu[...,0:1]**2-qu[...,3:4]**2), + np.block([np.arctan2(-_P*2.0*qu[...,0:1]*qu[...,3:4],qu[...,0:1]**2-qu[...,3:4]**2), np.zeros(qu.shape[:-1]+(2,))]), np.where(np.abs(q03_s) < 1.0e-8, np.block([np.arctan2( 2.0*qu[...,1:2]*qu[...,2:3],qu[...,1:2]**2-qu[...,2:3]**2), np.broadcast_to(np.pi,qu.shape[:-1]+(1,)), np.zeros(qu.shape[:-1]+(1,))]), - np.block([np.arctan2((-P*q02+q13)*chi, (-P*q01-q23)*chi), + np.block([np.arctan2((-_P*q02+q13)*chi, (-_P*q01-q23)*chi), np.arctan2( 2.0*chi, q03_s-q12_s ), - np.arctan2(( P*q02+q13)*chi, (-P*q01+q23)*chi)]) + np.arctan2(( _P*q02+q13)*chi, (-_P*q01+q23)*chi)]) ) ) # reduce Euler angles to definition range @@ -541,7 +541,7 @@ class Rotation: ro = np.array([qu[1], qu[2], qu[3], np.inf]) else: s = np.linalg.norm(qu[1:4]) - ro = np.array([0.0,0.0,P,0.0] if iszero(s) else \ + ro = np.array([0.0,0.0,_P,0.0] if iszero(s) else \ [ qu[1]/s, qu[2]/s, qu[3]/s, np.tan(np.arccos(np.clip(qu[0],-1.0,1.0)))]) else: with np.errstate(invalid='ignore',divide='ignore'): @@ -552,7 +552,7 @@ class Rotation: np.tan(np.arccos(np.clip(qu[...,0:1],-1.0,1.0))) ]) ) - ro[np.abs(s).squeeze(-1) < 1.0e-12] = [0.0,0.0,P,0.0] + ro[np.abs(s).squeeze(-1) < 1.0e-12] = [0.0,0.0,_P,0.0] return ro @staticmethod @@ -636,11 +636,11 @@ class Rotation: # next, find the eigenvalue (1,0j) i = np.where(np.isclose(w,1.0+0.0j))[0][0] ax[0:3] = np.real(vr[0:3,i]) - diagDelta = -P*np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) + diagDelta = -_P*np.array([om[1,2]-om[2,1],om[2,0]-om[0,2],om[0,1]-om[1,0]]) diagDelta[np.abs(diagDelta)<1.e-6] = 1.0 ax[0:3] = np.where(np.abs(diagDelta)<0, ax[0:3],np.abs(ax[0:3])*np.sign(diagDelta)) else: - diag_delta = -P*np.block([om[...,1,2:3]-om[...,2,1:2], + diag_delta = -_P*np.block([om[...,1,2:3]-om[...,2,1:2], om[...,2,0:1]-om[...,0,2:3], om[...,0,1:2]-om[...,1,0:1] ]) @@ -685,18 +685,18 @@ class Rotation: cPhi = np.cos(ee[1]) sPhi = np.sin(ee[1]) qu = np.array([ cPhi*np.cos(ee[0]+ee[2]), - -P*sPhi*np.cos(ee[0]-ee[2]), - -P*sPhi*np.sin(ee[0]-ee[2]), - -P*cPhi*np.sin(ee[0]+ee[2]) ]) + -_P*sPhi*np.cos(ee[0]-ee[2]), + -_P*sPhi*np.sin(ee[0]-ee[2]), + -_P*cPhi*np.sin(ee[0]+ee[2]) ]) if qu[0] < 0.0: qu*=-1 else: ee = 0.5*eu cPhi = np.cos(ee[...,1:2]) sPhi = np.sin(ee[...,1:2]) qu = np.block([ cPhi*np.cos(ee[...,0:1]+ee[...,2:3]), - -P*sPhi*np.cos(ee[...,0:1]-ee[...,2:3]), - -P*sPhi*np.sin(ee[...,0:1]-ee[...,2:3]), - -P*cPhi*np.sin(ee[...,0:1]+ee[...,2:3])]) + -_P*sPhi*np.cos(ee[...,0:1]-ee[...,2:3]), + -_P*sPhi*np.sin(ee[...,0:1]-ee[...,2:3]), + -_P*cPhi*np.sin(ee[...,0:1]+ee[...,2:3])]) qu[qu[...,0]<0.0]*=-1 return qu @@ -741,7 +741,7 @@ class Rotation: if np.abs(alpha)<1.e-6: ax = np.array([ 0.0, 0.0, 1.0, 0.0 ]) else: - ax = -P/tau * np.array([ t*np.cos(delta), t*np.sin(delta), np.sin(sigma) ]) # passive axis angle pair so a minus sign in front + ax = -_P/tau * np.array([ t*np.cos(delta), t*np.sin(delta), np.sin(sigma) ]) # passive axis angle pair so a minus sign in front ax = np.append(ax,alpha) if alpha < 0.0: ax *= -1.0 # ensure alpha is positive else: @@ -753,9 +753,9 @@ class Rotation: with np.errstate(invalid='ignore',divide='ignore'): ax = np.where(np.broadcast_to(np.abs(alpha)<1.0e-12,eu.shape[:-1]+(4,)), [0.0,0.0,1.0,0.0], - np.block([-P/tau*t*np.cos(delta), - -P/tau*t*np.sin(delta), - -P/tau* np.sin(sigma), + np.block([-_P/tau*t*np.cos(delta), + -_P/tau*t*np.sin(delta), + -_P/tau* np.sin(sigma), alpha ])) ax[(alpha<0.0).squeeze()] *=-1 @@ -769,14 +769,14 @@ class Rotation: if ro[3] >= np.pi: # Differs from original implementation. check convention 5 ro[3] = np.inf elif iszero(ro[3]): - ro = np.array([ 0.0, 0.0, P, 0.0 ]) + ro = np.array([ 0.0, 0.0, _P, 0.0 ]) else: ro[3] = np.tan(ro[3]*0.5) else: ax = Rotation.eu2ax(eu) ro = np.block([ax[...,:3],np.tan(ax[...,3:4]*.5)]) ro[ax[...,3]>=np.pi,3] = np.inf - ro[np.abs(ax[...,3])<1.e-16] = [ 0.0, 0.0, P, 0.0 ] + ro[np.abs(ax[...,3])<1.e-16] = [ 0.0, 0.0, _P, 0.0 ] return ro @staticmethod @@ -834,7 +834,7 @@ class Rotation: omc*ax[...,0:1]*ax[...,2:3] + s*ax[...,1:2], omc*ax[...,1:2]*ax[...,2:3] - s*ax[...,0:1], c+omc*ax[...,2:3]**2]).reshape(ax.shape[:-1]+(3,3)) - return om if P < 0.0 else np.swapaxes(om,(-1,-2)) + return om if _P < 0.0 else np.swapaxes(om,(-1,-2)) @staticmethod def ax2eu(ax): @@ -846,7 +846,7 @@ class Rotation: """Axis angle pair to Rodrigues-Frank vector.""" if len(ax.shape) == 1: if np.abs(ax[3])<1.e-6: - ro = [ 0.0, 0.0, P, 0.0 ] + ro = [ 0.0, 0.0, _P, 0.0 ] else: ro = [ax[0], ax[1], ax[2]] # 180 degree case @@ -859,7 +859,7 @@ class Rotation: np.inf, np.tan(ax[...,3:4]*0.5)) ]) - ro[np.abs(ax[...,3])<1.e-6] = [.0,.0,P,.0] + ro[np.abs(ax[...,3])<1.e-6] = [.0,.0,_P,.0] return ro @staticmethod From 3d10266fbc759da1c54b8c15996e2f5d837714c2 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 12 Apr 2020 01:29:11 +0200 Subject: [PATCH 33/33] similar style as for other conversions --- python/damask/_Lambert.py | 4 ---- python/damask/_rotation.py | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/python/damask/_Lambert.py b/python/damask/_Lambert.py index 59abe7f64..a823764e9 100644 --- a/python/damask/_Lambert.py +++ b/python/damask/_Lambert.py @@ -52,8 +52,6 @@ def cube_to_ball(cube): """ cube_ = np.clip(cube,None,np.pi**(2./3.) * 0.5) if np.isclose(np.abs(np.max(cube)),np.pi**(2./3.) * 0.5,atol=1e-6) else cube - if np.abs(np.max(cube_))>np.pi**(2./3.) * 0.5: - raise ValueError('Coordinate outside of the cube: {} {} {}.'.format(*cube)) # transform to the sphere grid via the curved square, and intercept the zero point if np.allclose(cube_,0.0,rtol=0.0,atol=1.0e-16): @@ -105,8 +103,6 @@ def ball_to_cube(ball): """ ball_ = ball/np.linalg.norm(ball)*R1 if np.isclose(np.linalg.norm(ball),R1,atol=1e-6) else ball rs = np.linalg.norm(ball_) - if rs > R1+1.e-9: - raise ValueError('Coordinate outside of the sphere: {} {} {}.'.format(*ball)) if np.allclose(ball_,0.0,rtol=0.0,atol=1.0e-16): cube = np.zeros(3) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index b68ce4d8e..dcd61822f 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -350,6 +350,9 @@ class Rotation: else np.array(homochoric,dtype=float) if P > 0: ho *= -1 # convert from P=1 to P=-1 + if np.linalg.norm(ho) > (3.*np.pi/4.)**(1./3.)+1e-9: + raise ValueError('Coordinate outside of the sphere: {} {} {}.'.format(ho)) + return Rotation(Rotation.ho2qu(ho)) @staticmethod @@ -358,6 +361,10 @@ class Rotation: cu = cubochoric if isinstance(cubochoric, np.ndarray) and cubochoric.dtype == np.dtype(float) \ else np.array(cubochoric,dtype=float) + + if np.abs(np.max(cu))>np.pi**(2./3.) * 0.5+1e-9: + raise ValueError('Coordinate outside of the cube: {} {} {}.'.format(*cu)) + ho = Rotation.cu2ho(cu) if P > 0: ho *= -1 # convert from P=1 to P=-1