tests.test_diagnostics
PyDRex: tests for texture diagnostics.
1"""> PyDRex: tests for texture diagnostics.""" 2 3import numpy as np 4import pytest 5from numpy import random as rn 6from scipy.spatial.transform import Rotation 7 8from pydrex import diagnostics as _diagnostics 9from pydrex import geometry as _geo 10from pydrex import stats as _stats 11 12 13class TestElasticityComponents: 14 """Test symmetry decomposition of elastic tensors.""" 15 16 def test_olivine_Browaeys2004(self): 17 C = np.array( 18 [ 19 [192, 66, 60, 0, 0, 0], 20 [66, 160, 56, 0, 0, 0], 21 [60, 56, 272, 0, 0, 0], 22 [0, 0, 0, 60, 0, 0], 23 [0, 0, 0, 0, 62, 0], 24 [0, 0, 0, 0, 0, 49], 25 ] 26 ) 27 out = _diagnostics.elasticity_components([C]) 28 # FIXME: How do they get 15.2% for hexagonal? It isn't the ratio of the norms 29 # nor the ratio of the squared norms... 30 expected = { 31 "bulk_modulus": 109.8, 32 "shear_modulus": 63.7, 33 "percent_anisotropy": 20.7, 34 # "percent_hexagonal": 15.2, 35 # "percent_tetragonal": 0.625, 36 # "percent_orthorhombic": 4.875, 37 "percent_monoclinic": 0, 38 "percent_triclinic": 0, 39 } 40 for k, v in expected.items(): 41 assert np.isclose(out[k][0], v, atol=0.1, rtol=0), f"{k}: {out[k]} != {v}" 42 np.testing.assert_allclose(out["hexagonal_axis"][0], [0, 0, 1]) 43 44 def test_enstatite_Browaeys2004(self): 45 C = np.array( 46 [ 47 [225, 54, 72, 0, 0, 0], 48 [54, 214, 53, 0, 0, 0], 49 [72, 53, 178, 0, 0, 0], 50 [0, 0, 0, 78, 0, 0], 51 [0, 0, 0, 0, 82, 0], 52 [0, 0, 0, 0, 0, 76], 53 ] 54 ) 55 out = _diagnostics.elasticity_components([C]) 56 # FIXME: Test remaining percentages when I figure out how they get the values. 57 expected = { 58 "bulk_modulus": 108.3, 59 "shear_modulus": 76.4, 60 "percent_anisotropy": 9.2, 61 # "percent_hexagonal": 4.3, 62 # "percent_tetragonal": ?, # + ortho = 4.9 63 # "percent_orthorhombic": ?, # + tetra = 4.9 64 "percent_monoclinic": 0, 65 "percent_triclinic": 0, 66 } 67 for k, v in expected.items(): 68 assert np.isclose(out[k][0], v, atol=0.1, rtol=0), f"{k}: {out[k]} != {v}" 69 np.testing.assert_allclose(out["hexagonal_axis"][0], [0, 0, 1]) 70 71 72class TestSymmetryPGR: 73 """Test Point-Girdle-Random (eigenvalue) symmetry diagnostics.""" 74 75 def test_pointX(self): 76 """Test diagnostics of point symmetry aligned to the X axis.""" 77 # Initial orientations within 10°. 78 orientations = ( 79 Rotation.from_rotvec( 80 np.stack( 81 [ 82 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 83 for x in rn.default_rng().random(100) 84 ] 85 ) 86 ) 87 .inv() 88 .as_matrix() 89 ) 90 np.testing.assert_allclose( 91 _diagnostics.symmetry_pgr(orientations, axis="a"), (1, 0, 0), atol=0.05 92 ) 93 94 def test_random(self): 95 """Test diagnostics of random grain orientations.""" 96 orientations = Rotation.random(1000).as_matrix() 97 np.testing.assert_allclose( 98 _diagnostics.symmetry_pgr(orientations, axis="a"), (0, 0, 1), atol=0.15 99 ) 100 101 def test_girdle(self): 102 """Test diagnostics of girdled orientations.""" 103 rng = rn.default_rng() 104 a = np.zeros(1000) 105 b = np.zeros(1000) 106 c = rng.normal(0, 1.0, size=1000) 107 d = rng.normal(0, 1.0, size=1000) 108 orientations = Rotation.from_quat(np.column_stack([a, b, c, d])).as_matrix() 109 np.testing.assert_allclose( 110 _diagnostics.symmetry_pgr(orientations, axis="a"), (0, 1, 0), atol=0.1 111 ) 112 113 114class TestVolumeWeighting: 115 """Tests for volumetric resampling of orientation data.""" 116 117 def test_output_shape(self): 118 """Test that we get the correct output shape.""" 119 orientations = [ 120 Rotation.random(1000).as_matrix(), 121 Rotation.random(1000).as_matrix(), 122 ] 123 fractions = [np.full(1000, 1 / 1000), np.full(1000, 1 / 1000)] 124 new_orientations, new_fractions = _stats.resample_orientations( 125 orientations, fractions 126 ) 127 np.testing.assert_array_equal( 128 np.asarray(orientations).shape, new_orientations.shape 129 ) 130 np.testing.assert_array_equal(np.asarray(fractions).shape, new_fractions.shape) 131 new_orientations, new_fractions = _stats.resample_orientations( 132 orientations, fractions, n_samples=500 133 ) 134 assert new_orientations.shape[1] == 500 135 assert new_fractions.shape[1] == 500 136 137 def test_upsample(self): 138 """Test upsampling of the raw orientation data.""" 139 orientations = ( 140 Rotation.from_rotvec( 141 [ 142 [0, 0, 0], 143 [0, 0, np.pi / 6], 144 [np.pi / 6, 0, 0], 145 ] 146 ) 147 .inv() 148 .as_matrix() 149 ) 150 fractions = np.array([0.25, 0.6, 0.15]) 151 new_orientations, _ = _stats.resample_orientations( 152 [orientations], 153 [fractions], 154 25, 155 ) 156 assert np.all(a in orientations for a in new_orientations[0]) 157 158 def test_downsample(self): 159 """Test downsampling of orientation data.""" 160 orientations = ( 161 Rotation.from_rotvec( 162 [ 163 [0, 0, 0], 164 [0, 0, np.pi / 6], 165 [np.pi / 6, 0, 0], 166 ] 167 ) 168 .inv() 169 .as_matrix() 170 ) 171 fractions = np.array([0.25, 0.6, 0.15]) 172 new_orientations = _stats.resample_orientations( 173 [orientations], 174 [fractions], 175 2, 176 ) 177 assert np.all(a in orientations for a in new_orientations[0]) 178 179 def test_common_input_errors(self): 180 """Test that exceptions are raised for bad input data.""" 181 orientations = Rotation.from_rotvec( 182 [ 183 [0, 0, 0], 184 [0, 0, np.pi / 6], 185 [np.pi / 6, 0, 0], 186 ] 187 ) 188 fractions = np.array([0.25, 0.6, 0.15]) 189 with pytest.raises(ValueError): 190 # SciPy Rotation instance is not valid input. 191 _stats.resample_orientations(orientations, fractions) 192 # Input must be a stack of orientations. 193 _stats.resample_orientations(orientations.as_matrix(), fractions) 194 # Input must be a stack of fractions. 195 _stats.resample_orientations([orientations.as_matrix()], fractions) 196 # First two dimensions of inputs must match. 197 _stats.resample_orientations([orientations.as_matrix()], [fractions[:-1]]) 198 199 # This is the proper way to do it: 200 _stats.resample_orientations([orientations.as_matrix()], [fractions]) 201 202 203class TestBinghamStats: 204 """Tests for antipodally symmetric (bingham) statistics.""" 205 206 def test_average_0(self): 207 """Test bingham average of vectors aligned to the reference frame.""" 208 orientations = Rotation.from_rotvec([[0, 0, 0]] * 10).inv().as_matrix() 209 a_mean = _diagnostics.bingham_average(orientations, axis="a") 210 b_mean = _diagnostics.bingham_average(orientations, axis="b") 211 c_mean = _diagnostics.bingham_average(orientations, axis="c") 212 np.testing.assert_array_equal(a_mean, [1, 0, 0]) 213 np.testing.assert_array_equal(b_mean, [0, 1, 0]) 214 np.testing.assert_array_equal(c_mean, [0, 0, 1]) 215 216 def test_average_twopoles90Z(self): 217 """Test bingham average of vectors rotated by ±90° around Z.""" 218 orientations = ( 219 Rotation.from_rotvec( 220 [ 221 [0, 0, -np.pi / 2], 222 [0, 0, np.pi / 2], 223 ] 224 ) 225 .inv() 226 .as_matrix() 227 ) 228 a_mean = _diagnostics.bingham_average(orientations, axis="a") 229 b_mean = _diagnostics.bingham_average(orientations, axis="b") 230 c_mean = _diagnostics.bingham_average(orientations, axis="c") 231 np.testing.assert_array_equal(a_mean, [0, 1, 0]) 232 np.testing.assert_array_equal(b_mean, [1, 0, 0]) 233 np.testing.assert_array_equal(c_mean, [0, 0, 1]) 234 235 def test_average_spread10X(self): 236 """Test bingham average of vectors spread within 10° of the ±X-axis.""" 237 orientations = ( 238 Rotation.from_rotvec( 239 np.stack( 240 [ 241 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 242 for x in rn.default_rng().random(100) 243 ] 244 ) 245 ) 246 .inv() 247 .as_matrix() 248 ) 249 a_mean = _diagnostics.bingham_average(orientations, axis="a") 250 b_mean = _diagnostics.bingham_average(orientations, axis="b") 251 c_mean = _diagnostics.bingham_average(orientations, axis="c") 252 np.testing.assert_allclose(np.abs(a_mean), [1.0, 0, 0], atol=np.sin(np.pi / 18)) 253 np.testing.assert_allclose(np.abs(b_mean), [0, 1.0, 0], atol=np.sin(np.pi / 18)) 254 np.testing.assert_allclose(np.abs(c_mean), [0, 0, 1.0], atol=np.sin(np.pi / 18)) 255 256 257class TestMIndex: 258 """Tests for the M-index texture strength diagnostic.""" 259 260 # TODO: Add tests for other (non-orthorhombic) lattice symmetries. 261 262 def test_texture_uniform_ortho(self, seed): 263 """Test with random (uniform distribution) orthorhombic grain orientations.""" 264 orientations = Rotation.random(1000, random_state=seed).as_matrix() 265 assert np.isclose( 266 _diagnostics.misorientation_index( 267 orientations, _geo.LatticeSystem.orthorhombic 268 ), 269 0.05, 270 atol=1e-2, 271 rtol=0, 272 ) 273 274 def test_texture_spread10X_ortho(self, seed): 275 """Test for orthorhombic grains spread within 10° of the ±X axis.""" 276 orientations = ( 277 Rotation.from_rotvec( 278 np.stack( 279 [ 280 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 281 for x in rn.default_rng(seed=seed).random(1000) 282 ] 283 ) 284 ) 285 .inv() 286 .as_matrix() 287 ) 288 assert np.isclose( 289 _diagnostics.misorientation_index( 290 orientations, _geo.LatticeSystem.orthorhombic 291 ), 292 0.99, 293 atol=1e-2, 294 rtol=0, 295 ) 296 297 def test_texture_spread45X_ortho(self, seed): 298 """Test for orthorhombic grains spread within 45° of the ±X axis.""" 299 orientations = Rotation.from_rotvec( 300 np.stack( 301 [ 302 [0, x * np.pi / 2 - np.pi / 4, x * np.pi / 2 - np.pi / 4] 303 for x in rn.default_rng(seed=seed).random(1000) 304 ] 305 ) 306 ).as_matrix() 307 assert np.isclose( 308 _diagnostics.misorientation_index( 309 orientations, _geo.LatticeSystem.orthorhombic 310 ), 311 0.81, 312 atol=1e-2, 313 rtol=0, 314 ) 315 316 def test_textures_increasing_ortho(self, seed): 317 """Test M-index for textures of increasing strength.""" 318 M_vals = np.empty(4) 319 factors = (2, 3, 4, 5) 320 for i, factor in enumerate(factors): 321 orientations = Rotation.from_rotvec( 322 np.stack( 323 [ 324 [ 325 0, 326 x * np.pi / factor - np.pi / factor / 2, 327 x * np.pi / factor - np.pi / factor / 2, 328 ] 329 for x in rn.default_rng(seed=seed).random(1000) 330 ] 331 ) 332 ).as_matrix() 333 M_vals[i] = _diagnostics.misorientation_index( 334 orientations, _geo.LatticeSystem.orthorhombic 335 ) 336 assert np.all( 337 np.diff(M_vals) >= 0 338 ), f"M-index values {M_vals} are not increasing" 339 340 def test_texture_girdle_ortho(self, seed): 341 """Test M-index for girdled texture.""" 342 rng = rn.default_rng(seed=seed) 343 a = np.zeros(1000) 344 b = np.zeros(1000) 345 c = rng.normal(0, 1.0, size=1000) 346 d = rng.normal(0, 1.0, size=1000) 347 orientations = Rotation.from_quat(np.column_stack([a, b, c, d])).as_matrix() 348 assert np.isclose( 349 _diagnostics.misorientation_index( 350 orientations, _geo.LatticeSystem.orthorhombic 351 ), 352 0.67, 353 atol=1e-2, 354 )
14class TestElasticityComponents: 15 """Test symmetry decomposition of elastic tensors.""" 16 17 def test_olivine_Browaeys2004(self): 18 C = np.array( 19 [ 20 [192, 66, 60, 0, 0, 0], 21 [66, 160, 56, 0, 0, 0], 22 [60, 56, 272, 0, 0, 0], 23 [0, 0, 0, 60, 0, 0], 24 [0, 0, 0, 0, 62, 0], 25 [0, 0, 0, 0, 0, 49], 26 ] 27 ) 28 out = _diagnostics.elasticity_components([C]) 29 # FIXME: How do they get 15.2% for hexagonal? It isn't the ratio of the norms 30 # nor the ratio of the squared norms... 31 expected = { 32 "bulk_modulus": 109.8, 33 "shear_modulus": 63.7, 34 "percent_anisotropy": 20.7, 35 # "percent_hexagonal": 15.2, 36 # "percent_tetragonal": 0.625, 37 # "percent_orthorhombic": 4.875, 38 "percent_monoclinic": 0, 39 "percent_triclinic": 0, 40 } 41 for k, v in expected.items(): 42 assert np.isclose(out[k][0], v, atol=0.1, rtol=0), f"{k}: {out[k]} != {v}" 43 np.testing.assert_allclose(out["hexagonal_axis"][0], [0, 0, 1]) 44 45 def test_enstatite_Browaeys2004(self): 46 C = np.array( 47 [ 48 [225, 54, 72, 0, 0, 0], 49 [54, 214, 53, 0, 0, 0], 50 [72, 53, 178, 0, 0, 0], 51 [0, 0, 0, 78, 0, 0], 52 [0, 0, 0, 0, 82, 0], 53 [0, 0, 0, 0, 0, 76], 54 ] 55 ) 56 out = _diagnostics.elasticity_components([C]) 57 # FIXME: Test remaining percentages when I figure out how they get the values. 58 expected = { 59 "bulk_modulus": 108.3, 60 "shear_modulus": 76.4, 61 "percent_anisotropy": 9.2, 62 # "percent_hexagonal": 4.3, 63 # "percent_tetragonal": ?, # + ortho = 4.9 64 # "percent_orthorhombic": ?, # + tetra = 4.9 65 "percent_monoclinic": 0, 66 "percent_triclinic": 0, 67 } 68 for k, v in expected.items(): 69 assert np.isclose(out[k][0], v, atol=0.1, rtol=0), f"{k}: {out[k]} != {v}" 70 np.testing.assert_allclose(out["hexagonal_axis"][0], [0, 0, 1])
Test symmetry decomposition of elastic tensors.
17 def test_olivine_Browaeys2004(self): 18 C = np.array( 19 [ 20 [192, 66, 60, 0, 0, 0], 21 [66, 160, 56, 0, 0, 0], 22 [60, 56, 272, 0, 0, 0], 23 [0, 0, 0, 60, 0, 0], 24 [0, 0, 0, 0, 62, 0], 25 [0, 0, 0, 0, 0, 49], 26 ] 27 ) 28 out = _diagnostics.elasticity_components([C]) 29 # FIXME: How do they get 15.2% for hexagonal? It isn't the ratio of the norms 30 # nor the ratio of the squared norms... 31 expected = { 32 "bulk_modulus": 109.8, 33 "shear_modulus": 63.7, 34 "percent_anisotropy": 20.7, 35 # "percent_hexagonal": 15.2, 36 # "percent_tetragonal": 0.625, 37 # "percent_orthorhombic": 4.875, 38 "percent_monoclinic": 0, 39 "percent_triclinic": 0, 40 } 41 for k, v in expected.items(): 42 assert np.isclose(out[k][0], v, atol=0.1, rtol=0), f"{k}: {out[k]} != {v}" 43 np.testing.assert_allclose(out["hexagonal_axis"][0], [0, 0, 1])
45 def test_enstatite_Browaeys2004(self): 46 C = np.array( 47 [ 48 [225, 54, 72, 0, 0, 0], 49 [54, 214, 53, 0, 0, 0], 50 [72, 53, 178, 0, 0, 0], 51 [0, 0, 0, 78, 0, 0], 52 [0, 0, 0, 0, 82, 0], 53 [0, 0, 0, 0, 0, 76], 54 ] 55 ) 56 out = _diagnostics.elasticity_components([C]) 57 # FIXME: Test remaining percentages when I figure out how they get the values. 58 expected = { 59 "bulk_modulus": 108.3, 60 "shear_modulus": 76.4, 61 "percent_anisotropy": 9.2, 62 # "percent_hexagonal": 4.3, 63 # "percent_tetragonal": ?, # + ortho = 4.9 64 # "percent_orthorhombic": ?, # + tetra = 4.9 65 "percent_monoclinic": 0, 66 "percent_triclinic": 0, 67 } 68 for k, v in expected.items(): 69 assert np.isclose(out[k][0], v, atol=0.1, rtol=0), f"{k}: {out[k]} != {v}" 70 np.testing.assert_allclose(out["hexagonal_axis"][0], [0, 0, 1])
73class TestSymmetryPGR: 74 """Test Point-Girdle-Random (eigenvalue) symmetry diagnostics.""" 75 76 def test_pointX(self): 77 """Test diagnostics of point symmetry aligned to the X axis.""" 78 # Initial orientations within 10°. 79 orientations = ( 80 Rotation.from_rotvec( 81 np.stack( 82 [ 83 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 84 for x in rn.default_rng().random(100) 85 ] 86 ) 87 ) 88 .inv() 89 .as_matrix() 90 ) 91 np.testing.assert_allclose( 92 _diagnostics.symmetry_pgr(orientations, axis="a"), (1, 0, 0), atol=0.05 93 ) 94 95 def test_random(self): 96 """Test diagnostics of random grain orientations.""" 97 orientations = Rotation.random(1000).as_matrix() 98 np.testing.assert_allclose( 99 _diagnostics.symmetry_pgr(orientations, axis="a"), (0, 0, 1), atol=0.15 100 ) 101 102 def test_girdle(self): 103 """Test diagnostics of girdled orientations.""" 104 rng = rn.default_rng() 105 a = np.zeros(1000) 106 b = np.zeros(1000) 107 c = rng.normal(0, 1.0, size=1000) 108 d = rng.normal(0, 1.0, size=1000) 109 orientations = Rotation.from_quat(np.column_stack([a, b, c, d])).as_matrix() 110 np.testing.assert_allclose( 111 _diagnostics.symmetry_pgr(orientations, axis="a"), (0, 1, 0), atol=0.1 112 )
Test Point-Girdle-Random (eigenvalue) symmetry diagnostics.
76 def test_pointX(self): 77 """Test diagnostics of point symmetry aligned to the X axis.""" 78 # Initial orientations within 10°. 79 orientations = ( 80 Rotation.from_rotvec( 81 np.stack( 82 [ 83 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 84 for x in rn.default_rng().random(100) 85 ] 86 ) 87 ) 88 .inv() 89 .as_matrix() 90 ) 91 np.testing.assert_allclose( 92 _diagnostics.symmetry_pgr(orientations, axis="a"), (1, 0, 0), atol=0.05 93 )
Test diagnostics of point symmetry aligned to the X axis.
95 def test_random(self): 96 """Test diagnostics of random grain orientations.""" 97 orientations = Rotation.random(1000).as_matrix() 98 np.testing.assert_allclose( 99 _diagnostics.symmetry_pgr(orientations, axis="a"), (0, 0, 1), atol=0.15 100 )
Test diagnostics of random grain orientations.
102 def test_girdle(self): 103 """Test diagnostics of girdled orientations.""" 104 rng = rn.default_rng() 105 a = np.zeros(1000) 106 b = np.zeros(1000) 107 c = rng.normal(0, 1.0, size=1000) 108 d = rng.normal(0, 1.0, size=1000) 109 orientations = Rotation.from_quat(np.column_stack([a, b, c, d])).as_matrix() 110 np.testing.assert_allclose( 111 _diagnostics.symmetry_pgr(orientations, axis="a"), (0, 1, 0), atol=0.1 112 )
Test diagnostics of girdled orientations.
115class TestVolumeWeighting: 116 """Tests for volumetric resampling of orientation data.""" 117 118 def test_output_shape(self): 119 """Test that we get the correct output shape.""" 120 orientations = [ 121 Rotation.random(1000).as_matrix(), 122 Rotation.random(1000).as_matrix(), 123 ] 124 fractions = [np.full(1000, 1 / 1000), np.full(1000, 1 / 1000)] 125 new_orientations, new_fractions = _stats.resample_orientations( 126 orientations, fractions 127 ) 128 np.testing.assert_array_equal( 129 np.asarray(orientations).shape, new_orientations.shape 130 ) 131 np.testing.assert_array_equal(np.asarray(fractions).shape, new_fractions.shape) 132 new_orientations, new_fractions = _stats.resample_orientations( 133 orientations, fractions, n_samples=500 134 ) 135 assert new_orientations.shape[1] == 500 136 assert new_fractions.shape[1] == 500 137 138 def test_upsample(self): 139 """Test upsampling of the raw orientation data.""" 140 orientations = ( 141 Rotation.from_rotvec( 142 [ 143 [0, 0, 0], 144 [0, 0, np.pi / 6], 145 [np.pi / 6, 0, 0], 146 ] 147 ) 148 .inv() 149 .as_matrix() 150 ) 151 fractions = np.array([0.25, 0.6, 0.15]) 152 new_orientations, _ = _stats.resample_orientations( 153 [orientations], 154 [fractions], 155 25, 156 ) 157 assert np.all(a in orientations for a in new_orientations[0]) 158 159 def test_downsample(self): 160 """Test downsampling of orientation data.""" 161 orientations = ( 162 Rotation.from_rotvec( 163 [ 164 [0, 0, 0], 165 [0, 0, np.pi / 6], 166 [np.pi / 6, 0, 0], 167 ] 168 ) 169 .inv() 170 .as_matrix() 171 ) 172 fractions = np.array([0.25, 0.6, 0.15]) 173 new_orientations = _stats.resample_orientations( 174 [orientations], 175 [fractions], 176 2, 177 ) 178 assert np.all(a in orientations for a in new_orientations[0]) 179 180 def test_common_input_errors(self): 181 """Test that exceptions are raised for bad input data.""" 182 orientations = Rotation.from_rotvec( 183 [ 184 [0, 0, 0], 185 [0, 0, np.pi / 6], 186 [np.pi / 6, 0, 0], 187 ] 188 ) 189 fractions = np.array([0.25, 0.6, 0.15]) 190 with pytest.raises(ValueError): 191 # SciPy Rotation instance is not valid input. 192 _stats.resample_orientations(orientations, fractions) 193 # Input must be a stack of orientations. 194 _stats.resample_orientations(orientations.as_matrix(), fractions) 195 # Input must be a stack of fractions. 196 _stats.resample_orientations([orientations.as_matrix()], fractions) 197 # First two dimensions of inputs must match. 198 _stats.resample_orientations([orientations.as_matrix()], [fractions[:-1]]) 199 200 # This is the proper way to do it: 201 _stats.resample_orientations([orientations.as_matrix()], [fractions])
Tests for volumetric resampling of orientation data.
118 def test_output_shape(self): 119 """Test that we get the correct output shape.""" 120 orientations = [ 121 Rotation.random(1000).as_matrix(), 122 Rotation.random(1000).as_matrix(), 123 ] 124 fractions = [np.full(1000, 1 / 1000), np.full(1000, 1 / 1000)] 125 new_orientations, new_fractions = _stats.resample_orientations( 126 orientations, fractions 127 ) 128 np.testing.assert_array_equal( 129 np.asarray(orientations).shape, new_orientations.shape 130 ) 131 np.testing.assert_array_equal(np.asarray(fractions).shape, new_fractions.shape) 132 new_orientations, new_fractions = _stats.resample_orientations( 133 orientations, fractions, n_samples=500 134 ) 135 assert new_orientations.shape[1] == 500 136 assert new_fractions.shape[1] == 500
Test that we get the correct output shape.
138 def test_upsample(self): 139 """Test upsampling of the raw orientation data.""" 140 orientations = ( 141 Rotation.from_rotvec( 142 [ 143 [0, 0, 0], 144 [0, 0, np.pi / 6], 145 [np.pi / 6, 0, 0], 146 ] 147 ) 148 .inv() 149 .as_matrix() 150 ) 151 fractions = np.array([0.25, 0.6, 0.15]) 152 new_orientations, _ = _stats.resample_orientations( 153 [orientations], 154 [fractions], 155 25, 156 ) 157 assert np.all(a in orientations for a in new_orientations[0])
Test upsampling of the raw orientation data.
159 def test_downsample(self): 160 """Test downsampling of orientation data.""" 161 orientations = ( 162 Rotation.from_rotvec( 163 [ 164 [0, 0, 0], 165 [0, 0, np.pi / 6], 166 [np.pi / 6, 0, 0], 167 ] 168 ) 169 .inv() 170 .as_matrix() 171 ) 172 fractions = np.array([0.25, 0.6, 0.15]) 173 new_orientations = _stats.resample_orientations( 174 [orientations], 175 [fractions], 176 2, 177 ) 178 assert np.all(a in orientations for a in new_orientations[0])
Test downsampling of orientation data.
180 def test_common_input_errors(self): 181 """Test that exceptions are raised for bad input data.""" 182 orientations = Rotation.from_rotvec( 183 [ 184 [0, 0, 0], 185 [0, 0, np.pi / 6], 186 [np.pi / 6, 0, 0], 187 ] 188 ) 189 fractions = np.array([0.25, 0.6, 0.15]) 190 with pytest.raises(ValueError): 191 # SciPy Rotation instance is not valid input. 192 _stats.resample_orientations(orientations, fractions) 193 # Input must be a stack of orientations. 194 _stats.resample_orientations(orientations.as_matrix(), fractions) 195 # Input must be a stack of fractions. 196 _stats.resample_orientations([orientations.as_matrix()], fractions) 197 # First two dimensions of inputs must match. 198 _stats.resample_orientations([orientations.as_matrix()], [fractions[:-1]]) 199 200 # This is the proper way to do it: 201 _stats.resample_orientations([orientations.as_matrix()], [fractions])
Test that exceptions are raised for bad input data.
204class TestBinghamStats: 205 """Tests for antipodally symmetric (bingham) statistics.""" 206 207 def test_average_0(self): 208 """Test bingham average of vectors aligned to the reference frame.""" 209 orientations = Rotation.from_rotvec([[0, 0, 0]] * 10).inv().as_matrix() 210 a_mean = _diagnostics.bingham_average(orientations, axis="a") 211 b_mean = _diagnostics.bingham_average(orientations, axis="b") 212 c_mean = _diagnostics.bingham_average(orientations, axis="c") 213 np.testing.assert_array_equal(a_mean, [1, 0, 0]) 214 np.testing.assert_array_equal(b_mean, [0, 1, 0]) 215 np.testing.assert_array_equal(c_mean, [0, 0, 1]) 216 217 def test_average_twopoles90Z(self): 218 """Test bingham average of vectors rotated by ±90° around Z.""" 219 orientations = ( 220 Rotation.from_rotvec( 221 [ 222 [0, 0, -np.pi / 2], 223 [0, 0, np.pi / 2], 224 ] 225 ) 226 .inv() 227 .as_matrix() 228 ) 229 a_mean = _diagnostics.bingham_average(orientations, axis="a") 230 b_mean = _diagnostics.bingham_average(orientations, axis="b") 231 c_mean = _diagnostics.bingham_average(orientations, axis="c") 232 np.testing.assert_array_equal(a_mean, [0, 1, 0]) 233 np.testing.assert_array_equal(b_mean, [1, 0, 0]) 234 np.testing.assert_array_equal(c_mean, [0, 0, 1]) 235 236 def test_average_spread10X(self): 237 """Test bingham average of vectors spread within 10° of the ±X-axis.""" 238 orientations = ( 239 Rotation.from_rotvec( 240 np.stack( 241 [ 242 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 243 for x in rn.default_rng().random(100) 244 ] 245 ) 246 ) 247 .inv() 248 .as_matrix() 249 ) 250 a_mean = _diagnostics.bingham_average(orientations, axis="a") 251 b_mean = _diagnostics.bingham_average(orientations, axis="b") 252 c_mean = _diagnostics.bingham_average(orientations, axis="c") 253 np.testing.assert_allclose(np.abs(a_mean), [1.0, 0, 0], atol=np.sin(np.pi / 18)) 254 np.testing.assert_allclose(np.abs(b_mean), [0, 1.0, 0], atol=np.sin(np.pi / 18)) 255 np.testing.assert_allclose(np.abs(c_mean), [0, 0, 1.0], atol=np.sin(np.pi / 18))
Tests for antipodally symmetric (bingham) statistics.
207 def test_average_0(self): 208 """Test bingham average of vectors aligned to the reference frame.""" 209 orientations = Rotation.from_rotvec([[0, 0, 0]] * 10).inv().as_matrix() 210 a_mean = _diagnostics.bingham_average(orientations, axis="a") 211 b_mean = _diagnostics.bingham_average(orientations, axis="b") 212 c_mean = _diagnostics.bingham_average(orientations, axis="c") 213 np.testing.assert_array_equal(a_mean, [1, 0, 0]) 214 np.testing.assert_array_equal(b_mean, [0, 1, 0]) 215 np.testing.assert_array_equal(c_mean, [0, 0, 1])
Test bingham average of vectors aligned to the reference frame.
217 def test_average_twopoles90Z(self): 218 """Test bingham average of vectors rotated by ±90° around Z.""" 219 orientations = ( 220 Rotation.from_rotvec( 221 [ 222 [0, 0, -np.pi / 2], 223 [0, 0, np.pi / 2], 224 ] 225 ) 226 .inv() 227 .as_matrix() 228 ) 229 a_mean = _diagnostics.bingham_average(orientations, axis="a") 230 b_mean = _diagnostics.bingham_average(orientations, axis="b") 231 c_mean = _diagnostics.bingham_average(orientations, axis="c") 232 np.testing.assert_array_equal(a_mean, [0, 1, 0]) 233 np.testing.assert_array_equal(b_mean, [1, 0, 0]) 234 np.testing.assert_array_equal(c_mean, [0, 0, 1])
Test bingham average of vectors rotated by ±90° around Z.
236 def test_average_spread10X(self): 237 """Test bingham average of vectors spread within 10° of the ±X-axis.""" 238 orientations = ( 239 Rotation.from_rotvec( 240 np.stack( 241 [ 242 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 243 for x in rn.default_rng().random(100) 244 ] 245 ) 246 ) 247 .inv() 248 .as_matrix() 249 ) 250 a_mean = _diagnostics.bingham_average(orientations, axis="a") 251 b_mean = _diagnostics.bingham_average(orientations, axis="b") 252 c_mean = _diagnostics.bingham_average(orientations, axis="c") 253 np.testing.assert_allclose(np.abs(a_mean), [1.0, 0, 0], atol=np.sin(np.pi / 18)) 254 np.testing.assert_allclose(np.abs(b_mean), [0, 1.0, 0], atol=np.sin(np.pi / 18)) 255 np.testing.assert_allclose(np.abs(c_mean), [0, 0, 1.0], atol=np.sin(np.pi / 18))
Test bingham average of vectors spread within 10° of the ±X-axis.
258class TestMIndex: 259 """Tests for the M-index texture strength diagnostic.""" 260 261 # TODO: Add tests for other (non-orthorhombic) lattice symmetries. 262 263 def test_texture_uniform_ortho(self, seed): 264 """Test with random (uniform distribution) orthorhombic grain orientations.""" 265 orientations = Rotation.random(1000, random_state=seed).as_matrix() 266 assert np.isclose( 267 _diagnostics.misorientation_index( 268 orientations, _geo.LatticeSystem.orthorhombic 269 ), 270 0.05, 271 atol=1e-2, 272 rtol=0, 273 ) 274 275 def test_texture_spread10X_ortho(self, seed): 276 """Test for orthorhombic grains spread within 10° of the ±X axis.""" 277 orientations = ( 278 Rotation.from_rotvec( 279 np.stack( 280 [ 281 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 282 for x in rn.default_rng(seed=seed).random(1000) 283 ] 284 ) 285 ) 286 .inv() 287 .as_matrix() 288 ) 289 assert np.isclose( 290 _diagnostics.misorientation_index( 291 orientations, _geo.LatticeSystem.orthorhombic 292 ), 293 0.99, 294 atol=1e-2, 295 rtol=0, 296 ) 297 298 def test_texture_spread45X_ortho(self, seed): 299 """Test for orthorhombic grains spread within 45° of the ±X axis.""" 300 orientations = Rotation.from_rotvec( 301 np.stack( 302 [ 303 [0, x * np.pi / 2 - np.pi / 4, x * np.pi / 2 - np.pi / 4] 304 for x in rn.default_rng(seed=seed).random(1000) 305 ] 306 ) 307 ).as_matrix() 308 assert np.isclose( 309 _diagnostics.misorientation_index( 310 orientations, _geo.LatticeSystem.orthorhombic 311 ), 312 0.81, 313 atol=1e-2, 314 rtol=0, 315 ) 316 317 def test_textures_increasing_ortho(self, seed): 318 """Test M-index for textures of increasing strength.""" 319 M_vals = np.empty(4) 320 factors = (2, 3, 4, 5) 321 for i, factor in enumerate(factors): 322 orientations = Rotation.from_rotvec( 323 np.stack( 324 [ 325 [ 326 0, 327 x * np.pi / factor - np.pi / factor / 2, 328 x * np.pi / factor - np.pi / factor / 2, 329 ] 330 for x in rn.default_rng(seed=seed).random(1000) 331 ] 332 ) 333 ).as_matrix() 334 M_vals[i] = _diagnostics.misorientation_index( 335 orientations, _geo.LatticeSystem.orthorhombic 336 ) 337 assert np.all( 338 np.diff(M_vals) >= 0 339 ), f"M-index values {M_vals} are not increasing" 340 341 def test_texture_girdle_ortho(self, seed): 342 """Test M-index for girdled texture.""" 343 rng = rn.default_rng(seed=seed) 344 a = np.zeros(1000) 345 b = np.zeros(1000) 346 c = rng.normal(0, 1.0, size=1000) 347 d = rng.normal(0, 1.0, size=1000) 348 orientations = Rotation.from_quat(np.column_stack([a, b, c, d])).as_matrix() 349 assert np.isclose( 350 _diagnostics.misorientation_index( 351 orientations, _geo.LatticeSystem.orthorhombic 352 ), 353 0.67, 354 atol=1e-2, 355 )
Tests for the M-index texture strength diagnostic.
263 def test_texture_uniform_ortho(self, seed): 264 """Test with random (uniform distribution) orthorhombic grain orientations.""" 265 orientations = Rotation.random(1000, random_state=seed).as_matrix() 266 assert np.isclose( 267 _diagnostics.misorientation_index( 268 orientations, _geo.LatticeSystem.orthorhombic 269 ), 270 0.05, 271 atol=1e-2, 272 rtol=0, 273 )
Test with random (uniform distribution) orthorhombic grain orientations.
275 def test_texture_spread10X_ortho(self, seed): 276 """Test for orthorhombic grains spread within 10° of the ±X axis.""" 277 orientations = ( 278 Rotation.from_rotvec( 279 np.stack( 280 [ 281 [0, x * np.pi / 18 - np.pi / 36, x * np.pi / 18 - np.pi / 36] 282 for x in rn.default_rng(seed=seed).random(1000) 283 ] 284 ) 285 ) 286 .inv() 287 .as_matrix() 288 ) 289 assert np.isclose( 290 _diagnostics.misorientation_index( 291 orientations, _geo.LatticeSystem.orthorhombic 292 ), 293 0.99, 294 atol=1e-2, 295 rtol=0, 296 )
Test for orthorhombic grains spread within 10° of the ±X axis.
298 def test_texture_spread45X_ortho(self, seed): 299 """Test for orthorhombic grains spread within 45° of the ±X axis.""" 300 orientations = Rotation.from_rotvec( 301 np.stack( 302 [ 303 [0, x * np.pi / 2 - np.pi / 4, x * np.pi / 2 - np.pi / 4] 304 for x in rn.default_rng(seed=seed).random(1000) 305 ] 306 ) 307 ).as_matrix() 308 assert np.isclose( 309 _diagnostics.misorientation_index( 310 orientations, _geo.LatticeSystem.orthorhombic 311 ), 312 0.81, 313 atol=1e-2, 314 rtol=0, 315 )
Test for orthorhombic grains spread within 45° of the ±X axis.
317 def test_textures_increasing_ortho(self, seed): 318 """Test M-index for textures of increasing strength.""" 319 M_vals = np.empty(4) 320 factors = (2, 3, 4, 5) 321 for i, factor in enumerate(factors): 322 orientations = Rotation.from_rotvec( 323 np.stack( 324 [ 325 [ 326 0, 327 x * np.pi / factor - np.pi / factor / 2, 328 x * np.pi / factor - np.pi / factor / 2, 329 ] 330 for x in rn.default_rng(seed=seed).random(1000) 331 ] 332 ) 333 ).as_matrix() 334 M_vals[i] = _diagnostics.misorientation_index( 335 orientations, _geo.LatticeSystem.orthorhombic 336 ) 337 assert np.all( 338 np.diff(M_vals) >= 0 339 ), f"M-index values {M_vals} are not increasing"
Test M-index for textures of increasing strength.
341 def test_texture_girdle_ortho(self, seed): 342 """Test M-index for girdled texture.""" 343 rng = rn.default_rng(seed=seed) 344 a = np.zeros(1000) 345 b = np.zeros(1000) 346 c = rng.normal(0, 1.0, size=1000) 347 d = rng.normal(0, 1.0, size=1000) 348 orientations = Rotation.from_quat(np.column_stack([a, b, c, d])).as_matrix() 349 assert np.isclose( 350 _diagnostics.misorientation_index( 351 orientations, _geo.LatticeSystem.orthorhombic 352 ), 353 0.67, 354 atol=1e-2, 355 )
Test M-index for girdled texture.