tests.test_core
PyDRex: Tests for core D-Rex routines.
1"""> PyDRex: Tests for core D-Rex routines.""" 2 3import contextlib as cl 4 5import numpy as np 6import pytest 7from numpy import testing as nt 8from pydrex import core as _core 9from pydrex import io as _io 10from pydrex import logger as _log 11from pydrex import minerals as _minerals 12from pydrex import utils as _utils 13from pydrex import visualisation as _vis 14from scipy.spatial.transform import Rotation 15 16# Subdirectory of `outdir` used to store outputs from these tests. 17SUBDIR = "core" 18 19 20class TestDislocationCreepOPX: 21 """Single-grain orthopyroxene crystallographic rotation rate tests.""" 22 23 class_id = "dislocation_creep_OPX" 24 25 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 26 def test_shear_dudz(self, outdir): 27 test_id = "dudz" 28 optional_logging = cl.nullcontext() 29 if outdir is not None: 30 optional_logging = _io.logfile_enable( 31 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 32 ) 33 with optional_logging: 34 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 35 _log.debug("θ (°): %s", np.rad2deg(θ)) 36 orientations_diff, fractions_diff = _core.derivatives( 37 regime=_core.DeformationRegime.matrix_dislocation, 38 phase=_core.MineralPhase.enstatite, 39 fabric=_core.MineralFabric.enstatite_AB, 40 n_grains=1, 41 orientations=Rotation.from_rotvec([[0, θ, 0]]).as_matrix(), 42 fractions=np.array([1.0]), 43 strain_rate=np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]), 44 velocity_gradient=np.array([[0, 0, 2], [0, 0, 0], [0, 0, 0]]), 45 deformation_gradient_spin=np.full((3, 3), np.nan), 46 stress_exponent=1.5, 47 deformation_exponent=3.5, 48 nucleation_efficiency=5, 49 gbm_mobility=0, 50 volume_fraction=1.0, 51 ) 52 cosθ = np.cos(θ) 53 cos2θ = np.cos(2 * θ) 54 sinθ = np.sin(θ) 55 target_orientations_diff = (1 + cos2θ) * np.array( 56 [[sinθ, 0, -cosθ], [0, 0, 0], [cosθ, 0, sinθ]] 57 ) 58 np.testing.assert_allclose( 59 orientations_diff[0], target_orientations_diff 60 ) 61 assert np.isclose(np.sum(fractions_diff), 0.0) 62 63 def test_shear_dvdx(self, outdir): 64 test_id = "dvdx" 65 optional_logging = cl.nullcontext() 66 if outdir is not None: 67 optional_logging = _io.logfile_enable( 68 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 69 ) 70 with optional_logging: 71 for θ in np.linspace(0, 2 * np.pi, 361): 72 _log.debug("θ (°): %s", np.rad2deg(θ)) 73 orientations = Rotation.from_rotvec([[0, 0, θ]]).as_matrix() 74 deformation_rate = _core._get_deformation_rate( 75 _core.MineralPhase.enstatite, 76 orientations[0], 77 np.array([0, 0, 0, 0]), 78 ) 79 np.testing.assert_allclose(deformation_rate.flatten(), np.zeros(9)) 80 orientations_diff, fractions_diff = _core.derivatives( 81 regime=_core.DeformationRegime.matrix_dislocation, 82 phase=_core.MineralPhase.enstatite, 83 fabric=_core.MineralFabric.enstatite_AB, 84 n_grains=1, 85 orientations=orientations, 86 fractions=np.array([1.0]), 87 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 88 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 89 deformation_gradient_spin=np.full((3, 3), np.nan), 90 stress_exponent=1.5, 91 deformation_exponent=3.5, 92 nucleation_efficiency=5, 93 gbm_mobility=0, 94 volume_fraction=1.0, 95 ) 96 # Can't activate the (100)[001] slip system, no plastic deformation. 97 # Only passive (rigid-body) rotation due to the velocity gradient. 98 sinθ = np.sin(θ) 99 cosθ = np.cos(θ) 100 target_orientations_diff = np.array( 101 [[sinθ, cosθ, 0], [-cosθ, sinθ, 0], [0, 0, 0]] 102 ) 103 np.testing.assert_allclose( 104 orientations_diff[0], target_orientations_diff, atol=1e-15 105 ) 106 assert np.isclose(np.sum(fractions_diff), 0.0) 107 108 109class TestDislocationCreepOlivineA: 110 """Single-grain A-type olivine analytical rotation rate tests.""" 111 112 class_id = "dislocation_creep_OlA" 113 114 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 115 def test_shear_dvdx_slip_010_100(self, outdir): 116 r"""Single grain of A-type olivine, slip on (010)[100]. 117 118 Velocity gradient: 119 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 120 121 """ 122 test_id = "dvdx_010_100" 123 nondim_velocity_gradient = np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]) 124 # Strain rate is 0.5*(L + Lᵀ). 125 nondim_strain_rate = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]) 126 127 optional_logging = cl.nullcontext() 128 if outdir is not None: 129 optional_logging = _io.logfile_enable( 130 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 131 ) 132 initial_angles = [] 133 rotation_rates = [] 134 target_rotation_rates = [] 135 136 with optional_logging: 137 for θ in np.mgrid[0 : 2 * np.pi : 3600j]: 138 _log.debug("θ (°): %s", np.rad2deg(θ)) 139 # Initial grain rotations around Z (anti-clockwise). 140 initial_orientations = Rotation.from_rotvec([[0, 0, θ]]) 141 orientation_matrix = initial_orientations[0].as_matrix() 142 slip_invariants = _core._get_slip_invariants( 143 nondim_strain_rate, orientation_matrix 144 ) 145 _log.debug("slip invariants: %s", slip_invariants) 146 147 crss = _core.get_crss( 148 _core.MineralPhase.olivine, 149 _core.MineralFabric.olivine_A, 150 ) 151 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 152 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 153 assert slip_system == ([0, 1, 0], [1, 0, 0]) 154 155 slip_rates = _core._get_slip_rates_olivine( 156 slip_invariants, slip_indices, crss, 3.5 157 ) 158 _log.debug("slip rates: %s", slip_rates) 159 160 deformation_rate = _core._get_deformation_rate( 161 _core.MineralPhase.olivine, 162 orientation_matrix, 163 slip_rates, 164 ) 165 _log.debug("deformation rate:\n%s", deformation_rate) 166 167 orientations_diff, fractions_diff = _core.derivatives( 168 regime=_core.DeformationRegime.matrix_dislocation, 169 phase=_core.MineralPhase.olivine, 170 fabric=_core.MineralFabric.olivine_A, 171 n_grains=1, 172 orientations=initial_orientations.as_matrix(), 173 fractions=np.array([1.0]), 174 strain_rate=nondim_strain_rate, 175 velocity_gradient=nondim_velocity_gradient, 176 deformation_gradient_spin=np.full((3, 3), np.nan), 177 stress_exponent=1.5, 178 deformation_exponent=3.5, 179 nucleation_efficiency=5, 180 gbm_mobility=0, 181 volume_fraction=1.0, 182 ) 183 cosθ = np.cos(θ) 184 cos2θ = np.cos(2 * θ) 185 sinθ = np.sin(θ) 186 target_orientations_diff = np.array( 187 [ 188 [sinθ * (1 + cos2θ), cosθ * (1 + cos2θ), 0], 189 [-cosθ * (1 + cos2θ), sinθ * (1 + cos2θ), 0], 190 [0, 0, 0], 191 ] 192 ) 193 np.testing.assert_allclose( 194 orientations_diff[0], target_orientations_diff 195 ) 196 if outdir is not None: 197 initial_angles.append(np.rad2deg(θ)) 198 rotation_rates.append( 199 np.sqrt( 200 orientations_diff[0][0, 0] ** 2 201 + orientations_diff[0][0, 1] ** 2 202 ) 203 ) 204 target_rotation_rates.append(1 + cos2θ) 205 assert np.isclose(np.sum(fractions_diff), 0.0) 206 207 if outdir is not None: 208 fig = _vis.figure(figscale=(1, 2 / 3)) 209 axs = fig.subplot_mosaic( # Put the legend on a special axes. 210 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 211 ) 212 ax = axs["spin"] 213 fig, ax, colors = _vis.spin( 214 ax, 215 initial_angles[::25], 216 rotation_rates[::25], 217 initial_angles, 218 target_rotation_rates, 219 shear_axis=90, 220 ) 221 _utils.redraw_legend( 222 ax, 223 legendax=axs["legend"], 224 loc="upper center", 225 ncols=3, 226 mode="expand", 227 ) 228 fig.savefig( 229 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 230 ) 231 232 def test_shear_dudz_slip_001_100(self, outdir): 233 r"""Single grain of A-type olivine, slip on (001)[100]. 234 235 Velocity gradient: 236 $$\bm{L} = \begin{bmatrix} 0 & 0 & 2 \cr 0 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 237 238 """ 239 test_id = "dudz_001_100" 240 nondim_velocity_gradient = np.array([[0, 0, 2], [0, 0, 0], [0, 0, 0]]) 241 # Strain rate is 0.5*(L + Lᵀ). 242 nondim_strain_rate = np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]) 243 244 optional_logging = cl.nullcontext() 245 if outdir is not None: 246 optional_logging = _io.logfile_enable( 247 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 248 ) 249 initial_angles = [] 250 rotation_rates = [] 251 target_rotation_rates = [] 252 253 with optional_logging: 254 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 255 _log.debug("θ (°): %s", np.rad2deg(θ)) 256 # Initial grain rotations around Y (anti-clockwise). 257 initial_orientations = Rotation.from_rotvec([[0, θ, 0]]) 258 orientation_matrix = initial_orientations[0].as_matrix() 259 slip_invariants = _core._get_slip_invariants( 260 nondim_strain_rate, orientation_matrix 261 ) 262 _log.debug("slip invariants: %s", slip_invariants) 263 264 crss = _core.get_crss( 265 _core.MineralPhase.olivine, 266 _core.MineralFabric.olivine_A, 267 ) 268 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 269 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 270 assert slip_system == ([0, 0, 1], [1, 0, 0]) 271 272 slip_rates = _core._get_slip_rates_olivine( 273 slip_invariants, slip_indices, crss, 3.5 274 ) 275 _log.debug("slip rates: %s", slip_rates) 276 277 deformation_rate = _core._get_deformation_rate( 278 _core.MineralPhase.olivine, 279 orientation_matrix, 280 slip_rates, 281 ) 282 _log.debug("deformation rate:\n%s", deformation_rate) 283 284 orientations_diff, fractions_diff = _core.derivatives( 285 regime=_core.DeformationRegime.matrix_dislocation, 286 phase=_core.MineralPhase.olivine, 287 fabric=_core.MineralFabric.olivine_A, 288 n_grains=1, 289 orientations=initial_orientations.as_matrix(), 290 fractions=np.array([1.0]), 291 strain_rate=nondim_strain_rate, 292 velocity_gradient=nondim_velocity_gradient, 293 deformation_gradient_spin=np.full((3, 3), np.nan), 294 stress_exponent=1.5, 295 deformation_exponent=3.5, 296 nucleation_efficiency=5, 297 gbm_mobility=0, 298 volume_fraction=1.0, 299 ) 300 cosθ = np.cos(θ) 301 cos2θ = np.cos(2 * θ) 302 sinθ = np.sin(θ) 303 target_orientations_diff = np.array( 304 [ 305 [-sinθ * (cos2θ - 1), 0, cosθ * (cos2θ - 1)], 306 [0, 0, 0], 307 [cosθ * (1 - cos2θ), 0, sinθ * (1 - cos2θ)], 308 ] 309 ) 310 np.testing.assert_allclose( 311 orientations_diff[0], target_orientations_diff 312 ) 313 if outdir is not None: 314 initial_angles.append(np.rad2deg(θ)) 315 rotation_rates.append( 316 np.sqrt( 317 orientations_diff[0][0, 0] ** 2 318 + orientations_diff[0][0, 2] ** 2 319 ) 320 ) 321 target_rotation_rates.append(1 - cos2θ) 322 assert np.isclose(np.sum(fractions_diff), 0.0) 323 324 if outdir is not None: 325 fig = _vis.figure(figscale=(1, 2 / 3)) 326 axs = fig.subplot_mosaic( # Put the legend on a special axes. 327 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 328 ) 329 ax = axs["spin"] 330 fig, ax, colors = _vis.spin( 331 ax, 332 initial_angles[::5], 333 rotation_rates[::5], 334 initial_angles, 335 target_rotation_rates, 336 shear_axis=0, 337 ) 338 _utils.redraw_legend( 339 ax, 340 legendax=axs["legend"], 341 loc="upper center", 342 ncols=3, 343 mode="expand", 344 ) 345 fig.savefig( 346 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 347 ) 348 349 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 350 def test_shear_dwdx_slip_001_100(self, outdir): 351 r"""Single grain of A-type olivine, slip on (001)[100]. 352 353 Velocity gradient: 354 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 0 & 0 & 0 \cr 2 & 0 & 0 \end{bmatrix}$$ 355 356 """ 357 test_id = "dwdx_001_100" 358 nondim_velocity_gradient = np.array([[0, 0, 0], [0, 0, 0], [2, 0, 0]]) 359 # Strain rate is 0.5*(L + Lᵀ). 360 nondim_strain_rate = np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]) 361 362 optional_logging = cl.nullcontext() 363 if outdir is not None: 364 optional_logging = _io.logfile_enable( 365 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 366 ) 367 initial_angles = [] 368 rotation_rates = [] 369 target_rotation_rates = [] 370 371 with optional_logging: 372 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 373 _log.debug("θ (°): %s", np.rad2deg(θ)) 374 # Initial grain rotations around Y (anti-clockwise). 375 initial_orientations = Rotation.from_rotvec([[0, θ, 0]]) 376 orientation_matrix = initial_orientations[0].as_matrix() 377 slip_invariants = _core._get_slip_invariants( 378 nondim_strain_rate, orientation_matrix 379 ) 380 _log.debug("slip invariants: %s", slip_invariants) 381 382 crss = _core.get_crss( 383 _core.MineralPhase.olivine, 384 _core.MineralFabric.olivine_A, 385 ) 386 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 387 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 388 assert slip_system == ([0, 0, 1], [1, 0, 0]) 389 390 slip_rates = _core._get_slip_rates_olivine( 391 slip_invariants, slip_indices, crss, 3.5 392 ) 393 _log.debug("slip rates: %s", slip_rates) 394 395 deformation_rate = _core._get_deformation_rate( 396 _core.MineralPhase.olivine, 397 orientation_matrix, 398 slip_rates, 399 ) 400 _log.debug("deformation rate:\n%s", deformation_rate) 401 402 orientations_diff, fractions_diff = _core.derivatives( 403 regime=_core.DeformationRegime.matrix_dislocation, 404 phase=_core.MineralPhase.olivine, 405 fabric=_core.MineralFabric.olivine_A, 406 n_grains=1, 407 orientations=initial_orientations.as_matrix(), 408 fractions=np.array([1.0]), 409 strain_rate=nondim_strain_rate, 410 velocity_gradient=nondim_velocity_gradient, 411 deformation_gradient_spin=np.full((3, 3), np.nan), 412 stress_exponent=1.5, 413 deformation_exponent=3.5, 414 nucleation_efficiency=5, 415 gbm_mobility=0, 416 volume_fraction=1.0, 417 ) 418 cosθ = np.cos(θ) 419 cos2θ = np.cos(2 * θ) 420 sinθ = np.sin(θ) 421 target_orientations_diff = np.array( 422 [ 423 [-sinθ * (1 + cos2θ), 0, cosθ * (1 + cos2θ)], 424 [0, 0, 0], 425 [-cosθ * (1 + cos2θ), 0, -sinθ * (1 + cos2θ)], 426 ] 427 ) 428 np.testing.assert_allclose( 429 orientations_diff[0], target_orientations_diff 430 ) 431 if outdir is not None: 432 initial_angles.append(np.rad2deg(θ)) 433 rotation_rates.append( 434 np.sqrt( 435 orientations_diff[0][0, 0] ** 2 436 + orientations_diff[0][0, 2] ** 2 437 ) 438 ) 439 target_rotation_rates.append(1 + cos2θ) 440 assert np.isclose(np.sum(fractions_diff), 0.0) 441 442 if outdir is not None: 443 fig = _vis.figure(figscale=(1, 2 / 3)) 444 axs = fig.subplot_mosaic( # Put the legend on a special axes. 445 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 446 ) 447 ax = axs["spin"] 448 fig, ax, colors = _vis.spin( 449 ax, 450 initial_angles[::5], 451 rotation_rates[::5], 452 initial_angles, 453 target_rotation_rates, 454 shear_axis=90, 455 ) 456 _utils.redraw_legend( 457 ax, 458 legendax=axs["legend"], 459 loc="upper center", 460 ncols=3, 461 mode="expand", 462 ) 463 fig.savefig( 464 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 465 ) 466 467 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 468 def test_shear_dvdz_slip_010_001(self, outdir): 469 r"""Single grain of A-type olivine, slip on (010)[001]. 470 471 Velocity gradient: 472 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 0 & 0 & 2 \cr 0 & 0 & 0 \end{bmatrix}$$ 473 474 """ 475 test_id = "dvdz_010_001" 476 nondim_velocity_gradient = np.array([[0, 0, 0], [0, 0, 2], [0, 0, 0]]) 477 # Strain rate is 0.5*(L + Lᵀ). 478 nondim_strain_rate = np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]]) 479 480 optional_logging = cl.nullcontext() 481 if outdir is not None: 482 optional_logging = _io.logfile_enable( 483 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 484 ) 485 initial_angles = [] 486 rotation_rates = [] 487 target_rotation_rates = [] 488 489 with optional_logging: 490 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 491 _log.debug("θ (°): %s", np.rad2deg(θ)) 492 # Initial grain rotations around X (anti-clockwise). 493 initial_orientations = Rotation.from_rotvec([[θ, 0, 0]]) 494 orientation_matrix = initial_orientations[0].as_matrix() 495 slip_invariants = _core._get_slip_invariants( 496 nondim_strain_rate, orientation_matrix 497 ) 498 _log.debug("slip invariants: %s", slip_invariants) 499 500 crss = _core.get_crss( 501 _core.MineralPhase.olivine, 502 _core.MineralFabric.olivine_A, 503 ) 504 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 505 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 506 assert slip_system == ([0, 1, 0], [0, 0, 1]) 507 508 slip_rates = _core._get_slip_rates_olivine( 509 slip_invariants, slip_indices, crss, 3.5 510 ) 511 _log.debug("slip rates: %s", slip_rates) 512 513 deformation_rate = _core._get_deformation_rate( 514 _core.MineralPhase.olivine, 515 orientation_matrix, 516 slip_rates, 517 ) 518 _log.debug("deformation rate:\n%s", deformation_rate) 519 520 orientations_diff, fractions_diff = _core.derivatives( 521 regime=_core.DeformationRegime.matrix_dislocation, 522 phase=_core.MineralPhase.olivine, 523 fabric=_core.MineralFabric.olivine_A, 524 n_grains=1, 525 orientations=initial_orientations.as_matrix(), 526 fractions=np.array([1.0]), 527 strain_rate=nondim_strain_rate, 528 velocity_gradient=nondim_velocity_gradient, 529 deformation_gradient_spin=np.full((3, 3), np.nan), 530 stress_exponent=1.5, 531 deformation_exponent=3.5, 532 nucleation_efficiency=5, 533 gbm_mobility=0, 534 volume_fraction=1.0, 535 ) 536 cosθ = np.cos(θ) 537 cos2θ = np.cos(2 * θ) 538 sinθ = np.sin(θ) 539 target_orientations_diff = np.array( 540 [ 541 [0, 0, 0], 542 [0, -sinθ * (1 + cos2θ), -cosθ * (1 + cos2θ)], 543 [0, cosθ * (1 + cos2θ), -sinθ * (1 + cos2θ)], 544 ] 545 ) 546 np.testing.assert_allclose( 547 orientations_diff[0], target_orientations_diff 548 ) 549 if outdir is not None: 550 initial_angles.append(np.rad2deg(θ)) 551 rotation_rates.append( 552 np.sqrt( 553 orientations_diff[0][1, 1] ** 2 554 + orientations_diff[0][1, 2] ** 2 555 ) 556 ) 557 target_rotation_rates.append(1 + cos2θ) 558 assert np.isclose(np.sum(fractions_diff), 0.0) 559 560 if outdir is not None: 561 fig = _vis.figure(figscale=(1, 2 / 3)) 562 axs = fig.subplot_mosaic( # Put the legend on a special axes. 563 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 564 ) 565 ax = axs["spin"] 566 fig, ax, colors = _vis.spin( 567 ax, 568 initial_angles[::5], 569 rotation_rates[::5], 570 initial_angles, 571 target_rotation_rates, 572 shear_axis=90, 573 ) 574 _utils.redraw_legend( 575 ax, 576 legendax=axs["legend"], 577 loc="upper center", 578 ncols=3, 579 mode="expand", 580 ) 581 fig.savefig( 582 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 583 ) 584 585 586class TestRecrystallisation2D: 587 """Basic recrystallisation tests for 2D simple shear.""" 588 589 class_id = "recrystallisation_2D" 590 591 def test_shear_dvdx_circle_inplane(self, outdir): 592 r"""360000 grains of A-type olivine with uniform spread of a-axes on a circle. 593 594 Grain growth rates are compared to analytical calculations. 595 The a-axes are distributed in the YX plane (i.e.\ rotated around Z). 596 597 Velocity gradient: 598 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 599 600 """ 601 test_id = "dvdx_circle_inplane" 602 603 optional_logging = cl.nullcontext() 604 # Initial uniform distribution of orientations on a 2D circle. 605 initial_angles = np.mgrid[0 : 2 * np.pi : 360000j] 606 cos2θ = np.cos(2 * initial_angles) 607 if outdir is not None: 608 out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}" 609 optional_logging = _io.logfile_enable(f"{out_basepath}.log") 610 611 with optional_logging: 612 initial_orientations = Rotation.from_rotvec( 613 [[0, 0, θ] for θ in initial_angles] 614 ) 615 orientations_diff, fractions_diff = _core.derivatives( 616 regime=_core.DeformationRegime.matrix_dislocation, 617 phase=_core.MineralPhase.olivine, 618 fabric=_core.MineralFabric.olivine_A, 619 n_grains=360000, 620 orientations=initial_orientations.as_matrix(), 621 fractions=np.full(360000, 1 / 360000), 622 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 623 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 624 deformation_gradient_spin=np.full((3, 3), np.nan), 625 stress_exponent=1.5, 626 deformation_exponent=3.5, 627 nucleation_efficiency=5, 628 gbm_mobility=125, 629 volume_fraction=1.0, 630 ) 631 ρ = np.abs(cos2θ) ** (1.5 / 3.5) 632 # No need to sum over slip systems, only (010)[100] can activate in this 633 # 2D simple shear deformation geometry (ρₛ=ρ). 634 target_strain_energies = ρ * np.exp(-5 * ρ**2) 635 target_fractions_diff = np.array( 636 [ # df/dt* = - M* f (E - E_mean) 637 -125 * 1 / 360000 * (E - np.mean(target_strain_energies)) 638 for E in target_strain_energies 639 ] 640 ) 641 642 if outdir is not None: 643 fig = _vis.figure(figscale=(1, 4 / 3)) 644 axs = fig.subplot_mosaic( # Put the legend on a special axes. 645 [["legend"], ["spin"], ["growth"]], 646 gridspec_kw={"height_ratios": [0.2, 1, 1]}, 647 sharex=True, 648 ) 649 ax = axs["spin"] 650 xvals = np.rad2deg(initial_angles) 651 fig, ax, colors = _vis.spin( 652 ax, 653 xvals[::2500], 654 np.sqrt( 655 [ 656 o[0, 0] ** 2 + o[0, 1] ** 2 + o[0, 2] ** 2 657 for o in orientations_diff 658 ][::2500] 659 ), 660 xvals, 661 1 + cos2θ, 662 shear_axis=90, 663 ) 664 ax.get_legend().remove() 665 ax.label_outer() 666 ax2 = axs["growth"] 667 fig, ax2, colors = _vis.growth( 668 ax2, 669 xvals[::2500], 670 fractions_diff[::2500], 671 xvals, 672 target_fractions_diff, 673 shear_axis=90, 674 ) 675 _utils.redraw_legend( 676 ax2, 677 legendax=axs["legend"], 678 loc="upper center", 679 ncols=3, 680 mode="expand", 681 ) 682 _utils.add_subplot_labels(axs, labelmap={"spin": "a)", "growth": "b)"}) 683 fig.savefig(f"{out_basepath}.pdf") 684 685 nt.assert_allclose(fractions_diff, target_fractions_diff, atol=1e-15, rtol=0) 686 687 def test_shear_dvdx_circle_shearplane(self, outdir): 688 r"""360000 grains of A-type olivine with uniform spread of a-axes on a circle. 689 690 Unlike `test_shear_dvdx_circle_inplane`, two slip systems are active here, 691 with cyclical variety in which one is dominant depending on grain orientation. 692 The a-axes are distributed in the YZ plane 693 (i.e.\ extrinsic rotation around Z by 90° and then around X). 694 695 Velocity gradient: 696 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 697 698 """ 699 test_id = "dvdx_circle_shearplane" 700 701 optional_logging = cl.nullcontext() 702 # Initial uniform distribution of orientations on a 2D circle. 703 initial_angles = np.mgrid[0 : 2 * np.pi : 360000j] 704 if outdir is not None: 705 out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}" 706 optional_logging = _io.logfile_enable(f"{out_basepath}.log") 707 708 with optional_logging: 709 initial_orientations = Rotation.from_euler( 710 "zx", [[np.pi / 2, θ] for θ in initial_angles] 711 ) 712 orientations_diff, fractions_diff = _core.derivatives( 713 regime=_core.DeformationRegime.matrix_dislocation, 714 phase=_core.MineralPhase.olivine, 715 fabric=_core.MineralFabric.olivine_A, 716 n_grains=360000, 717 orientations=initial_orientations.as_matrix(), 718 fractions=np.full(360000, 1 / 360000), 719 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 720 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 721 deformation_gradient_spin=np.full((3, 3), np.nan), 722 stress_exponent=1.5, 723 deformation_exponent=3.5, 724 nucleation_efficiency=5, 725 gbm_mobility=125, 726 volume_fraction=1.0, 727 ) 728 729 if outdir is not None: 730 fig = _vis.figure(figscale=(1, 4 / 3)) 731 axs = fig.subplot_mosaic([["spin"], ["growth"]], sharex=True) 732 ax = axs["spin"] 733 xvals = np.rad2deg(initial_angles) 734 fig, ax, colors = _vis.spin( 735 ax, 736 xvals[::2500], 737 np.sqrt( 738 [ 739 o[0, 0] ** 2 + o[0, 1] ** 2 + o[0, 2] ** 2 740 for o in orientations_diff 741 ][::2500] 742 ), 743 ) 744 ax.get_legend().remove() 745 ax.label_outer() 746 ax2 = axs["growth"] 747 fig, ax2, colors = _vis.growth(ax2, xvals[::2500], fractions_diff[::2500]) 748 _utils.add_subplot_labels(axs, labelmap={"spin": "a)", "growth": "b)"}) 749 fig.savefig(f"{out_basepath}.pdf") 750 751 # Check dominant slip system every 1°. 752 for θ in initial_angles[::1000]: 753 slip_invariants = _core._get_slip_invariants( 754 np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 755 Rotation.from_euler("zx", [np.pi / 2, θ]).as_matrix(), 756 ) 757 θ = np.rad2deg(θ) 758 crss = _core.get_crss( 759 _core.MineralPhase.olivine, 760 _core.MineralFabric.olivine_A, 761 ) 762 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 763 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 764 765 if 0 <= θ < 64: 766 assert slip_system == ([0, 1, 0], [1, 0, 0]) 767 elif 64 <= θ < 117: 768 assert slip_system == ([0, 0, 1], [1, 0, 0]) 769 elif 117 <= θ < 244: 770 assert slip_system == ([0, 1, 0], [1, 0, 0]) 771 elif 244 <= θ < 297: 772 assert slip_system == ([0, 0, 1], [1, 0, 0]) 773 else: 774 assert slip_system == ([0, 1, 0], [1, 0, 0])
21class TestDislocationCreepOPX: 22 """Single-grain orthopyroxene crystallographic rotation rate tests.""" 23 24 class_id = "dislocation_creep_OPX" 25 26 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 27 def test_shear_dudz(self, outdir): 28 test_id = "dudz" 29 optional_logging = cl.nullcontext() 30 if outdir is not None: 31 optional_logging = _io.logfile_enable( 32 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 33 ) 34 with optional_logging: 35 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 36 _log.debug("θ (°): %s", np.rad2deg(θ)) 37 orientations_diff, fractions_diff = _core.derivatives( 38 regime=_core.DeformationRegime.matrix_dislocation, 39 phase=_core.MineralPhase.enstatite, 40 fabric=_core.MineralFabric.enstatite_AB, 41 n_grains=1, 42 orientations=Rotation.from_rotvec([[0, θ, 0]]).as_matrix(), 43 fractions=np.array([1.0]), 44 strain_rate=np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]), 45 velocity_gradient=np.array([[0, 0, 2], [0, 0, 0], [0, 0, 0]]), 46 deformation_gradient_spin=np.full((3, 3), np.nan), 47 stress_exponent=1.5, 48 deformation_exponent=3.5, 49 nucleation_efficiency=5, 50 gbm_mobility=0, 51 volume_fraction=1.0, 52 ) 53 cosθ = np.cos(θ) 54 cos2θ = np.cos(2 * θ) 55 sinθ = np.sin(θ) 56 target_orientations_diff = (1 + cos2θ) * np.array( 57 [[sinθ, 0, -cosθ], [0, 0, 0], [cosθ, 0, sinθ]] 58 ) 59 np.testing.assert_allclose( 60 orientations_diff[0], target_orientations_diff 61 ) 62 assert np.isclose(np.sum(fractions_diff), 0.0) 63 64 def test_shear_dvdx(self, outdir): 65 test_id = "dvdx" 66 optional_logging = cl.nullcontext() 67 if outdir is not None: 68 optional_logging = _io.logfile_enable( 69 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 70 ) 71 with optional_logging: 72 for θ in np.linspace(0, 2 * np.pi, 361): 73 _log.debug("θ (°): %s", np.rad2deg(θ)) 74 orientations = Rotation.from_rotvec([[0, 0, θ]]).as_matrix() 75 deformation_rate = _core._get_deformation_rate( 76 _core.MineralPhase.enstatite, 77 orientations[0], 78 np.array([0, 0, 0, 0]), 79 ) 80 np.testing.assert_allclose(deformation_rate.flatten(), np.zeros(9)) 81 orientations_diff, fractions_diff = _core.derivatives( 82 regime=_core.DeformationRegime.matrix_dislocation, 83 phase=_core.MineralPhase.enstatite, 84 fabric=_core.MineralFabric.enstatite_AB, 85 n_grains=1, 86 orientations=orientations, 87 fractions=np.array([1.0]), 88 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 89 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 90 deformation_gradient_spin=np.full((3, 3), np.nan), 91 stress_exponent=1.5, 92 deformation_exponent=3.5, 93 nucleation_efficiency=5, 94 gbm_mobility=0, 95 volume_fraction=1.0, 96 ) 97 # Can't activate the (100)[001] slip system, no plastic deformation. 98 # Only passive (rigid-body) rotation due to the velocity gradient. 99 sinθ = np.sin(θ) 100 cosθ = np.cos(θ) 101 target_orientations_diff = np.array( 102 [[sinθ, cosθ, 0], [-cosθ, sinθ, 0], [0, 0, 0]] 103 ) 104 np.testing.assert_allclose( 105 orientations_diff[0], target_orientations_diff, atol=1e-15 106 ) 107 assert np.isclose(np.sum(fractions_diff), 0.0)
Single-grain orthopyroxene crystallographic rotation rate tests.
26 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 27 def test_shear_dudz(self, outdir): 28 test_id = "dudz" 29 optional_logging = cl.nullcontext() 30 if outdir is not None: 31 optional_logging = _io.logfile_enable( 32 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 33 ) 34 with optional_logging: 35 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 36 _log.debug("θ (°): %s", np.rad2deg(θ)) 37 orientations_diff, fractions_diff = _core.derivatives( 38 regime=_core.DeformationRegime.matrix_dislocation, 39 phase=_core.MineralPhase.enstatite, 40 fabric=_core.MineralFabric.enstatite_AB, 41 n_grains=1, 42 orientations=Rotation.from_rotvec([[0, θ, 0]]).as_matrix(), 43 fractions=np.array([1.0]), 44 strain_rate=np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]), 45 velocity_gradient=np.array([[0, 0, 2], [0, 0, 0], [0, 0, 0]]), 46 deformation_gradient_spin=np.full((3, 3), np.nan), 47 stress_exponent=1.5, 48 deformation_exponent=3.5, 49 nucleation_efficiency=5, 50 gbm_mobility=0, 51 volume_fraction=1.0, 52 ) 53 cosθ = np.cos(θ) 54 cos2θ = np.cos(2 * θ) 55 sinθ = np.sin(θ) 56 target_orientations_diff = (1 + cos2θ) * np.array( 57 [[sinθ, 0, -cosθ], [0, 0, 0], [cosθ, 0, sinθ]] 58 ) 59 np.testing.assert_allclose( 60 orientations_diff[0], target_orientations_diff 61 ) 62 assert np.isclose(np.sum(fractions_diff), 0.0)
64 def test_shear_dvdx(self, outdir): 65 test_id = "dvdx" 66 optional_logging = cl.nullcontext() 67 if outdir is not None: 68 optional_logging = _io.logfile_enable( 69 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 70 ) 71 with optional_logging: 72 for θ in np.linspace(0, 2 * np.pi, 361): 73 _log.debug("θ (°): %s", np.rad2deg(θ)) 74 orientations = Rotation.from_rotvec([[0, 0, θ]]).as_matrix() 75 deformation_rate = _core._get_deformation_rate( 76 _core.MineralPhase.enstatite, 77 orientations[0], 78 np.array([0, 0, 0, 0]), 79 ) 80 np.testing.assert_allclose(deformation_rate.flatten(), np.zeros(9)) 81 orientations_diff, fractions_diff = _core.derivatives( 82 regime=_core.DeformationRegime.matrix_dislocation, 83 phase=_core.MineralPhase.enstatite, 84 fabric=_core.MineralFabric.enstatite_AB, 85 n_grains=1, 86 orientations=orientations, 87 fractions=np.array([1.0]), 88 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 89 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 90 deformation_gradient_spin=np.full((3, 3), np.nan), 91 stress_exponent=1.5, 92 deformation_exponent=3.5, 93 nucleation_efficiency=5, 94 gbm_mobility=0, 95 volume_fraction=1.0, 96 ) 97 # Can't activate the (100)[001] slip system, no plastic deformation. 98 # Only passive (rigid-body) rotation due to the velocity gradient. 99 sinθ = np.sin(θ) 100 cosθ = np.cos(θ) 101 target_orientations_diff = np.array( 102 [[sinθ, cosθ, 0], [-cosθ, sinθ, 0], [0, 0, 0]] 103 ) 104 np.testing.assert_allclose( 105 orientations_diff[0], target_orientations_diff, atol=1e-15 106 ) 107 assert np.isclose(np.sum(fractions_diff), 0.0)
110class TestDislocationCreepOlivineA: 111 """Single-grain A-type olivine analytical rotation rate tests.""" 112 113 class_id = "dislocation_creep_OlA" 114 115 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 116 def test_shear_dvdx_slip_010_100(self, outdir): 117 r"""Single grain of A-type olivine, slip on (010)[100]. 118 119 Velocity gradient: 120 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 121 122 """ 123 test_id = "dvdx_010_100" 124 nondim_velocity_gradient = np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]) 125 # Strain rate is 0.5*(L + Lᵀ). 126 nondim_strain_rate = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]) 127 128 optional_logging = cl.nullcontext() 129 if outdir is not None: 130 optional_logging = _io.logfile_enable( 131 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 132 ) 133 initial_angles = [] 134 rotation_rates = [] 135 target_rotation_rates = [] 136 137 with optional_logging: 138 for θ in np.mgrid[0 : 2 * np.pi : 3600j]: 139 _log.debug("θ (°): %s", np.rad2deg(θ)) 140 # Initial grain rotations around Z (anti-clockwise). 141 initial_orientations = Rotation.from_rotvec([[0, 0, θ]]) 142 orientation_matrix = initial_orientations[0].as_matrix() 143 slip_invariants = _core._get_slip_invariants( 144 nondim_strain_rate, orientation_matrix 145 ) 146 _log.debug("slip invariants: %s", slip_invariants) 147 148 crss = _core.get_crss( 149 _core.MineralPhase.olivine, 150 _core.MineralFabric.olivine_A, 151 ) 152 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 153 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 154 assert slip_system == ([0, 1, 0], [1, 0, 0]) 155 156 slip_rates = _core._get_slip_rates_olivine( 157 slip_invariants, slip_indices, crss, 3.5 158 ) 159 _log.debug("slip rates: %s", slip_rates) 160 161 deformation_rate = _core._get_deformation_rate( 162 _core.MineralPhase.olivine, 163 orientation_matrix, 164 slip_rates, 165 ) 166 _log.debug("deformation rate:\n%s", deformation_rate) 167 168 orientations_diff, fractions_diff = _core.derivatives( 169 regime=_core.DeformationRegime.matrix_dislocation, 170 phase=_core.MineralPhase.olivine, 171 fabric=_core.MineralFabric.olivine_A, 172 n_grains=1, 173 orientations=initial_orientations.as_matrix(), 174 fractions=np.array([1.0]), 175 strain_rate=nondim_strain_rate, 176 velocity_gradient=nondim_velocity_gradient, 177 deformation_gradient_spin=np.full((3, 3), np.nan), 178 stress_exponent=1.5, 179 deformation_exponent=3.5, 180 nucleation_efficiency=5, 181 gbm_mobility=0, 182 volume_fraction=1.0, 183 ) 184 cosθ = np.cos(θ) 185 cos2θ = np.cos(2 * θ) 186 sinθ = np.sin(θ) 187 target_orientations_diff = np.array( 188 [ 189 [sinθ * (1 + cos2θ), cosθ * (1 + cos2θ), 0], 190 [-cosθ * (1 + cos2θ), sinθ * (1 + cos2θ), 0], 191 [0, 0, 0], 192 ] 193 ) 194 np.testing.assert_allclose( 195 orientations_diff[0], target_orientations_diff 196 ) 197 if outdir is not None: 198 initial_angles.append(np.rad2deg(θ)) 199 rotation_rates.append( 200 np.sqrt( 201 orientations_diff[0][0, 0] ** 2 202 + orientations_diff[0][0, 1] ** 2 203 ) 204 ) 205 target_rotation_rates.append(1 + cos2θ) 206 assert np.isclose(np.sum(fractions_diff), 0.0) 207 208 if outdir is not None: 209 fig = _vis.figure(figscale=(1, 2 / 3)) 210 axs = fig.subplot_mosaic( # Put the legend on a special axes. 211 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 212 ) 213 ax = axs["spin"] 214 fig, ax, colors = _vis.spin( 215 ax, 216 initial_angles[::25], 217 rotation_rates[::25], 218 initial_angles, 219 target_rotation_rates, 220 shear_axis=90, 221 ) 222 _utils.redraw_legend( 223 ax, 224 legendax=axs["legend"], 225 loc="upper center", 226 ncols=3, 227 mode="expand", 228 ) 229 fig.savefig( 230 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 231 ) 232 233 def test_shear_dudz_slip_001_100(self, outdir): 234 r"""Single grain of A-type olivine, slip on (001)[100]. 235 236 Velocity gradient: 237 $$\bm{L} = \begin{bmatrix} 0 & 0 & 2 \cr 0 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 238 239 """ 240 test_id = "dudz_001_100" 241 nondim_velocity_gradient = np.array([[0, 0, 2], [0, 0, 0], [0, 0, 0]]) 242 # Strain rate is 0.5*(L + Lᵀ). 243 nondim_strain_rate = np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]) 244 245 optional_logging = cl.nullcontext() 246 if outdir is not None: 247 optional_logging = _io.logfile_enable( 248 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 249 ) 250 initial_angles = [] 251 rotation_rates = [] 252 target_rotation_rates = [] 253 254 with optional_logging: 255 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 256 _log.debug("θ (°): %s", np.rad2deg(θ)) 257 # Initial grain rotations around Y (anti-clockwise). 258 initial_orientations = Rotation.from_rotvec([[0, θ, 0]]) 259 orientation_matrix = initial_orientations[0].as_matrix() 260 slip_invariants = _core._get_slip_invariants( 261 nondim_strain_rate, orientation_matrix 262 ) 263 _log.debug("slip invariants: %s", slip_invariants) 264 265 crss = _core.get_crss( 266 _core.MineralPhase.olivine, 267 _core.MineralFabric.olivine_A, 268 ) 269 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 270 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 271 assert slip_system == ([0, 0, 1], [1, 0, 0]) 272 273 slip_rates = _core._get_slip_rates_olivine( 274 slip_invariants, slip_indices, crss, 3.5 275 ) 276 _log.debug("slip rates: %s", slip_rates) 277 278 deformation_rate = _core._get_deformation_rate( 279 _core.MineralPhase.olivine, 280 orientation_matrix, 281 slip_rates, 282 ) 283 _log.debug("deformation rate:\n%s", deformation_rate) 284 285 orientations_diff, fractions_diff = _core.derivatives( 286 regime=_core.DeformationRegime.matrix_dislocation, 287 phase=_core.MineralPhase.olivine, 288 fabric=_core.MineralFabric.olivine_A, 289 n_grains=1, 290 orientations=initial_orientations.as_matrix(), 291 fractions=np.array([1.0]), 292 strain_rate=nondim_strain_rate, 293 velocity_gradient=nondim_velocity_gradient, 294 deformation_gradient_spin=np.full((3, 3), np.nan), 295 stress_exponent=1.5, 296 deformation_exponent=3.5, 297 nucleation_efficiency=5, 298 gbm_mobility=0, 299 volume_fraction=1.0, 300 ) 301 cosθ = np.cos(θ) 302 cos2θ = np.cos(2 * θ) 303 sinθ = np.sin(θ) 304 target_orientations_diff = np.array( 305 [ 306 [-sinθ * (cos2θ - 1), 0, cosθ * (cos2θ - 1)], 307 [0, 0, 0], 308 [cosθ * (1 - cos2θ), 0, sinθ * (1 - cos2θ)], 309 ] 310 ) 311 np.testing.assert_allclose( 312 orientations_diff[0], target_orientations_diff 313 ) 314 if outdir is not None: 315 initial_angles.append(np.rad2deg(θ)) 316 rotation_rates.append( 317 np.sqrt( 318 orientations_diff[0][0, 0] ** 2 319 + orientations_diff[0][0, 2] ** 2 320 ) 321 ) 322 target_rotation_rates.append(1 - cos2θ) 323 assert np.isclose(np.sum(fractions_diff), 0.0) 324 325 if outdir is not None: 326 fig = _vis.figure(figscale=(1, 2 / 3)) 327 axs = fig.subplot_mosaic( # Put the legend on a special axes. 328 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 329 ) 330 ax = axs["spin"] 331 fig, ax, colors = _vis.spin( 332 ax, 333 initial_angles[::5], 334 rotation_rates[::5], 335 initial_angles, 336 target_rotation_rates, 337 shear_axis=0, 338 ) 339 _utils.redraw_legend( 340 ax, 341 legendax=axs["legend"], 342 loc="upper center", 343 ncols=3, 344 mode="expand", 345 ) 346 fig.savefig( 347 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 348 ) 349 350 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 351 def test_shear_dwdx_slip_001_100(self, outdir): 352 r"""Single grain of A-type olivine, slip on (001)[100]. 353 354 Velocity gradient: 355 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 0 & 0 & 0 \cr 2 & 0 & 0 \end{bmatrix}$$ 356 357 """ 358 test_id = "dwdx_001_100" 359 nondim_velocity_gradient = np.array([[0, 0, 0], [0, 0, 0], [2, 0, 0]]) 360 # Strain rate is 0.5*(L + Lᵀ). 361 nondim_strain_rate = np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]) 362 363 optional_logging = cl.nullcontext() 364 if outdir is not None: 365 optional_logging = _io.logfile_enable( 366 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 367 ) 368 initial_angles = [] 369 rotation_rates = [] 370 target_rotation_rates = [] 371 372 with optional_logging: 373 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 374 _log.debug("θ (°): %s", np.rad2deg(θ)) 375 # Initial grain rotations around Y (anti-clockwise). 376 initial_orientations = Rotation.from_rotvec([[0, θ, 0]]) 377 orientation_matrix = initial_orientations[0].as_matrix() 378 slip_invariants = _core._get_slip_invariants( 379 nondim_strain_rate, orientation_matrix 380 ) 381 _log.debug("slip invariants: %s", slip_invariants) 382 383 crss = _core.get_crss( 384 _core.MineralPhase.olivine, 385 _core.MineralFabric.olivine_A, 386 ) 387 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 388 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 389 assert slip_system == ([0, 0, 1], [1, 0, 0]) 390 391 slip_rates = _core._get_slip_rates_olivine( 392 slip_invariants, slip_indices, crss, 3.5 393 ) 394 _log.debug("slip rates: %s", slip_rates) 395 396 deformation_rate = _core._get_deformation_rate( 397 _core.MineralPhase.olivine, 398 orientation_matrix, 399 slip_rates, 400 ) 401 _log.debug("deformation rate:\n%s", deformation_rate) 402 403 orientations_diff, fractions_diff = _core.derivatives( 404 regime=_core.DeformationRegime.matrix_dislocation, 405 phase=_core.MineralPhase.olivine, 406 fabric=_core.MineralFabric.olivine_A, 407 n_grains=1, 408 orientations=initial_orientations.as_matrix(), 409 fractions=np.array([1.0]), 410 strain_rate=nondim_strain_rate, 411 velocity_gradient=nondim_velocity_gradient, 412 deformation_gradient_spin=np.full((3, 3), np.nan), 413 stress_exponent=1.5, 414 deformation_exponent=3.5, 415 nucleation_efficiency=5, 416 gbm_mobility=0, 417 volume_fraction=1.0, 418 ) 419 cosθ = np.cos(θ) 420 cos2θ = np.cos(2 * θ) 421 sinθ = np.sin(θ) 422 target_orientations_diff = np.array( 423 [ 424 [-sinθ * (1 + cos2θ), 0, cosθ * (1 + cos2θ)], 425 [0, 0, 0], 426 [-cosθ * (1 + cos2θ), 0, -sinθ * (1 + cos2θ)], 427 ] 428 ) 429 np.testing.assert_allclose( 430 orientations_diff[0], target_orientations_diff 431 ) 432 if outdir is not None: 433 initial_angles.append(np.rad2deg(θ)) 434 rotation_rates.append( 435 np.sqrt( 436 orientations_diff[0][0, 0] ** 2 437 + orientations_diff[0][0, 2] ** 2 438 ) 439 ) 440 target_rotation_rates.append(1 + cos2θ) 441 assert np.isclose(np.sum(fractions_diff), 0.0) 442 443 if outdir is not None: 444 fig = _vis.figure(figscale=(1, 2 / 3)) 445 axs = fig.subplot_mosaic( # Put the legend on a special axes. 446 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 447 ) 448 ax = axs["spin"] 449 fig, ax, colors = _vis.spin( 450 ax, 451 initial_angles[::5], 452 rotation_rates[::5], 453 initial_angles, 454 target_rotation_rates, 455 shear_axis=90, 456 ) 457 _utils.redraw_legend( 458 ax, 459 legendax=axs["legend"], 460 loc="upper center", 461 ncols=3, 462 mode="expand", 463 ) 464 fig.savefig( 465 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 466 ) 467 468 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 469 def test_shear_dvdz_slip_010_001(self, outdir): 470 r"""Single grain of A-type olivine, slip on (010)[001]. 471 472 Velocity gradient: 473 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 0 & 0 & 2 \cr 0 & 0 & 0 \end{bmatrix}$$ 474 475 """ 476 test_id = "dvdz_010_001" 477 nondim_velocity_gradient = np.array([[0, 0, 0], [0, 0, 2], [0, 0, 0]]) 478 # Strain rate is 0.5*(L + Lᵀ). 479 nondim_strain_rate = np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]]) 480 481 optional_logging = cl.nullcontext() 482 if outdir is not None: 483 optional_logging = _io.logfile_enable( 484 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 485 ) 486 initial_angles = [] 487 rotation_rates = [] 488 target_rotation_rates = [] 489 490 with optional_logging: 491 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 492 _log.debug("θ (°): %s", np.rad2deg(θ)) 493 # Initial grain rotations around X (anti-clockwise). 494 initial_orientations = Rotation.from_rotvec([[θ, 0, 0]]) 495 orientation_matrix = initial_orientations[0].as_matrix() 496 slip_invariants = _core._get_slip_invariants( 497 nondim_strain_rate, orientation_matrix 498 ) 499 _log.debug("slip invariants: %s", slip_invariants) 500 501 crss = _core.get_crss( 502 _core.MineralPhase.olivine, 503 _core.MineralFabric.olivine_A, 504 ) 505 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 506 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 507 assert slip_system == ([0, 1, 0], [0, 0, 1]) 508 509 slip_rates = _core._get_slip_rates_olivine( 510 slip_invariants, slip_indices, crss, 3.5 511 ) 512 _log.debug("slip rates: %s", slip_rates) 513 514 deformation_rate = _core._get_deformation_rate( 515 _core.MineralPhase.olivine, 516 orientation_matrix, 517 slip_rates, 518 ) 519 _log.debug("deformation rate:\n%s", deformation_rate) 520 521 orientations_diff, fractions_diff = _core.derivatives( 522 regime=_core.DeformationRegime.matrix_dislocation, 523 phase=_core.MineralPhase.olivine, 524 fabric=_core.MineralFabric.olivine_A, 525 n_grains=1, 526 orientations=initial_orientations.as_matrix(), 527 fractions=np.array([1.0]), 528 strain_rate=nondim_strain_rate, 529 velocity_gradient=nondim_velocity_gradient, 530 deformation_gradient_spin=np.full((3, 3), np.nan), 531 stress_exponent=1.5, 532 deformation_exponent=3.5, 533 nucleation_efficiency=5, 534 gbm_mobility=0, 535 volume_fraction=1.0, 536 ) 537 cosθ = np.cos(θ) 538 cos2θ = np.cos(2 * θ) 539 sinθ = np.sin(θ) 540 target_orientations_diff = np.array( 541 [ 542 [0, 0, 0], 543 [0, -sinθ * (1 + cos2θ), -cosθ * (1 + cos2θ)], 544 [0, cosθ * (1 + cos2θ), -sinθ * (1 + cos2θ)], 545 ] 546 ) 547 np.testing.assert_allclose( 548 orientations_diff[0], target_orientations_diff 549 ) 550 if outdir is not None: 551 initial_angles.append(np.rad2deg(θ)) 552 rotation_rates.append( 553 np.sqrt( 554 orientations_diff[0][1, 1] ** 2 555 + orientations_diff[0][1, 2] ** 2 556 ) 557 ) 558 target_rotation_rates.append(1 + cos2θ) 559 assert np.isclose(np.sum(fractions_diff), 0.0) 560 561 if outdir is not None: 562 fig = _vis.figure(figscale=(1, 2 / 3)) 563 axs = fig.subplot_mosaic( # Put the legend on a special axes. 564 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 565 ) 566 ax = axs["spin"] 567 fig, ax, colors = _vis.spin( 568 ax, 569 initial_angles[::5], 570 rotation_rates[::5], 571 initial_angles, 572 target_rotation_rates, 573 shear_axis=90, 574 ) 575 _utils.redraw_legend( 576 ax, 577 legendax=axs["legend"], 578 loc="upper center", 579 ncols=3, 580 mode="expand", 581 ) 582 fig.savefig( 583 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 584 )
Single-grain A-type olivine analytical rotation rate tests.
115 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 116 def test_shear_dvdx_slip_010_100(self, outdir): 117 r"""Single grain of A-type olivine, slip on (010)[100]. 118 119 Velocity gradient: 120 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 121 122 """ 123 test_id = "dvdx_010_100" 124 nondim_velocity_gradient = np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]) 125 # Strain rate is 0.5*(L + Lᵀ). 126 nondim_strain_rate = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]) 127 128 optional_logging = cl.nullcontext() 129 if outdir is not None: 130 optional_logging = _io.logfile_enable( 131 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 132 ) 133 initial_angles = [] 134 rotation_rates = [] 135 target_rotation_rates = [] 136 137 with optional_logging: 138 for θ in np.mgrid[0 : 2 * np.pi : 3600j]: 139 _log.debug("θ (°): %s", np.rad2deg(θ)) 140 # Initial grain rotations around Z (anti-clockwise). 141 initial_orientations = Rotation.from_rotvec([[0, 0, θ]]) 142 orientation_matrix = initial_orientations[0].as_matrix() 143 slip_invariants = _core._get_slip_invariants( 144 nondim_strain_rate, orientation_matrix 145 ) 146 _log.debug("slip invariants: %s", slip_invariants) 147 148 crss = _core.get_crss( 149 _core.MineralPhase.olivine, 150 _core.MineralFabric.olivine_A, 151 ) 152 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 153 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 154 assert slip_system == ([0, 1, 0], [1, 0, 0]) 155 156 slip_rates = _core._get_slip_rates_olivine( 157 slip_invariants, slip_indices, crss, 3.5 158 ) 159 _log.debug("slip rates: %s", slip_rates) 160 161 deformation_rate = _core._get_deformation_rate( 162 _core.MineralPhase.olivine, 163 orientation_matrix, 164 slip_rates, 165 ) 166 _log.debug("deformation rate:\n%s", deformation_rate) 167 168 orientations_diff, fractions_diff = _core.derivatives( 169 regime=_core.DeformationRegime.matrix_dislocation, 170 phase=_core.MineralPhase.olivine, 171 fabric=_core.MineralFabric.olivine_A, 172 n_grains=1, 173 orientations=initial_orientations.as_matrix(), 174 fractions=np.array([1.0]), 175 strain_rate=nondim_strain_rate, 176 velocity_gradient=nondim_velocity_gradient, 177 deformation_gradient_spin=np.full((3, 3), np.nan), 178 stress_exponent=1.5, 179 deformation_exponent=3.5, 180 nucleation_efficiency=5, 181 gbm_mobility=0, 182 volume_fraction=1.0, 183 ) 184 cosθ = np.cos(θ) 185 cos2θ = np.cos(2 * θ) 186 sinθ = np.sin(θ) 187 target_orientations_diff = np.array( 188 [ 189 [sinθ * (1 + cos2θ), cosθ * (1 + cos2θ), 0], 190 [-cosθ * (1 + cos2θ), sinθ * (1 + cos2θ), 0], 191 [0, 0, 0], 192 ] 193 ) 194 np.testing.assert_allclose( 195 orientations_diff[0], target_orientations_diff 196 ) 197 if outdir is not None: 198 initial_angles.append(np.rad2deg(θ)) 199 rotation_rates.append( 200 np.sqrt( 201 orientations_diff[0][0, 0] ** 2 202 + orientations_diff[0][0, 1] ** 2 203 ) 204 ) 205 target_rotation_rates.append(1 + cos2θ) 206 assert np.isclose(np.sum(fractions_diff), 0.0) 207 208 if outdir is not None: 209 fig = _vis.figure(figscale=(1, 2 / 3)) 210 axs = fig.subplot_mosaic( # Put the legend on a special axes. 211 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 212 ) 213 ax = axs["spin"] 214 fig, ax, colors = _vis.spin( 215 ax, 216 initial_angles[::25], 217 rotation_rates[::25], 218 initial_angles, 219 target_rotation_rates, 220 shear_axis=90, 221 ) 222 _utils.redraw_legend( 223 ax, 224 legendax=axs["legend"], 225 loc="upper center", 226 ncols=3, 227 mode="expand", 228 ) 229 fig.savefig( 230 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 231 )
Single grain of A-type olivine, slip on (010)[100].
Velocity gradient: $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$
233 def test_shear_dudz_slip_001_100(self, outdir): 234 r"""Single grain of A-type olivine, slip on (001)[100]. 235 236 Velocity gradient: 237 $$\bm{L} = \begin{bmatrix} 0 & 0 & 2 \cr 0 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 238 239 """ 240 test_id = "dudz_001_100" 241 nondim_velocity_gradient = np.array([[0, 0, 2], [0, 0, 0], [0, 0, 0]]) 242 # Strain rate is 0.5*(L + Lᵀ). 243 nondim_strain_rate = np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]) 244 245 optional_logging = cl.nullcontext() 246 if outdir is not None: 247 optional_logging = _io.logfile_enable( 248 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 249 ) 250 initial_angles = [] 251 rotation_rates = [] 252 target_rotation_rates = [] 253 254 with optional_logging: 255 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 256 _log.debug("θ (°): %s", np.rad2deg(θ)) 257 # Initial grain rotations around Y (anti-clockwise). 258 initial_orientations = Rotation.from_rotvec([[0, θ, 0]]) 259 orientation_matrix = initial_orientations[0].as_matrix() 260 slip_invariants = _core._get_slip_invariants( 261 nondim_strain_rate, orientation_matrix 262 ) 263 _log.debug("slip invariants: %s", slip_invariants) 264 265 crss = _core.get_crss( 266 _core.MineralPhase.olivine, 267 _core.MineralFabric.olivine_A, 268 ) 269 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 270 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 271 assert slip_system == ([0, 0, 1], [1, 0, 0]) 272 273 slip_rates = _core._get_slip_rates_olivine( 274 slip_invariants, slip_indices, crss, 3.5 275 ) 276 _log.debug("slip rates: %s", slip_rates) 277 278 deformation_rate = _core._get_deformation_rate( 279 _core.MineralPhase.olivine, 280 orientation_matrix, 281 slip_rates, 282 ) 283 _log.debug("deformation rate:\n%s", deformation_rate) 284 285 orientations_diff, fractions_diff = _core.derivatives( 286 regime=_core.DeformationRegime.matrix_dislocation, 287 phase=_core.MineralPhase.olivine, 288 fabric=_core.MineralFabric.olivine_A, 289 n_grains=1, 290 orientations=initial_orientations.as_matrix(), 291 fractions=np.array([1.0]), 292 strain_rate=nondim_strain_rate, 293 velocity_gradient=nondim_velocity_gradient, 294 deformation_gradient_spin=np.full((3, 3), np.nan), 295 stress_exponent=1.5, 296 deformation_exponent=3.5, 297 nucleation_efficiency=5, 298 gbm_mobility=0, 299 volume_fraction=1.0, 300 ) 301 cosθ = np.cos(θ) 302 cos2θ = np.cos(2 * θ) 303 sinθ = np.sin(θ) 304 target_orientations_diff = np.array( 305 [ 306 [-sinθ * (cos2θ - 1), 0, cosθ * (cos2θ - 1)], 307 [0, 0, 0], 308 [cosθ * (1 - cos2θ), 0, sinθ * (1 - cos2θ)], 309 ] 310 ) 311 np.testing.assert_allclose( 312 orientations_diff[0], target_orientations_diff 313 ) 314 if outdir is not None: 315 initial_angles.append(np.rad2deg(θ)) 316 rotation_rates.append( 317 np.sqrt( 318 orientations_diff[0][0, 0] ** 2 319 + orientations_diff[0][0, 2] ** 2 320 ) 321 ) 322 target_rotation_rates.append(1 - cos2θ) 323 assert np.isclose(np.sum(fractions_diff), 0.0) 324 325 if outdir is not None: 326 fig = _vis.figure(figscale=(1, 2 / 3)) 327 axs = fig.subplot_mosaic( # Put the legend on a special axes. 328 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 329 ) 330 ax = axs["spin"] 331 fig, ax, colors = _vis.spin( 332 ax, 333 initial_angles[::5], 334 rotation_rates[::5], 335 initial_angles, 336 target_rotation_rates, 337 shear_axis=0, 338 ) 339 _utils.redraw_legend( 340 ax, 341 legendax=axs["legend"], 342 loc="upper center", 343 ncols=3, 344 mode="expand", 345 ) 346 fig.savefig( 347 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 348 )
Single grain of A-type olivine, slip on (001)[100].
Velocity gradient: $$\bm{L} = \begin{bmatrix} 0 & 0 & 2 \cr 0 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$
350 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 351 def test_shear_dwdx_slip_001_100(self, outdir): 352 r"""Single grain of A-type olivine, slip on (001)[100]. 353 354 Velocity gradient: 355 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 0 & 0 & 0 \cr 2 & 0 & 0 \end{bmatrix}$$ 356 357 """ 358 test_id = "dwdx_001_100" 359 nondim_velocity_gradient = np.array([[0, 0, 0], [0, 0, 0], [2, 0, 0]]) 360 # Strain rate is 0.5*(L + Lᵀ). 361 nondim_strain_rate = np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]) 362 363 optional_logging = cl.nullcontext() 364 if outdir is not None: 365 optional_logging = _io.logfile_enable( 366 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 367 ) 368 initial_angles = [] 369 rotation_rates = [] 370 target_rotation_rates = [] 371 372 with optional_logging: 373 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 374 _log.debug("θ (°): %s", np.rad2deg(θ)) 375 # Initial grain rotations around Y (anti-clockwise). 376 initial_orientations = Rotation.from_rotvec([[0, θ, 0]]) 377 orientation_matrix = initial_orientations[0].as_matrix() 378 slip_invariants = _core._get_slip_invariants( 379 nondim_strain_rate, orientation_matrix 380 ) 381 _log.debug("slip invariants: %s", slip_invariants) 382 383 crss = _core.get_crss( 384 _core.MineralPhase.olivine, 385 _core.MineralFabric.olivine_A, 386 ) 387 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 388 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 389 assert slip_system == ([0, 0, 1], [1, 0, 0]) 390 391 slip_rates = _core._get_slip_rates_olivine( 392 slip_invariants, slip_indices, crss, 3.5 393 ) 394 _log.debug("slip rates: %s", slip_rates) 395 396 deformation_rate = _core._get_deformation_rate( 397 _core.MineralPhase.olivine, 398 orientation_matrix, 399 slip_rates, 400 ) 401 _log.debug("deformation rate:\n%s", deformation_rate) 402 403 orientations_diff, fractions_diff = _core.derivatives( 404 regime=_core.DeformationRegime.matrix_dislocation, 405 phase=_core.MineralPhase.olivine, 406 fabric=_core.MineralFabric.olivine_A, 407 n_grains=1, 408 orientations=initial_orientations.as_matrix(), 409 fractions=np.array([1.0]), 410 strain_rate=nondim_strain_rate, 411 velocity_gradient=nondim_velocity_gradient, 412 deformation_gradient_spin=np.full((3, 3), np.nan), 413 stress_exponent=1.5, 414 deformation_exponent=3.5, 415 nucleation_efficiency=5, 416 gbm_mobility=0, 417 volume_fraction=1.0, 418 ) 419 cosθ = np.cos(θ) 420 cos2θ = np.cos(2 * θ) 421 sinθ = np.sin(θ) 422 target_orientations_diff = np.array( 423 [ 424 [-sinθ * (1 + cos2θ), 0, cosθ * (1 + cos2θ)], 425 [0, 0, 0], 426 [-cosθ * (1 + cos2θ), 0, -sinθ * (1 + cos2θ)], 427 ] 428 ) 429 np.testing.assert_allclose( 430 orientations_diff[0], target_orientations_diff 431 ) 432 if outdir is not None: 433 initial_angles.append(np.rad2deg(θ)) 434 rotation_rates.append( 435 np.sqrt( 436 orientations_diff[0][0, 0] ** 2 437 + orientations_diff[0][0, 2] ** 2 438 ) 439 ) 440 target_rotation_rates.append(1 + cos2θ) 441 assert np.isclose(np.sum(fractions_diff), 0.0) 442 443 if outdir is not None: 444 fig = _vis.figure(figscale=(1, 2 / 3)) 445 axs = fig.subplot_mosaic( # Put the legend on a special axes. 446 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 447 ) 448 ax = axs["spin"] 449 fig, ax, colors = _vis.spin( 450 ax, 451 initial_angles[::5], 452 rotation_rates[::5], 453 initial_angles, 454 target_rotation_rates, 455 shear_axis=90, 456 ) 457 _utils.redraw_legend( 458 ax, 459 legendax=axs["legend"], 460 loc="upper center", 461 ncols=3, 462 mode="expand", 463 ) 464 fig.savefig( 465 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 466 )
Single grain of A-type olivine, slip on (001)[100].
Velocity gradient: $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 0 & 0 & 0 \cr 2 & 0 & 0 \end{bmatrix}$$
468 @pytest.mark.skipif(_utils.in_ci("win32"), reason="Not equal to tolerance") 469 def test_shear_dvdz_slip_010_001(self, outdir): 470 r"""Single grain of A-type olivine, slip on (010)[001]. 471 472 Velocity gradient: 473 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 0 & 0 & 2 \cr 0 & 0 & 0 \end{bmatrix}$$ 474 475 """ 476 test_id = "dvdz_010_001" 477 nondim_velocity_gradient = np.array([[0, 0, 0], [0, 0, 2], [0, 0, 0]]) 478 # Strain rate is 0.5*(L + Lᵀ). 479 nondim_strain_rate = np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]]) 480 481 optional_logging = cl.nullcontext() 482 if outdir is not None: 483 optional_logging = _io.logfile_enable( 484 f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.log" 485 ) 486 initial_angles = [] 487 rotation_rates = [] 488 target_rotation_rates = [] 489 490 with optional_logging: 491 for θ in np.mgrid[0 : 2 * np.pi : 360j]: 492 _log.debug("θ (°): %s", np.rad2deg(θ)) 493 # Initial grain rotations around X (anti-clockwise). 494 initial_orientations = Rotation.from_rotvec([[θ, 0, 0]]) 495 orientation_matrix = initial_orientations[0].as_matrix() 496 slip_invariants = _core._get_slip_invariants( 497 nondim_strain_rate, orientation_matrix 498 ) 499 _log.debug("slip invariants: %s", slip_invariants) 500 501 crss = _core.get_crss( 502 _core.MineralPhase.olivine, 503 _core.MineralFabric.olivine_A, 504 ) 505 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 506 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 507 assert slip_system == ([0, 1, 0], [0, 0, 1]) 508 509 slip_rates = _core._get_slip_rates_olivine( 510 slip_invariants, slip_indices, crss, 3.5 511 ) 512 _log.debug("slip rates: %s", slip_rates) 513 514 deformation_rate = _core._get_deformation_rate( 515 _core.MineralPhase.olivine, 516 orientation_matrix, 517 slip_rates, 518 ) 519 _log.debug("deformation rate:\n%s", deformation_rate) 520 521 orientations_diff, fractions_diff = _core.derivatives( 522 regime=_core.DeformationRegime.matrix_dislocation, 523 phase=_core.MineralPhase.olivine, 524 fabric=_core.MineralFabric.olivine_A, 525 n_grains=1, 526 orientations=initial_orientations.as_matrix(), 527 fractions=np.array([1.0]), 528 strain_rate=nondim_strain_rate, 529 velocity_gradient=nondim_velocity_gradient, 530 deformation_gradient_spin=np.full((3, 3), np.nan), 531 stress_exponent=1.5, 532 deformation_exponent=3.5, 533 nucleation_efficiency=5, 534 gbm_mobility=0, 535 volume_fraction=1.0, 536 ) 537 cosθ = np.cos(θ) 538 cos2θ = np.cos(2 * θ) 539 sinθ = np.sin(θ) 540 target_orientations_diff = np.array( 541 [ 542 [0, 0, 0], 543 [0, -sinθ * (1 + cos2θ), -cosθ * (1 + cos2θ)], 544 [0, cosθ * (1 + cos2θ), -sinθ * (1 + cos2θ)], 545 ] 546 ) 547 np.testing.assert_allclose( 548 orientations_diff[0], target_orientations_diff 549 ) 550 if outdir is not None: 551 initial_angles.append(np.rad2deg(θ)) 552 rotation_rates.append( 553 np.sqrt( 554 orientations_diff[0][1, 1] ** 2 555 + orientations_diff[0][1, 2] ** 2 556 ) 557 ) 558 target_rotation_rates.append(1 + cos2θ) 559 assert np.isclose(np.sum(fractions_diff), 0.0) 560 561 if outdir is not None: 562 fig = _vis.figure(figscale=(1, 2 / 3)) 563 axs = fig.subplot_mosaic( # Put the legend on a special axes. 564 [["legend"], ["spin"]], gridspec_kw={"height_ratios": [0.1, 1]} 565 ) 566 ax = axs["spin"] 567 fig, ax, colors = _vis.spin( 568 ax, 569 initial_angles[::5], 570 rotation_rates[::5], 571 initial_angles, 572 target_rotation_rates, 573 shear_axis=90, 574 ) 575 _utils.redraw_legend( 576 ax, 577 legendax=axs["legend"], 578 loc="upper center", 579 ncols=3, 580 mode="expand", 581 ) 582 fig.savefig( 583 _io.resolve_path(f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}.pdf") 584 )
Single grain of A-type olivine, slip on (010)[001].
Velocity gradient: $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 0 & 0 & 2 \cr 0 & 0 & 0 \end{bmatrix}$$
587class TestRecrystallisation2D: 588 """Basic recrystallisation tests for 2D simple shear.""" 589 590 class_id = "recrystallisation_2D" 591 592 def test_shear_dvdx_circle_inplane(self, outdir): 593 r"""360000 grains of A-type olivine with uniform spread of a-axes on a circle. 594 595 Grain growth rates are compared to analytical calculations. 596 The a-axes are distributed in the YX plane (i.e.\ rotated around Z). 597 598 Velocity gradient: 599 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 600 601 """ 602 test_id = "dvdx_circle_inplane" 603 604 optional_logging = cl.nullcontext() 605 # Initial uniform distribution of orientations on a 2D circle. 606 initial_angles = np.mgrid[0 : 2 * np.pi : 360000j] 607 cos2θ = np.cos(2 * initial_angles) 608 if outdir is not None: 609 out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}" 610 optional_logging = _io.logfile_enable(f"{out_basepath}.log") 611 612 with optional_logging: 613 initial_orientations = Rotation.from_rotvec( 614 [[0, 0, θ] for θ in initial_angles] 615 ) 616 orientations_diff, fractions_diff = _core.derivatives( 617 regime=_core.DeformationRegime.matrix_dislocation, 618 phase=_core.MineralPhase.olivine, 619 fabric=_core.MineralFabric.olivine_A, 620 n_grains=360000, 621 orientations=initial_orientations.as_matrix(), 622 fractions=np.full(360000, 1 / 360000), 623 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 624 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 625 deformation_gradient_spin=np.full((3, 3), np.nan), 626 stress_exponent=1.5, 627 deformation_exponent=3.5, 628 nucleation_efficiency=5, 629 gbm_mobility=125, 630 volume_fraction=1.0, 631 ) 632 ρ = np.abs(cos2θ) ** (1.5 / 3.5) 633 # No need to sum over slip systems, only (010)[100] can activate in this 634 # 2D simple shear deformation geometry (ρₛ=ρ). 635 target_strain_energies = ρ * np.exp(-5 * ρ**2) 636 target_fractions_diff = np.array( 637 [ # df/dt* = - M* f (E - E_mean) 638 -125 * 1 / 360000 * (E - np.mean(target_strain_energies)) 639 for E in target_strain_energies 640 ] 641 ) 642 643 if outdir is not None: 644 fig = _vis.figure(figscale=(1, 4 / 3)) 645 axs = fig.subplot_mosaic( # Put the legend on a special axes. 646 [["legend"], ["spin"], ["growth"]], 647 gridspec_kw={"height_ratios": [0.2, 1, 1]}, 648 sharex=True, 649 ) 650 ax = axs["spin"] 651 xvals = np.rad2deg(initial_angles) 652 fig, ax, colors = _vis.spin( 653 ax, 654 xvals[::2500], 655 np.sqrt( 656 [ 657 o[0, 0] ** 2 + o[0, 1] ** 2 + o[0, 2] ** 2 658 for o in orientations_diff 659 ][::2500] 660 ), 661 xvals, 662 1 + cos2θ, 663 shear_axis=90, 664 ) 665 ax.get_legend().remove() 666 ax.label_outer() 667 ax2 = axs["growth"] 668 fig, ax2, colors = _vis.growth( 669 ax2, 670 xvals[::2500], 671 fractions_diff[::2500], 672 xvals, 673 target_fractions_diff, 674 shear_axis=90, 675 ) 676 _utils.redraw_legend( 677 ax2, 678 legendax=axs["legend"], 679 loc="upper center", 680 ncols=3, 681 mode="expand", 682 ) 683 _utils.add_subplot_labels(axs, labelmap={"spin": "a)", "growth": "b)"}) 684 fig.savefig(f"{out_basepath}.pdf") 685 686 nt.assert_allclose(fractions_diff, target_fractions_diff, atol=1e-15, rtol=0) 687 688 def test_shear_dvdx_circle_shearplane(self, outdir): 689 r"""360000 grains of A-type olivine with uniform spread of a-axes on a circle. 690 691 Unlike `test_shear_dvdx_circle_inplane`, two slip systems are active here, 692 with cyclical variety in which one is dominant depending on grain orientation. 693 The a-axes are distributed in the YZ plane 694 (i.e.\ extrinsic rotation around Z by 90° and then around X). 695 696 Velocity gradient: 697 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 698 699 """ 700 test_id = "dvdx_circle_shearplane" 701 702 optional_logging = cl.nullcontext() 703 # Initial uniform distribution of orientations on a 2D circle. 704 initial_angles = np.mgrid[0 : 2 * np.pi : 360000j] 705 if outdir is not None: 706 out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}" 707 optional_logging = _io.logfile_enable(f"{out_basepath}.log") 708 709 with optional_logging: 710 initial_orientations = Rotation.from_euler( 711 "zx", [[np.pi / 2, θ] for θ in initial_angles] 712 ) 713 orientations_diff, fractions_diff = _core.derivatives( 714 regime=_core.DeformationRegime.matrix_dislocation, 715 phase=_core.MineralPhase.olivine, 716 fabric=_core.MineralFabric.olivine_A, 717 n_grains=360000, 718 orientations=initial_orientations.as_matrix(), 719 fractions=np.full(360000, 1 / 360000), 720 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 721 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 722 deformation_gradient_spin=np.full((3, 3), np.nan), 723 stress_exponent=1.5, 724 deformation_exponent=3.5, 725 nucleation_efficiency=5, 726 gbm_mobility=125, 727 volume_fraction=1.0, 728 ) 729 730 if outdir is not None: 731 fig = _vis.figure(figscale=(1, 4 / 3)) 732 axs = fig.subplot_mosaic([["spin"], ["growth"]], sharex=True) 733 ax = axs["spin"] 734 xvals = np.rad2deg(initial_angles) 735 fig, ax, colors = _vis.spin( 736 ax, 737 xvals[::2500], 738 np.sqrt( 739 [ 740 o[0, 0] ** 2 + o[0, 1] ** 2 + o[0, 2] ** 2 741 for o in orientations_diff 742 ][::2500] 743 ), 744 ) 745 ax.get_legend().remove() 746 ax.label_outer() 747 ax2 = axs["growth"] 748 fig, ax2, colors = _vis.growth(ax2, xvals[::2500], fractions_diff[::2500]) 749 _utils.add_subplot_labels(axs, labelmap={"spin": "a)", "growth": "b)"}) 750 fig.savefig(f"{out_basepath}.pdf") 751 752 # Check dominant slip system every 1°. 753 for θ in initial_angles[::1000]: 754 slip_invariants = _core._get_slip_invariants( 755 np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 756 Rotation.from_euler("zx", [np.pi / 2, θ]).as_matrix(), 757 ) 758 θ = np.rad2deg(θ) 759 crss = _core.get_crss( 760 _core.MineralPhase.olivine, 761 _core.MineralFabric.olivine_A, 762 ) 763 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 764 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 765 766 if 0 <= θ < 64: 767 assert slip_system == ([0, 1, 0], [1, 0, 0]) 768 elif 64 <= θ < 117: 769 assert slip_system == ([0, 0, 1], [1, 0, 0]) 770 elif 117 <= θ < 244: 771 assert slip_system == ([0, 1, 0], [1, 0, 0]) 772 elif 244 <= θ < 297: 773 assert slip_system == ([0, 0, 1], [1, 0, 0]) 774 else: 775 assert slip_system == ([0, 1, 0], [1, 0, 0])
Basic recrystallisation tests for 2D simple shear.
592 def test_shear_dvdx_circle_inplane(self, outdir): 593 r"""360000 grains of A-type olivine with uniform spread of a-axes on a circle. 594 595 Grain growth rates are compared to analytical calculations. 596 The a-axes are distributed in the YX plane (i.e.\ rotated around Z). 597 598 Velocity gradient: 599 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 600 601 """ 602 test_id = "dvdx_circle_inplane" 603 604 optional_logging = cl.nullcontext() 605 # Initial uniform distribution of orientations on a 2D circle. 606 initial_angles = np.mgrid[0 : 2 * np.pi : 360000j] 607 cos2θ = np.cos(2 * initial_angles) 608 if outdir is not None: 609 out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}" 610 optional_logging = _io.logfile_enable(f"{out_basepath}.log") 611 612 with optional_logging: 613 initial_orientations = Rotation.from_rotvec( 614 [[0, 0, θ] for θ in initial_angles] 615 ) 616 orientations_diff, fractions_diff = _core.derivatives( 617 regime=_core.DeformationRegime.matrix_dislocation, 618 phase=_core.MineralPhase.olivine, 619 fabric=_core.MineralFabric.olivine_A, 620 n_grains=360000, 621 orientations=initial_orientations.as_matrix(), 622 fractions=np.full(360000, 1 / 360000), 623 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 624 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 625 deformation_gradient_spin=np.full((3, 3), np.nan), 626 stress_exponent=1.5, 627 deformation_exponent=3.5, 628 nucleation_efficiency=5, 629 gbm_mobility=125, 630 volume_fraction=1.0, 631 ) 632 ρ = np.abs(cos2θ) ** (1.5 / 3.5) 633 # No need to sum over slip systems, only (010)[100] can activate in this 634 # 2D simple shear deformation geometry (ρₛ=ρ). 635 target_strain_energies = ρ * np.exp(-5 * ρ**2) 636 target_fractions_diff = np.array( 637 [ # df/dt* = - M* f (E - E_mean) 638 -125 * 1 / 360000 * (E - np.mean(target_strain_energies)) 639 for E in target_strain_energies 640 ] 641 ) 642 643 if outdir is not None: 644 fig = _vis.figure(figscale=(1, 4 / 3)) 645 axs = fig.subplot_mosaic( # Put the legend on a special axes. 646 [["legend"], ["spin"], ["growth"]], 647 gridspec_kw={"height_ratios": [0.2, 1, 1]}, 648 sharex=True, 649 ) 650 ax = axs["spin"] 651 xvals = np.rad2deg(initial_angles) 652 fig, ax, colors = _vis.spin( 653 ax, 654 xvals[::2500], 655 np.sqrt( 656 [ 657 o[0, 0] ** 2 + o[0, 1] ** 2 + o[0, 2] ** 2 658 for o in orientations_diff 659 ][::2500] 660 ), 661 xvals, 662 1 + cos2θ, 663 shear_axis=90, 664 ) 665 ax.get_legend().remove() 666 ax.label_outer() 667 ax2 = axs["growth"] 668 fig, ax2, colors = _vis.growth( 669 ax2, 670 xvals[::2500], 671 fractions_diff[::2500], 672 xvals, 673 target_fractions_diff, 674 shear_axis=90, 675 ) 676 _utils.redraw_legend( 677 ax2, 678 legendax=axs["legend"], 679 loc="upper center", 680 ncols=3, 681 mode="expand", 682 ) 683 _utils.add_subplot_labels(axs, labelmap={"spin": "a)", "growth": "b)"}) 684 fig.savefig(f"{out_basepath}.pdf") 685 686 nt.assert_allclose(fractions_diff, target_fractions_diff, atol=1e-15, rtol=0)
360000 grains of A-type olivine with uniform spread of a-axes on a circle.
Grain growth rates are compared to analytical calculations. The a-axes are distributed in the YX plane (i.e.\ rotated around Z).
Velocity gradient: $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$
688 def test_shear_dvdx_circle_shearplane(self, outdir): 689 r"""360000 grains of A-type olivine with uniform spread of a-axes on a circle. 690 691 Unlike `test_shear_dvdx_circle_inplane`, two slip systems are active here, 692 with cyclical variety in which one is dominant depending on grain orientation. 693 The a-axes are distributed in the YZ plane 694 (i.e.\ extrinsic rotation around Z by 90° and then around X). 695 696 Velocity gradient: 697 $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$ 698 699 """ 700 test_id = "dvdx_circle_shearplane" 701 702 optional_logging = cl.nullcontext() 703 # Initial uniform distribution of orientations on a 2D circle. 704 initial_angles = np.mgrid[0 : 2 * np.pi : 360000j] 705 if outdir is not None: 706 out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_{test_id}" 707 optional_logging = _io.logfile_enable(f"{out_basepath}.log") 708 709 with optional_logging: 710 initial_orientations = Rotation.from_euler( 711 "zx", [[np.pi / 2, θ] for θ in initial_angles] 712 ) 713 orientations_diff, fractions_diff = _core.derivatives( 714 regime=_core.DeformationRegime.matrix_dislocation, 715 phase=_core.MineralPhase.olivine, 716 fabric=_core.MineralFabric.olivine_A, 717 n_grains=360000, 718 orientations=initial_orientations.as_matrix(), 719 fractions=np.full(360000, 1 / 360000), 720 strain_rate=np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 721 velocity_gradient=np.array([[0, 0, 0], [2, 0, 0], [0, 0, 0]]), 722 deformation_gradient_spin=np.full((3, 3), np.nan), 723 stress_exponent=1.5, 724 deformation_exponent=3.5, 725 nucleation_efficiency=5, 726 gbm_mobility=125, 727 volume_fraction=1.0, 728 ) 729 730 if outdir is not None: 731 fig = _vis.figure(figscale=(1, 4 / 3)) 732 axs = fig.subplot_mosaic([["spin"], ["growth"]], sharex=True) 733 ax = axs["spin"] 734 xvals = np.rad2deg(initial_angles) 735 fig, ax, colors = _vis.spin( 736 ax, 737 xvals[::2500], 738 np.sqrt( 739 [ 740 o[0, 0] ** 2 + o[0, 1] ** 2 + o[0, 2] ** 2 741 for o in orientations_diff 742 ][::2500] 743 ), 744 ) 745 ax.get_legend().remove() 746 ax.label_outer() 747 ax2 = axs["growth"] 748 fig, ax2, colors = _vis.growth(ax2, xvals[::2500], fractions_diff[::2500]) 749 _utils.add_subplot_labels(axs, labelmap={"spin": "a)", "growth": "b)"}) 750 fig.savefig(f"{out_basepath}.pdf") 751 752 # Check dominant slip system every 1°. 753 for θ in initial_angles[::1000]: 754 slip_invariants = _core._get_slip_invariants( 755 np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]]), 756 Rotation.from_euler("zx", [np.pi / 2, θ]).as_matrix(), 757 ) 758 θ = np.rad2deg(θ) 759 crss = _core.get_crss( 760 _core.MineralPhase.olivine, 761 _core.MineralFabric.olivine_A, 762 ) 763 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 764 slip_system = _minerals.OLIVINE_SLIP_SYSTEMS[slip_indices[-1]] 765 766 if 0 <= θ < 64: 767 assert slip_system == ([0, 1, 0], [1, 0, 0]) 768 elif 64 <= θ < 117: 769 assert slip_system == ([0, 0, 1], [1, 0, 0]) 770 elif 117 <= θ < 244: 771 assert slip_system == ([0, 1, 0], [1, 0, 0]) 772 elif 244 <= θ < 297: 773 assert slip_system == ([0, 0, 1], [1, 0, 0]) 774 else: 775 assert slip_system == ([0, 1, 0], [1, 0, 0])
360000 grains of A-type olivine with uniform spread of a-axes on a circle.
Unlike test_shear_dvdx_circle_inplane
, two slip systems are active here,
with cyclical variety in which one is dominant depending on grain orientation.
The a-axes are distributed in the YZ plane
(i.e.\ extrinsic rotation around Z by 90° and then around X).
Velocity gradient: $$\bm{L} = \begin{bmatrix} 0 & 0 & 0 \cr 2 & 0 & 0 \cr 0 & 0 & 0 \end{bmatrix}$$