pydrex.core
PyDRex: Core D-Rex functions and enums.
The function derivatives
implements the core D-Rex solver, which computes the
crystallographic rotation rate and changes in fractional grain volumes.
Acronyms:
- CRSS = Critical Resolved Shear Stress, i.e. threshold stress required to initiate slip on a slip system, normalised to the stress required to initiate slip on the softest slip system
1"""> PyDRex: Core D-Rex functions and enums. 2 3The function `derivatives` implements the core D-Rex solver, which computes the 4crystallographic rotation rate and changes in fractional grain volumes. 5 6**Acronyms:** 7- CRSS = Critical Resolved Shear Stress, 8 i.e. threshold stress required to initiate slip on a slip system, 9 normalised to the stress required to initiate slip on the softest slip system 10 11""" 12 13from dataclasses import asdict, dataclass 14from enum import IntEnum, unique 15 16import numba as nb 17import numpy as np 18 19# NOTE: Do NOT import any pydrex submodules here to avoid cyclical imports. 20 21 22_USE_ORIGINAL_DREX = False # Switch to use D-Rex 2004 version without corrections. 23 24 25PERMUTATION_SYMBOL = np.array( 26 [ 27 [[0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, -1.0, 0.0]], 28 [[0.0, 0.0, -1.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]], 29 [[0.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], 30 ] 31) 32"""Sometimes called the Levi-Civita symbol.""" 33 34 35@unique 36class MineralPhase(IntEnum): 37 """Supported mineral phases. 38 39 Forsterite and fayalite are grouped into “olivine”, because we treat them as 40 rheologically equivalent. 41 42 """ 43 44 olivine = 0 45 """(Mg,Fe)₂SiO₄""" 46 enstatite = 1 47 """MgSiO₃""" 48 49 50@unique 51class MineralFabric(IntEnum): 52 """Supported mineral fabrics. 53 54 The following fabric types are supported: 55 - olivine A-E type fabrics according to e.g. 56 [Karato et al. (2008)](https://doi.org/10.1146%2Fannurev.earth.36.031207.124120). 57 - enstatite AB fabric, see 58 [Bernard et al. (2021)](https://doi.org/10.1016/j.tecto.2021.228954). 59 60 """ 61 62 olivine_A = 0 63 olivine_B = 1 64 olivine_C = 2 65 olivine_D = 3 66 olivine_E = 4 67 enstatite_AB = 5 68 69 70@unique 71class DeformationRegime(IntEnum): 72 r"""Ordinals to track distinct regimes of dominant deformation mechanisms. 73 74 The mechanism of deformation that dominates accommodation of plastic deformation 75 depends in general both on material properties such as grain size and mineral phase 76 content as well as on thermodynamic properties such as temperature, pressure and 77 water fugacity. 78 79 The activity of diffusive mechanisms depends more strongly on grain size, whereas 80 that of dislocation mechanisms depends more strongly on temperature. High 81 temperatures enable more frequent recovery of dislocation density equilibrium via 82 e.g. dislocation climb. Dislocation mechanisms are often accompanied by dynamic 83 recrystallisation, which acts as an additional recovery mechanism. 84 85 Diffusive mechanisms are expected, however, to become dominant at depth, as these 86 mechanisms are primarily facilitated by Si diffusion in olivine, which is 87 temperature-dependent. 88 89 Rheology in the intra-granular dislocation regime was classically described by 90 separate flow laws depending on temperature; a power-law at high temperature 91 [$\dot{ε} ∝ σⁿ$] and an exponential-law at low temperature [$\dot{ε} ∝ \exp(σ)$]. 92 More recent work has suggested unified dislocation creep flow laws, 93 e.g. [Gouriet et al. 2019](http://dx.doi.org/10.1016/j.epsl.2018.10.049), 94 [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) and 95 [Demouchy et al. 2023](http://dx.doi.org/10.2138/gselements.19.3.151). 96 97 .. note:: Although a draft texture evolution behaviour is implemented in the 98 `frictional_yielding` regime, it is experimental and not yet configurable via 99 the parameter interface. 100 101 """ 102 103 min_viscosity = 0 104 """Arbitrary lower-bound viscosity regime.""" 105 matrix_diffusion = 1 106 """Intra-granular Nabarro-Herring creep, i.e. grains diffuse through the matrix.""" 107 boundary_diffusion = 2 108 """Inter-granular Coble creep, i.e. grains diffuse along grain boundaries.""" 109 sliding_diffusion = 3 110 """Inter-granular diffusion-assisted grain-boundary sliding (diffGBS).""" 111 matrix_dislocation = 4 112 """Intra-granular dislocation creep (glide + climb) and dynamic recrystallisation.""" 113 sliding_dislocation = 5 114 """Inter-granular dislocation-assisted grain-boundary sliding (disGBS).""" 115 frictional_yielding = 6 116 """Frictional sliding along micro-fractures (Byerlee's law for yield strength).""" 117 max_viscosity = 7 118 """Arbitrary upper-bound viscosity regime.""" 119 120 121@dataclass(frozen=True) 122class DefaultParams: 123 phase_assemblage: tuple = (MineralPhase.olivine,) 124 """Mineral phases present in the aggregate.""" 125 phase_fractions: tuple = (1.0,) 126 """Volume fractions of each mineral phase present in the aggregate.""" 127 stress_exponent: float = 1.5 128 """The value for $p$ in $ρ ∝ τᵖ$ where $ρ$ is the dislocation density and $τ$ the shear stress. 129 130 Default value taken from [Kaminski et al. 2004](https://doi.org/10.1111/j.1365-246X.2004.02308.x) 131 based on studies referenced therein. 132 133 """ 134 deformation_exponent: float = 3.5 135 """The value for $n$ in $τ ∝ |D|^{1/n}$ where $τ$ is the shear stress and D the deformation rate. 136 137 Default value taken from [Kaminski et al. 2004](https://doi.org/10.1111/j.1365-246X.2004.02308.x) 138 based on studies referenced therein. 139 140 """ 141 gbm_mobility: int = 125 142 """Dimensionless grain boundary mobility parameter (M*). 143 144 This controls the rate of all dynamic recrystallisation processes in 145 `DeformationRegime.matrix_dislocation`. 146 147 .. note:: This parameter is not easy to constrain. Reasonable values may lie in the 148 range [10, 150]. Comparing outputs for multiple M* values is recommended. 149 150 """ 151 gbs_threshold: float = 0.3 152 """Grain boundary sliding threshold. 153 154 In `DeformationRegime.matrix_dislocation` or `DeformationRegime.sliding_dislocation` 155 this controls the smallest size of a grain, relative to its original size, 156 at which it will still deform via dislocation creep. 157 Smaller grains will instead deform via grain boundary sliding, 158 therefore not contributing to any further texture evolution. 159 160 .. note:: Values for this parameter do NOT correspond to a physical grain size 161 threshold. Grains in PyDRex are a surrogate numerical discretisation. There are 162 likely to be feedbacks between this parameter and `number_of_grains`. Values of 163 ~0.3 have been used in numerous studies which employed ~3000 grains. 164 165 """ 166 nucleation_efficiency: float = 5.0 167 """Dimensionless nucleation efficiency (λ*). 168 169 This controls the nucleation of subgrains in `DeformationRegime.matrix_dislocation`. 170 171 The default value comes from [Kaminski & Ribe 2001](https://doi.org/10.1016%2Fs0012-821x%2801%2900356-9). 172 173 """ 174 number_of_grains: int = 3500 175 """Number of surrogate grains for numerical discretisation of the aggregate. 176 177 This is a numerical discretisation, so generally more grains is better. However, 178 there is a sharp tradeoff in performance, especially for M-index calculation. 179 180 """ 181 initial_olivine_fabric: MineralFabric = MineralFabric.olivine_A 182 """Olivine fabric (CRSS distribution) at the beginning of the simulation. 183 184 In general, the fabric should be allowed to change during the simulation, 185 but that is not yet implemented. 186 187 """ 188 disl_Peierls_stress: float = 2 189 """Stress barrier in GPa for activation of dislocation motion at low temperatures. 190 191 - 2GPa suggested by [Demouchy et al. 2023](http://dx.doi.org/10.2138/gselements.19.3.151) 192 193 .. note:: Not relevant if the unified dislocation creep flow law is used. 194 195 """ 196 # NOTE: For now, we just roll this into the prefactors. 197 # water_fugacity: float = 1.5 198 # """Approximate constant water fugacity of the aggregate (GPa).""" 199 # TODO: Check units of Garel 2020 pre-exp value below. 200 # TODO: Find and add references for the other value. 201 disl_prefactors: tuple = (1e-16, 1e-17) 202 """Prefactors for dislocation creep exponential & power laws (s⁻¹). 203 204 - B = 1.7e-16 s⁻¹ for the exponential law suggested by [Demouchy et al. 2023](http://dx.doi.org/10.2138/gselements.19.3.151) 205 - pre-exponential factor of 4.4e-17 Pa⁻ⁿs⁻¹ used for the exponential law by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 206 207 """ 208 # TODO: Add references, tweak default values if necessary. 209 diff_prefactors: tuple = (1e-10, 1e-10) 210 r"""Prefactors for (matrix and boundary) diffusion creep power laws (s⁻¹). 211 212 Dependence on molar volume and physical grain size are suppressed because these 213 diagnostics are not readily available. The prefactor is roughly equal to 214 $(Vₘ D⁰)/d²$ for $Vₘ$ the molar volume, $D⁰$ the reference diffusion coefficient and 215 $d²$ an average grain size assumed to be constant. 216 217 """ 218 disl_lowtemp_switch: float = 0.7 219 """Threshold homologous temperature below which to use the exponential flow law. 220 221 The default value suggested here comes from 222 [Demouchy et al. 2023](http://dx.doi.org/10.2138/gselements.19.3.151). 223 224 .. note:: Not relevant if the unified dislocation creep flow law is used instead. 225 226 """ 227 # TODO: Add more references from experimental studies/reviews. 228 disl_activation_energy: float = 460.0 229 """Activation energy for dislocation creep power law (kJ/mol). 230 231 - 443 kJ/mol used by [Gouriet et al. 2019](http://dx.doi.org/10.1016/j.epsl.2018.10.049), 232 but for an exponential law 233 - 540 kJ/mol used by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 234 235 """ 236 # TODO: Add more references, tweak default if necessary. 237 disl_activation_volume: float = 12.0 238 """Activation volume for dislocation creep power law (cm³/mol). 239 240 - 0, 6 and 12 cm³/mol used by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 241 242 """ 243 # TODO: Add more references, justify choice of lower value than Garel 2020. 244 diff_activation_energies: tuple = (430.0, 330) 245 """Activation energies for (matrix and boundary) diffusion creep power laws (kJ/mol). 246 247 - 530 kJ/mol reported for Si self-diffusion in olivine¹ 248 - 410 kJ/mol used by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 249 250 ¹[Dohmen et al. 2002](http://dx.doi.org/10.1029/2002GL015480) 251 252 """ 253 # TODO: Add more references, tweak default values if necessary. 254 diff_activation_volumes: tuple = (4.0, 4.0) 255 """Activation volumes for (matrix and boundary) diffusion creep power laws (cm³/mol). 256 257 - 4 kJ/mol used by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 258 259 """ 260 disl_coefficients: tuple = ( 261 4.4e8, # a₀ 262 -5.26e4, # b₀ 263 2.11e-2, # a₁ 264 1.74e-4, # b₁ 265 -41.8, # a₂ 266 4.21e-2, # b₂ 267 -1.14e-5, # c₂ 268 ) 269 """Coefficients for polynomials used in the alternative dislocation creep flow law. 270 271 The defaults are for the TANH variant of the unified creep law, proposed in 272 [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243). 273 By contrast, [Gouriet et al. 2019](http://dx.doi.org/10.1016/j.epsl.2018.10.049) 274 used the ERF variant with: [4.4e8, -2.2e4, 3e-2, 1.3e-4, -42, 4.2e-2, -1.1e-5]. 275 276 """ 277 278 def as_dict(self): 279 """Return mutable copy of default arguments as a dictionary.""" 280 return asdict(self) 281 282 283@nb.njit 284def get_crss(phase: MineralPhase, fabric: MineralFabric) -> np.ndarray: 285 """Get Critical Resolved Shear Stress for the mineral `phase` and `fabric`. 286 287 Returns an array of the normalised threshold stresses required to activate slip on 288 each slip system. Olivine slip systems are ordered according to the convention used 289 for `pydrex.minerals.OLIVINE_SLIP_SYSTEMS`. 290 291 """ 292 if phase == MineralPhase.olivine: 293 match fabric: 294 case MineralFabric.olivine_A: 295 return np.array([1, 2, 3, np.inf]) 296 case MineralFabric.olivine_B: 297 return np.array([3, 2, 1, np.inf]) 298 case MineralFabric.olivine_C: 299 return np.array([3, 2, np.inf, 1]) 300 case MineralFabric.olivine_D: 301 return np.array([1, 1, 3, np.inf]) 302 case MineralFabric.olivine_E: 303 return np.array([3, 1, 2, np.inf]) 304 case _: 305 raise ValueError(f"unsupported olivine fabric: {fabric}") 306 elif phase == MineralPhase.enstatite: 307 if fabric == MineralFabric.enstatite_AB: 308 return np.array([np.inf, np.inf, np.inf, 1]) 309 raise ValueError(f"unsupported enstatite fabric: {fabric}") 310 raise ValueError(f"phase must be a valid `MineralPhase`, not {phase}") 311 312 313# 12+ args is a lot, but this way we can use numba 314# (only primitives and numpy containers allowed). 315@nb.njit(fastmath=True) 316def derivatives( 317 regime: DeformationRegime, 318 phase: MineralPhase, 319 fabric: MineralFabric, 320 n_grains: int, 321 orientations: np.ndarray, 322 fractions: np.ndarray, 323 strain_rate: np.ndarray, 324 velocity_gradient: np.ndarray, 325 deformation_gradient_spin: np.ndarray, 326 stress_exponent: float, 327 deformation_exponent: float, 328 nucleation_efficiency: float, 329 gbm_mobility: float, 330 volume_fraction: float, 331): 332 """Get derivatives of orientation and volume distribution. 333 334 - `regime` — ordinal number of the local deformation mechanism 335 - `phase` — ordinal number of the mineral phase 336 - `fabric` — ordinal number of the fabric type 337 - `n_grains` — number of "grains" i.e. discrete volume segments 338 - `orientations` — `n_grains`x3x3 orientations (direction cosines) 339 - `fractions` — volume fractions of the grains relative to aggregate volume 340 - `strain_rate` — 3x3 dimensionless macroscopic strain-rate tensor 341 - `velocity_gradient` — 3x3 dimensionless macroscopic velocity gradient 342 - `deformation_gradient_spin` — 3x3 spin tensor defining the rate of rotation of the 343 finite strain ellipse 344 - `stress_exponent` — `p` in `dislocation_density ∝ shear_stress^p` 345 - `deformation_exponent` — `n` in `shear_stress ∝ |deformation_rate|^(1/n)` 346 - `nucleation_efficiency` — parameter controlling grain nucleation 347 - `gmb_mobility` — grain boundary mobility parameter 348 - `volume_fraction` — volume fraction of the mineral phase relative to other phases 349 (multiphase simulations) 350 351 Returns a tuple with the rotation rates and grain volume fraction changes. 352 353 """ 354 if regime == DeformationRegime.min_viscosity: 355 # Do absolutely nothing, all derivatives are zero. 356 return ( 357 np.repeat(np.eye(3), n_grains).reshape(3, 3, n_grains).transpose(), 358 np.zeros(n_grains), 359 ) 360 elif regime == DeformationRegime.matrix_diffusion: 361 # Passive rotation based on macroscopic vorticity for diffusion creep? 362 # vorticity = 0.5 * (velocity_gradient - velocity_gradient.transpose()) 363 # Passive rotation based on spin of F for diffusion creep. 364 vorticity = deformation_gradient_spin 365 # Or just don't change at all? 366 # vorticity = np.zeros((3, 3)) 367 # This 💃 is because numba doesn't let us use np.tile or even np.array([a] * n). 368 return ( 369 np.repeat(vorticity.transpose(), n_grains) 370 .reshape(3, 3, n_grains) 371 .transpose(), 372 np.zeros(n_grains), 373 ) 374 elif regime == DeformationRegime.boundary_diffusion: 375 raise ValueError("this deformation mechanism is not yet supported.") 376 elif regime == DeformationRegime.sliding_diffusion: 377 # Miyazaki et al. 2013 wrote controversial Nature article proposing that CPO can 378 # develop in the diffGBS regime. However, Karato 2024 gives some convincing 379 # arguments in the Journal of Geodynamics for why their results are likely 380 # inapplicable to Earth's upper mantle. 381 raise ValueError("this deformation mechanism is not yet supported.") 382 elif regime == DeformationRegime.matrix_dislocation: 383 # Based on subroutine DERIV in original Fortran. 384 strain_energies = np.empty(n_grains) 385 orientations_diff = np.empty((n_grains, 3, 3)) 386 for grain_index in range(n_grains): 387 orientation_change, strain_energy = _get_rotation_and_strain( 388 phase, 389 fabric, 390 orientations[grain_index], 391 strain_rate, 392 velocity_gradient, 393 stress_exponent, 394 deformation_exponent, 395 nucleation_efficiency, 396 ) 397 orientations_diff[grain_index] = orientation_change 398 strain_energies[grain_index] = strain_energy 399 # Volume average mean strain energy. 400 mean_energy = np.sum(fractions * strain_energies) 401 # Strain energy residual. 402 strain_residuals = mean_energy - strain_energies 403 fractions_diff = volume_fraction * gbm_mobility * fractions * strain_residuals 404 return orientations_diff, fractions_diff 405 elif regime == DeformationRegime.sliding_dislocation: 406 raise ValueError("this deformation mechanism is not yet supported.") 407 elif regime == DeformationRegime.frictional_yielding: 408 # For now, the same as matrix_dislocation, but we smooth the strain energy 409 # distribution and the orientation changes, since some energy is lost to 410 # micro-fracturing. Also increase the GBS threshold, dislocations will tend to 411 # pile up at grain boundaries. 412 # TODO: Maybe modify the stress/deformation exponents? 413 # TODO: Reduce nucleation efficiency? 414 strain_energies = np.empty(n_grains) 415 orientations_diff = np.empty((n_grains, 3, 3)) 416 for grain_index in range(n_grains): 417 orientation_change, strain_energy = _get_rotation_and_strain( 418 phase, 419 fabric, 420 orientations[grain_index], 421 strain_rate, 422 velocity_gradient, 423 stress_exponent, 424 deformation_exponent, 425 nucleation_efficiency, 426 ) 427 orientations_diff[grain_index] = 0.3 * orientation_change 428 strain_energies[grain_index] = strain_energy 429 # Volume average mean strain energy. 430 mean_energy = np.sum(fractions * strain_energies) 431 # Strain energy residuals, minus the energy lost to micro-fracturing. 432 strain_residuals = 0.3 * (mean_energy - strain_energies) 433 fractions_diff = volume_fraction * gbm_mobility * fractions * strain_residuals 434 return orientations_diff, fractions_diff 435 elif regime == DeformationRegime.max_viscosity: 436 # Do absolutely nothing, all derivatives are zero. 437 return ( 438 np.repeat(np.eye(3), n_grains).reshape(3, 3, n_grains).transpose(), 439 np.zeros(n_grains), 440 ) 441 else: 442 raise ValueError(f"regime must be a valid `DeformationRegime`, not {regime}") 443 444 445@nb.njit(fastmath=True) 446def _get_deformation_rate( 447 phase: MineralPhase, orientation: np.ndarray, slip_rates: np.ndarray 448): 449 """Calculate deformation rate tensor for olivine or enstatite. 450 451 Calculate the deformation rate with respect to the local coordinate frame, 452 defined by the principal strain axes (finite strain ellipsoid). 453 454 - `phase` — ordinal number of the mineral phase 455 - `orientation` — 3x3 orientation matrix (direction cosines) 456 - `slip_rates` — slip rates relative to slip rate on softest slip system 457 458 """ 459 # This is called the 'G'/'slip' tensor in the Fortran code, aka the Schmid tensor. 460 deformation_rate = np.empty((3, 3)) 461 for i in range(3): 462 for j in range(3): 463 deformation_rate[i, j] = 2 * ( 464 slip_rates[0] * orientation[0, i] * orientation[1, j] 465 + slip_rates[1] * orientation[0, i] * orientation[2, j] 466 + slip_rates[2] * orientation[2, i] * orientation[1, j] 467 + slip_rates[3] * orientation[2, i] * orientation[0, j] 468 ) 469 return deformation_rate 470 471 472@nb.njit(fastmath=True) 473def _get_slip_rate_softest(deformation_rate: np.ndarray, velocity_gradient: np.ndarray): 474 """Calculate dimensionless strain rate on the softest slip system. 475 476 - `deformation_rate` — 3x3 dimensionless deformation rate matrix 477 - `velocity_gradient` — 3x3 dimensionless velocity gradient matrix 478 479 """ 480 # See eq. 4 in Fraters 2021. 481 enumerator = 0 482 denominator = 0 483 484 for j in range(3): 485 # NOTE: Mistake in original DRex code (j + 2), see Fraters & Billen 2021 S1. 486 if _USE_ORIGINAL_DREX: 487 k = (j + 2) % 3 488 else: 489 k = (j + 1) % 3 490 enumerator -= (velocity_gradient[j, k] - velocity_gradient[k, j]) * ( 491 deformation_rate[j, k] - deformation_rate[k, j] 492 ) 493 # NOTE: Mistake in Kaminski 2001 eq. 7: kl+1 instead of kk+1 494 # See Fraters & Billen 2021 supplementary informaton S1. 495 denominator -= (deformation_rate[j, k] - deformation_rate[k, j]) ** 2 496 497 for L in range(3): 498 enumerator += 2 * deformation_rate[j, L] * velocity_gradient[j, L] 499 denominator += 2 * deformation_rate[j, L] ** 2 500 501 # Avoid zero divisions: 502 # Tiny denominator means that relevant deformation_rate entries are zero. 503 # No deformation rate means no slip rate. 504 if -1e-15 < denominator < 1e-15: 505 return 0.0 506 return enumerator / denominator 507 508 509@nb.njit(fastmath=True) 510def _get_slip_rates_olivine( 511 invariants: np.ndarray, 512 slip_indices: np.ndarray, 513 crss: np.ndarray, 514 deformation_exponent: float, 515): 516 """Calculate relative slip rates of the active slip systems for olivine. 517 518 - `invariants` — strain rate invariants for the four slip systems 519 - `slip_indices` — indices that sort the CRSS by increasing slip-rate activity 520 - `crss` — reference resolved shear stresses (CRSS), see `pydrex.fabric` 521 - `deformation_exponent` — `n` in `shear_stress ∝ |deformation_rate|^(1/n)` 522 523 """ 524 i_inac, i_min, i_int, i_max = slip_indices 525 # Ratio of slip rates on each slip system to slip rate on softest slip system. 526 # Softest slip system has max. slip rate (aka activity). 527 # See eq. 5, Kaminski 2001. 528 prefactor = crss[i_max] / invariants[i_max] 529 ratio_min = prefactor * invariants[i_min] / crss[i_min] 530 ratio_int = prefactor * invariants[i_int] / crss[i_int] 531 slip_rates = np.empty(4) 532 slip_rates[i_inac] = 0 # Hardest system is completely inactive in olivine. 533 slip_rates[i_min] = ratio_min * np.abs(ratio_min) ** (deformation_exponent - 1) 534 slip_rates[i_int] = ratio_int * np.abs(ratio_int) ** (deformation_exponent - 1) 535 slip_rates[i_max] = 1 536 return slip_rates 537 538 539@nb.njit(fastmath=True) 540def _get_slip_invariants(strain_rate: np.ndarray, orientation: np.ndarray): 541 r"""Calculate strain rate invariants for minerals with four slip systems. 542 543 Calculates $I = ∑_{ij} l_{i} n_{j} \dot{ε}_{ij}$ for each slip sytem of: 544 - (010)[100] 545 - (001)[100] 546 - (010)[001] 547 - (100)[001] 548 Only the last return value is relevant for enstatite. 549 These are not configurable for now. 550 551 - `strain_rate` — 3x3 dimensionless strain rate matrix 552 - `orientation` — 3x3 orientation matrix (direction cosines) 553 554 """ 555 invariants = np.zeros(4) 556 for i in range(3): 557 for j in range(3): 558 # (010)[100] 559 invariants[0] += strain_rate[i, j] * orientation[0, i] * orientation[1, j] 560 # (001)[100] 561 invariants[1] += strain_rate[i, j] * orientation[0, i] * orientation[2, j] 562 # (010)[001] 563 invariants[2] += strain_rate[i, j] * orientation[2, i] * orientation[1, j] 564 # (100)[001] 565 invariants[3] += strain_rate[i, j] * orientation[2, i] * orientation[0, j] 566 return invariants 567 568 569@nb.njit(fastmath=True) 570def _get_orientation_change( 571 orientation: np.ndarray, 572 velocity_gradient: np.ndarray, 573 deformation_rate: np.ndarray, 574 slip_rate_softest: float, 575): 576 """Calculate the rotation rate for a grain undergoing dislocation creep. 577 578 - `orientation` — 3x3 orientation matrix (direction cosines) 579 - `velocity_gradient` — 3x3 dimensionless velocity gradient matrix 580 - `deformation_rate` — 3x3 dimensionless deformation rate matrix 581 - `slip_rate_softest` — slip rate on the softest (most active) slip system 582 583 """ 584 orientation_change = np.zeros((3, 3)) 585 # Spin vector for the grain, see eq. 3 in Fraters 2021. 586 # Includes rigid body rotation as well as the plastic deformation contribution. 587 # This means that even with no active slip systems, the rotation will be nonzero 588 # if there is a rotational (i.e. antisymmetric) component of the velocity_gradient. 589 spin_vector = np.empty(3) 590 for j in range(3): 591 r = (j + 1) % 3 592 s = (j + 2) % 3 593 spin_vector[j] = ( 594 (velocity_gradient[s, r] - velocity_gradient[r, s]) 595 - (deformation_rate[s, r] - deformation_rate[r, s]) * slip_rate_softest 596 ) / 2 597 598 # Calculate rotation rate, see eq. 9 Kaminski & Ribe (2001). 599 # Equivalent to: 600 # spin_matrix = np.einsum("ikj,k->ij", PERMUTATION_SYMBOL, spin_vector) 601 # orientation_change = spin_matrix.transpose() @ orientation 602 for p in range(3): 603 for q in range(3): 604 for r in range(3): 605 for s in range(3): 606 orientation_change[p, q] += ( 607 PERMUTATION_SYMBOL[q, r, s] * orientation[p, s] * spin_vector[r] 608 ) 609 610 return orientation_change 611 612 613@nb.njit(fastmath=True) 614def _get_strain_energy( 615 crss: np.ndarray, 616 slip_rates: np.ndarray, 617 slip_indices: np.ndarray, 618 slip_rate_softest: float, 619 stress_exponent: float, 620 deformation_exponent: float, 621 nucleation_efficiency: float, 622): 623 """Calculate strain energy due to dislocations for an olivine grain. 624 625 - `crss` — reference resolved shear stresses (CRSS), see `pydrex.fabric` 626 - `slip_rates` — slip rates relative to slip rate on softest slip system 627 - `slip_indices` — indices that sort the CRSS by increasing slip-rate activity 628 - `slip_rate_softest` — slip rate on the softest (most active) slip system 629 - `stress_exponent` — `p` in `dislocation_density ∝ shear_stress^p` 630 - `deformation_exponent` — `n` in `shear_stress ∝ |deformation_rate|^(1/n)` 631 - `nucleation_efficiency` — parameter controlling grain nucleation 632 633 Note that "new" grains are assumed to rotate with their parent. 634 635 """ 636 strain_energy = 0.0 637 # Dimensionless dislocation density for each slip system. 638 # See eq. 16 Fraters 2021. 639 # NOTE: Mistake in eq. 11, Kaminski 2004: spurious division by strain rate scale. 640 # NOTE: Here we call 'p' the 'stress_exponent' and 'n' the 'deformation_exponent', 641 # but in the original they use the variable 'stress_exponent' for 'n' (3.5). 642 for i in range(3): 643 dislocation_density = (1 / crss[i]) ** ( 644 deformation_exponent - stress_exponent 645 ) * np.abs(slip_rates[i] * slip_rate_softest) ** ( 646 stress_exponent / deformation_exponent 647 ) 648 # Dimensionless strain energy for this grain, see eq. 14, Fraters 2021. 649 strain_energy += dislocation_density * np.exp( 650 -nucleation_efficiency * dislocation_density**2 651 ) 652 return strain_energy 653 654 655@nb.njit(fastmath=True) 656def _get_rotation_and_strain( 657 phase: MineralPhase, 658 fabric: MineralFabric, 659 orientation: np.ndarray, 660 strain_rate: np.ndarray, 661 velocity_gradient: np.ndarray, 662 stress_exponent: float, 663 deformation_exponent: float, 664 nucleation_efficiency: float, 665): 666 """Get the crystal axes rotation rate and strain energy of individual grain. 667 668 - `phase` — ordinal number of the mineral phase 669 - `fabric` — ordinal number of the fabric type 670 - `orientation` — 3x3 orientation matrix (direction cosines) 671 - `strain_rate` — 3x3 dimensionless strain rate matrix 672 - `velocity_gradient` — 3x3 dimensionless velocity gradient matrix 673 - `stress_exponent` — `p` in `dislocation_density ∝ shear_stress^p` 674 - `deformation_exponent` — `n` in `shear_stress ∝ |deformation_rate|^(1/n)` 675 - `nucleation_efficiency — parameter controlling grain nucleation 676 677 Note that "new" grains are assumed to rotate with their parent. 678 679 Returns a tuple with the rotation rate of the crystalline axes 680 with respect to the principal strain axes and strain energy of the grain. 681 682 """ 683 crss = get_crss(phase, fabric) 684 slip_invariants = _get_slip_invariants(strain_rate, orientation) 685 if phase == MineralPhase.olivine: 686 slip_indices = np.argsort(np.abs(slip_invariants / crss)) 687 slip_rates = _get_slip_rates_olivine( 688 slip_invariants, 689 slip_indices, 690 crss, 691 deformation_exponent, 692 ) 693 elif phase == MineralPhase.enstatite: 694 slip_indices = np.argsort(1 / crss) 695 slip_rates = np.zeros(4) 696 if _USE_ORIGINAL_DREX: 697 slip_rates[-1] = 1 # Original had an arbitrary slip always active (L1410). 698 else: 699 # Assumes exclusively (100)[001] slip for enstatite. 700 if np.abs(slip_invariants[-1]) > 1e-15: 701 slip_rates[-1] = 1 702 else: 703 assert False # Should never happen. 704 705 deformation_rate = _get_deformation_rate(phase, orientation, slip_rates) 706 slip_rate_softest = _get_slip_rate_softest(deformation_rate, velocity_gradient) 707 orientation_change = _get_orientation_change( 708 orientation, 709 velocity_gradient, 710 deformation_rate, 711 slip_rate_softest, 712 ) 713 strain_energy = _get_strain_energy( 714 crss, 715 slip_rates, 716 slip_indices, 717 slip_rate_softest, 718 stress_exponent, 719 deformation_exponent, 720 nucleation_efficiency, 721 ) 722 return orientation_change, strain_energy
Sometimes called the Levi-Civita symbol.
36@unique 37class MineralPhase(IntEnum): 38 """Supported mineral phases. 39 40 Forsterite and fayalite are grouped into “olivine”, because we treat them as 41 rheologically equivalent. 42 43 """ 44 45 olivine = 0 46 """(Mg,Fe)₂SiO₄""" 47 enstatite = 1 48 """MgSiO₃"""
Supported mineral phases.
Forsterite and fayalite are grouped into “olivine”, because we treat them as rheologically equivalent.
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
51@unique 52class MineralFabric(IntEnum): 53 """Supported mineral fabrics. 54 55 The following fabric types are supported: 56 - olivine A-E type fabrics according to e.g. 57 [Karato et al. (2008)](https://doi.org/10.1146%2Fannurev.earth.36.031207.124120). 58 - enstatite AB fabric, see 59 [Bernard et al. (2021)](https://doi.org/10.1016/j.tecto.2021.228954). 60 61 """ 62 63 olivine_A = 0 64 olivine_B = 1 65 olivine_C = 2 66 olivine_D = 3 67 olivine_E = 4 68 enstatite_AB = 5
Supported mineral fabrics.
The following fabric types are supported:
- olivine A-E type fabrics according to e.g. Karato et al. (2008).
- enstatite AB fabric, see Bernard et al. (2021).
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
71@unique 72class DeformationRegime(IntEnum): 73 r"""Ordinals to track distinct regimes of dominant deformation mechanisms. 74 75 The mechanism of deformation that dominates accommodation of plastic deformation 76 depends in general both on material properties such as grain size and mineral phase 77 content as well as on thermodynamic properties such as temperature, pressure and 78 water fugacity. 79 80 The activity of diffusive mechanisms depends more strongly on grain size, whereas 81 that of dislocation mechanisms depends more strongly on temperature. High 82 temperatures enable more frequent recovery of dislocation density equilibrium via 83 e.g. dislocation climb. Dislocation mechanisms are often accompanied by dynamic 84 recrystallisation, which acts as an additional recovery mechanism. 85 86 Diffusive mechanisms are expected, however, to become dominant at depth, as these 87 mechanisms are primarily facilitated by Si diffusion in olivine, which is 88 temperature-dependent. 89 90 Rheology in the intra-granular dislocation regime was classically described by 91 separate flow laws depending on temperature; a power-law at high temperature 92 [$\dot{ε} ∝ σⁿ$] and an exponential-law at low temperature [$\dot{ε} ∝ \exp(σ)$]. 93 More recent work has suggested unified dislocation creep flow laws, 94 e.g. [Gouriet et al. 2019](http://dx.doi.org/10.1016/j.epsl.2018.10.049), 95 [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) and 96 [Demouchy et al. 2023](http://dx.doi.org/10.2138/gselements.19.3.151). 97 98 .. note:: Although a draft texture evolution behaviour is implemented in the 99 `frictional_yielding` regime, it is experimental and not yet configurable via 100 the parameter interface. 101 102 """ 103 104 min_viscosity = 0 105 """Arbitrary lower-bound viscosity regime.""" 106 matrix_diffusion = 1 107 """Intra-granular Nabarro-Herring creep, i.e. grains diffuse through the matrix.""" 108 boundary_diffusion = 2 109 """Inter-granular Coble creep, i.e. grains diffuse along grain boundaries.""" 110 sliding_diffusion = 3 111 """Inter-granular diffusion-assisted grain-boundary sliding (diffGBS).""" 112 matrix_dislocation = 4 113 """Intra-granular dislocation creep (glide + climb) and dynamic recrystallisation.""" 114 sliding_dislocation = 5 115 """Inter-granular dislocation-assisted grain-boundary sliding (disGBS).""" 116 frictional_yielding = 6 117 """Frictional sliding along micro-fractures (Byerlee's law for yield strength).""" 118 max_viscosity = 7 119 """Arbitrary upper-bound viscosity regime."""
Ordinals to track distinct regimes of dominant deformation mechanisms.
The mechanism of deformation that dominates accommodation of plastic deformation depends in general both on material properties such as grain size and mineral phase content as well as on thermodynamic properties such as temperature, pressure and water fugacity.
The activity of diffusive mechanisms depends more strongly on grain size, whereas that of dislocation mechanisms depends more strongly on temperature. High temperatures enable more frequent recovery of dislocation density equilibrium via e.g. dislocation climb. Dislocation mechanisms are often accompanied by dynamic recrystallisation, which acts as an additional recovery mechanism.
Diffusive mechanisms are expected, however, to become dominant at depth, as these mechanisms are primarily facilitated by Si diffusion in olivine, which is temperature-dependent.
Rheology in the intra-granular dislocation regime was classically described by separate flow laws depending on temperature; a power-law at high temperature [$\dot{ε} ∝ σⁿ$] and an exponential-law at low temperature [$\dot{ε} ∝ \exp(σ)$]. More recent work has suggested unified dislocation creep flow laws, e.g. Gouriet et al. 2019, Garel et al. 2020 and Demouchy et al. 2023.
Although a draft texture evolution behaviour is implemented in the
frictional_yielding
regime, it is experimental and not yet configurable via
the parameter interface.
Intra-granular Nabarro-Herring creep, i.e. grains diffuse through the matrix.
Inter-granular Coble creep, i.e. grains diffuse along grain boundaries.
Inter-granular diffusion-assisted grain-boundary sliding (diffGBS).
Intra-granular dislocation creep (glide + climb) and dynamic recrystallisation.
Inter-granular dislocation-assisted grain-boundary sliding (disGBS).
Frictional sliding along micro-fractures (Byerlee's law for yield strength).
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
122@dataclass(frozen=True) 123class DefaultParams: 124 phase_assemblage: tuple = (MineralPhase.olivine,) 125 """Mineral phases present in the aggregate.""" 126 phase_fractions: tuple = (1.0,) 127 """Volume fractions of each mineral phase present in the aggregate.""" 128 stress_exponent: float = 1.5 129 """The value for $p$ in $ρ ∝ τᵖ$ where $ρ$ is the dislocation density and $τ$ the shear stress. 130 131 Default value taken from [Kaminski et al. 2004](https://doi.org/10.1111/j.1365-246X.2004.02308.x) 132 based on studies referenced therein. 133 134 """ 135 deformation_exponent: float = 3.5 136 """The value for $n$ in $τ ∝ |D|^{1/n}$ where $τ$ is the shear stress and D the deformation rate. 137 138 Default value taken from [Kaminski et al. 2004](https://doi.org/10.1111/j.1365-246X.2004.02308.x) 139 based on studies referenced therein. 140 141 """ 142 gbm_mobility: int = 125 143 """Dimensionless grain boundary mobility parameter (M*). 144 145 This controls the rate of all dynamic recrystallisation processes in 146 `DeformationRegime.matrix_dislocation`. 147 148 .. note:: This parameter is not easy to constrain. Reasonable values may lie in the 149 range [10, 150]. Comparing outputs for multiple M* values is recommended. 150 151 """ 152 gbs_threshold: float = 0.3 153 """Grain boundary sliding threshold. 154 155 In `DeformationRegime.matrix_dislocation` or `DeformationRegime.sliding_dislocation` 156 this controls the smallest size of a grain, relative to its original size, 157 at which it will still deform via dislocation creep. 158 Smaller grains will instead deform via grain boundary sliding, 159 therefore not contributing to any further texture evolution. 160 161 .. note:: Values for this parameter do NOT correspond to a physical grain size 162 threshold. Grains in PyDRex are a surrogate numerical discretisation. There are 163 likely to be feedbacks between this parameter and `number_of_grains`. Values of 164 ~0.3 have been used in numerous studies which employed ~3000 grains. 165 166 """ 167 nucleation_efficiency: float = 5.0 168 """Dimensionless nucleation efficiency (λ*). 169 170 This controls the nucleation of subgrains in `DeformationRegime.matrix_dislocation`. 171 172 The default value comes from [Kaminski & Ribe 2001](https://doi.org/10.1016%2Fs0012-821x%2801%2900356-9). 173 174 """ 175 number_of_grains: int = 3500 176 """Number of surrogate grains for numerical discretisation of the aggregate. 177 178 This is a numerical discretisation, so generally more grains is better. However, 179 there is a sharp tradeoff in performance, especially for M-index calculation. 180 181 """ 182 initial_olivine_fabric: MineralFabric = MineralFabric.olivine_A 183 """Olivine fabric (CRSS distribution) at the beginning of the simulation. 184 185 In general, the fabric should be allowed to change during the simulation, 186 but that is not yet implemented. 187 188 """ 189 disl_Peierls_stress: float = 2 190 """Stress barrier in GPa for activation of dislocation motion at low temperatures. 191 192 - 2GPa suggested by [Demouchy et al. 2023](http://dx.doi.org/10.2138/gselements.19.3.151) 193 194 .. note:: Not relevant if the unified dislocation creep flow law is used. 195 196 """ 197 # NOTE: For now, we just roll this into the prefactors. 198 # water_fugacity: float = 1.5 199 # """Approximate constant water fugacity of the aggregate (GPa).""" 200 # TODO: Check units of Garel 2020 pre-exp value below. 201 # TODO: Find and add references for the other value. 202 disl_prefactors: tuple = (1e-16, 1e-17) 203 """Prefactors for dislocation creep exponential & power laws (s⁻¹). 204 205 - B = 1.7e-16 s⁻¹ for the exponential law suggested by [Demouchy et al. 2023](http://dx.doi.org/10.2138/gselements.19.3.151) 206 - pre-exponential factor of 4.4e-17 Pa⁻ⁿs⁻¹ used for the exponential law by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 207 208 """ 209 # TODO: Add references, tweak default values if necessary. 210 diff_prefactors: tuple = (1e-10, 1e-10) 211 r"""Prefactors for (matrix and boundary) diffusion creep power laws (s⁻¹). 212 213 Dependence on molar volume and physical grain size are suppressed because these 214 diagnostics are not readily available. The prefactor is roughly equal to 215 $(Vₘ D⁰)/d²$ for $Vₘ$ the molar volume, $D⁰$ the reference diffusion coefficient and 216 $d²$ an average grain size assumed to be constant. 217 218 """ 219 disl_lowtemp_switch: float = 0.7 220 """Threshold homologous temperature below which to use the exponential flow law. 221 222 The default value suggested here comes from 223 [Demouchy et al. 2023](http://dx.doi.org/10.2138/gselements.19.3.151). 224 225 .. note:: Not relevant if the unified dislocation creep flow law is used instead. 226 227 """ 228 # TODO: Add more references from experimental studies/reviews. 229 disl_activation_energy: float = 460.0 230 """Activation energy for dislocation creep power law (kJ/mol). 231 232 - 443 kJ/mol used by [Gouriet et al. 2019](http://dx.doi.org/10.1016/j.epsl.2018.10.049), 233 but for an exponential law 234 - 540 kJ/mol used by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 235 236 """ 237 # TODO: Add more references, tweak default if necessary. 238 disl_activation_volume: float = 12.0 239 """Activation volume for dislocation creep power law (cm³/mol). 240 241 - 0, 6 and 12 cm³/mol used by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 242 243 """ 244 # TODO: Add more references, justify choice of lower value than Garel 2020. 245 diff_activation_energies: tuple = (430.0, 330) 246 """Activation energies for (matrix and boundary) diffusion creep power laws (kJ/mol). 247 248 - 530 kJ/mol reported for Si self-diffusion in olivine¹ 249 - 410 kJ/mol used by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 250 251 ¹[Dohmen et al. 2002](http://dx.doi.org/10.1029/2002GL015480) 252 253 """ 254 # TODO: Add more references, tweak default values if necessary. 255 diff_activation_volumes: tuple = (4.0, 4.0) 256 """Activation volumes for (matrix and boundary) diffusion creep power laws (cm³/mol). 257 258 - 4 kJ/mol used by [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243) 259 260 """ 261 disl_coefficients: tuple = ( 262 4.4e8, # a₀ 263 -5.26e4, # b₀ 264 2.11e-2, # a₁ 265 1.74e-4, # b₁ 266 -41.8, # a₂ 267 4.21e-2, # b₂ 268 -1.14e-5, # c₂ 269 ) 270 """Coefficients for polynomials used in the alternative dislocation creep flow law. 271 272 The defaults are for the TANH variant of the unified creep law, proposed in 273 [Garel et al. 2020](http://dx.doi.org/10.1016/j.epsl.2020.116243). 274 By contrast, [Gouriet et al. 2019](http://dx.doi.org/10.1016/j.epsl.2018.10.049) 275 used the ERF variant with: [4.4e8, -2.2e4, 3e-2, 1.3e-4, -42, 4.2e-2, -1.1e-5]. 276 277 """ 278 279 def as_dict(self): 280 """Return mutable copy of default arguments as a dictionary.""" 281 return asdict(self)
The value for $p$ in $ρ ∝ τᵖ$ where $ρ$ is the dislocation density and $τ$ the shear stress.
Default value taken from Kaminski et al. 2004 based on studies referenced therein.
The value for $n$ in $τ ∝ |D|^{1/n}$ where $τ$ is the shear stress and D the deformation rate.
Default value taken from Kaminski et al. 2004 based on studies referenced therein.
Dimensionless grain boundary mobility parameter (M*).
This controls the rate of all dynamic recrystallisation processes in
DeformationRegime.matrix_dislocation
.
This parameter is not easy to constrain. Reasonable values may lie in the
range [10, 150]. Comparing outputs for multiple M* values is recommended.
Grain boundary sliding threshold.
In DeformationRegime.matrix_dislocation
or DeformationRegime.sliding_dislocation
this controls the smallest size of a grain, relative to its original size,
at which it will still deform via dislocation creep.
Smaller grains will instead deform via grain boundary sliding,
therefore not contributing to any further texture evolution.
Values for this parameter do NOT correspond to a physical grain size
threshold. Grains in PyDRex are a surrogate numerical discretisation. There are
likely to be feedbacks between this parameter and number_of_grains
. Values of
~0.3 have been used in numerous studies which employed ~3000 grains.
Dimensionless nucleation efficiency (λ*).
This controls the nucleation of subgrains in DeformationRegime.matrix_dislocation
.
The default value comes from Kaminski & Ribe 2001.
Number of surrogate grains for numerical discretisation of the aggregate.
This is a numerical discretisation, so generally more grains is better. However, there is a sharp tradeoff in performance, especially for M-index calculation.
Olivine fabric (CRSS distribution) at the beginning of the simulation.
In general, the fabric should be allowed to change during the simulation, but that is not yet implemented.
Stress barrier in GPa for activation of dislocation motion at low temperatures.
- 2GPa suggested by Demouchy et al. 2023
Not relevant if the unified dislocation creep flow law is used.
Prefactors for dislocation creep exponential & power laws (s⁻¹).
- B = 1.7e-16 s⁻¹ for the exponential law suggested by Demouchy et al. 2023
- pre-exponential factor of 4.4e-17 Pa⁻ⁿs⁻¹ used for the exponential law by Garel et al. 2020
Prefactors for (matrix and boundary) diffusion creep power laws (s⁻¹).
Dependence on molar volume and physical grain size are suppressed because these diagnostics are not readily available. The prefactor is roughly equal to $(Vₘ D⁰)/d²$ for $Vₘ$ the molar volume, $D⁰$ the reference diffusion coefficient and $d²$ an average grain size assumed to be constant.
Threshold homologous temperature below which to use the exponential flow law.
The default value suggested here comes from Demouchy et al. 2023.
Not relevant if the unified dislocation creep flow law is used instead.
Activation energy for dislocation creep power law (kJ/mol).
- 443 kJ/mol used by Gouriet et al. 2019, but for an exponential law
- 540 kJ/mol used by Garel et al. 2020
Activation volume for dislocation creep power law (cm³/mol).
- 0, 6 and 12 cm³/mol used by Garel et al. 2020
Activation energies for (matrix and boundary) diffusion creep power laws (kJ/mol).
- 530 kJ/mol reported for Si self-diffusion in olivine¹
- 410 kJ/mol used by Garel et al. 2020
Activation volumes for (matrix and boundary) diffusion creep power laws (cm³/mol).
- 4 kJ/mol used by Garel et al. 2020
Coefficients for polynomials used in the alternative dislocation creep flow law.
The defaults are for the TANH variant of the unified creep law, proposed in Garel et al. 2020. By contrast, Gouriet et al. 2019 used the ERF variant with: [4.4e8, -2.2e4, 3e-2, 1.3e-4, -42, 4.2e-2, -1.1e-5].
284@nb.njit 285def get_crss(phase: MineralPhase, fabric: MineralFabric) -> np.ndarray: 286 """Get Critical Resolved Shear Stress for the mineral `phase` and `fabric`. 287 288 Returns an array of the normalised threshold stresses required to activate slip on 289 each slip system. Olivine slip systems are ordered according to the convention used 290 for `pydrex.minerals.OLIVINE_SLIP_SYSTEMS`. 291 292 """ 293 if phase == MineralPhase.olivine: 294 match fabric: 295 case MineralFabric.olivine_A: 296 return np.array([1, 2, 3, np.inf]) 297 case MineralFabric.olivine_B: 298 return np.array([3, 2, 1, np.inf]) 299 case MineralFabric.olivine_C: 300 return np.array([3, 2, np.inf, 1]) 301 case MineralFabric.olivine_D: 302 return np.array([1, 1, 3, np.inf]) 303 case MineralFabric.olivine_E: 304 return np.array([3, 1, 2, np.inf]) 305 case _: 306 raise ValueError(f"unsupported olivine fabric: {fabric}") 307 elif phase == MineralPhase.enstatite: 308 if fabric == MineralFabric.enstatite_AB: 309 return np.array([np.inf, np.inf, np.inf, 1]) 310 raise ValueError(f"unsupported enstatite fabric: {fabric}") 311 raise ValueError(f"phase must be a valid `MineralPhase`, not {phase}")
Get Critical Resolved Shear Stress for the mineral phase
and fabric
.
Returns an array of the normalised threshold stresses required to activate slip on
each slip system. Olivine slip systems are ordered according to the convention used
for pydrex.minerals.OLIVINE_SLIP_SYSTEMS
.
316@nb.njit(fastmath=True) 317def derivatives( 318 regime: DeformationRegime, 319 phase: MineralPhase, 320 fabric: MineralFabric, 321 n_grains: int, 322 orientations: np.ndarray, 323 fractions: np.ndarray, 324 strain_rate: np.ndarray, 325 velocity_gradient: np.ndarray, 326 deformation_gradient_spin: np.ndarray, 327 stress_exponent: float, 328 deformation_exponent: float, 329 nucleation_efficiency: float, 330 gbm_mobility: float, 331 volume_fraction: float, 332): 333 """Get derivatives of orientation and volume distribution. 334 335 - `regime` — ordinal number of the local deformation mechanism 336 - `phase` — ordinal number of the mineral phase 337 - `fabric` — ordinal number of the fabric type 338 - `n_grains` — number of "grains" i.e. discrete volume segments 339 - `orientations` — `n_grains`x3x3 orientations (direction cosines) 340 - `fractions` — volume fractions of the grains relative to aggregate volume 341 - `strain_rate` — 3x3 dimensionless macroscopic strain-rate tensor 342 - `velocity_gradient` — 3x3 dimensionless macroscopic velocity gradient 343 - `deformation_gradient_spin` — 3x3 spin tensor defining the rate of rotation of the 344 finite strain ellipse 345 - `stress_exponent` — `p` in `dislocation_density ∝ shear_stress^p` 346 - `deformation_exponent` — `n` in `shear_stress ∝ |deformation_rate|^(1/n)` 347 - `nucleation_efficiency` — parameter controlling grain nucleation 348 - `gmb_mobility` — grain boundary mobility parameter 349 - `volume_fraction` — volume fraction of the mineral phase relative to other phases 350 (multiphase simulations) 351 352 Returns a tuple with the rotation rates and grain volume fraction changes. 353 354 """ 355 if regime == DeformationRegime.min_viscosity: 356 # Do absolutely nothing, all derivatives are zero. 357 return ( 358 np.repeat(np.eye(3), n_grains).reshape(3, 3, n_grains).transpose(), 359 np.zeros(n_grains), 360 ) 361 elif regime == DeformationRegime.matrix_diffusion: 362 # Passive rotation based on macroscopic vorticity for diffusion creep? 363 # vorticity = 0.5 * (velocity_gradient - velocity_gradient.transpose()) 364 # Passive rotation based on spin of F for diffusion creep. 365 vorticity = deformation_gradient_spin 366 # Or just don't change at all? 367 # vorticity = np.zeros((3, 3)) 368 # This 💃 is because numba doesn't let us use np.tile or even np.array([a] * n). 369 return ( 370 np.repeat(vorticity.transpose(), n_grains) 371 .reshape(3, 3, n_grains) 372 .transpose(), 373 np.zeros(n_grains), 374 ) 375 elif regime == DeformationRegime.boundary_diffusion: 376 raise ValueError("this deformation mechanism is not yet supported.") 377 elif regime == DeformationRegime.sliding_diffusion: 378 # Miyazaki et al. 2013 wrote controversial Nature article proposing that CPO can 379 # develop in the diffGBS regime. However, Karato 2024 gives some convincing 380 # arguments in the Journal of Geodynamics for why their results are likely 381 # inapplicable to Earth's upper mantle. 382 raise ValueError("this deformation mechanism is not yet supported.") 383 elif regime == DeformationRegime.matrix_dislocation: 384 # Based on subroutine DERIV in original Fortran. 385 strain_energies = np.empty(n_grains) 386 orientations_diff = np.empty((n_grains, 3, 3)) 387 for grain_index in range(n_grains): 388 orientation_change, strain_energy = _get_rotation_and_strain( 389 phase, 390 fabric, 391 orientations[grain_index], 392 strain_rate, 393 velocity_gradient, 394 stress_exponent, 395 deformation_exponent, 396 nucleation_efficiency, 397 ) 398 orientations_diff[grain_index] = orientation_change 399 strain_energies[grain_index] = strain_energy 400 # Volume average mean strain energy. 401 mean_energy = np.sum(fractions * strain_energies) 402 # Strain energy residual. 403 strain_residuals = mean_energy - strain_energies 404 fractions_diff = volume_fraction * gbm_mobility * fractions * strain_residuals 405 return orientations_diff, fractions_diff 406 elif regime == DeformationRegime.sliding_dislocation: 407 raise ValueError("this deformation mechanism is not yet supported.") 408 elif regime == DeformationRegime.frictional_yielding: 409 # For now, the same as matrix_dislocation, but we smooth the strain energy 410 # distribution and the orientation changes, since some energy is lost to 411 # micro-fracturing. Also increase the GBS threshold, dislocations will tend to 412 # pile up at grain boundaries. 413 # TODO: Maybe modify the stress/deformation exponents? 414 # TODO: Reduce nucleation efficiency? 415 strain_energies = np.empty(n_grains) 416 orientations_diff = np.empty((n_grains, 3, 3)) 417 for grain_index in range(n_grains): 418 orientation_change, strain_energy = _get_rotation_and_strain( 419 phase, 420 fabric, 421 orientations[grain_index], 422 strain_rate, 423 velocity_gradient, 424 stress_exponent, 425 deformation_exponent, 426 nucleation_efficiency, 427 ) 428 orientations_diff[grain_index] = 0.3 * orientation_change 429 strain_energies[grain_index] = strain_energy 430 # Volume average mean strain energy. 431 mean_energy = np.sum(fractions * strain_energies) 432 # Strain energy residuals, minus the energy lost to micro-fracturing. 433 strain_residuals = 0.3 * (mean_energy - strain_energies) 434 fractions_diff = volume_fraction * gbm_mobility * fractions * strain_residuals 435 return orientations_diff, fractions_diff 436 elif regime == DeformationRegime.max_viscosity: 437 # Do absolutely nothing, all derivatives are zero. 438 return ( 439 np.repeat(np.eye(3), n_grains).reshape(3, 3, n_grains).transpose(), 440 np.zeros(n_grains), 441 ) 442 else: 443 raise ValueError(f"regime must be a valid `DeformationRegime`, not {regime}")
Get derivatives of orientation and volume distribution.
regime
— ordinal number of the local deformation mechanismphase
— ordinal number of the mineral phasefabric
— ordinal number of the fabric typen_grains
— number of "grains" i.e. discrete volume segmentsorientations
—n_grains
x3x3 orientations (direction cosines)fractions
— volume fractions of the grains relative to aggregate volumestrain_rate
— 3x3 dimensionless macroscopic strain-rate tensorvelocity_gradient
— 3x3 dimensionless macroscopic velocity gradientdeformation_gradient_spin
— 3x3 spin tensor defining the rate of rotation of the finite strain ellipsestress_exponent
—p
indislocation_density ∝ shear_stress^p
deformation_exponent
—n
inshear_stress ∝ |deformation_rate|^(1/n)
nucleation_efficiency
— parameter controlling grain nucleationgmb_mobility
— grain boundary mobility parametervolume_fraction
— volume fraction of the mineral phase relative to other phases (multiphase simulations)
Returns a tuple with the rotation rates and grain volume fraction changes.