midas-ddd 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- midas_ddd/__init__.py +123 -0
- midas_ddd/_g5.py +1 -0
- midas_ddd/elasticity.py +294 -0
- midas_ddd/exadis.py +313 -0
- midas_ddd/fourier.py +674 -0
- midas_ddd/generate.py +308 -0
- midas_ddd/network.py +508 -0
- midas_ddd/realspace.py +316 -0
- midas_ddd/validate.py +377 -0
- midas_ddd-0.1.0.dist-info/METADATA +184 -0
- midas_ddd-0.1.0.dist-info/RECORD +14 -0
- midas_ddd-0.1.0.dist-info/WHEEL +5 -0
- midas_ddd-0.1.0.dist-info/licenses/LICENSE +31 -0
- midas_ddd-0.1.0.dist-info/top_level.txt +1 -0
midas_ddd/__init__.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""midas-ddd — dislocation networks for MIDAS.
|
|
2
|
+
|
|
3
|
+
Ingests a discrete-dislocation-dynamics network (ExaDiS / ParaDiS) and turns it
|
|
4
|
+
into the displacement fields that three MIDAS forward models consume:
|
|
5
|
+
|
|
6
|
+
real space beta(r) -> midas_dfxm (DFXM contrast)
|
|
7
|
+
Fourier u~(q) -> midas_saxs (small-angle, evaluated at q)
|
|
8
|
+
Fourier u~(q) -> midas_defect (near-Bragg diffuse, at G + q)
|
|
9
|
+
|
|
10
|
+
One kernel, three evaluation points. Everything is torch-differentiable.
|
|
11
|
+
|
|
12
|
+
from midas_ddd import cubic_stiffness, fcc_slip_systems
|
|
13
|
+
|
|
14
|
+
Why this package exists
|
|
15
|
+
-----------------------
|
|
16
|
+
The anisotropic-elasticity (Stroh) primitives here were previously private to
|
|
17
|
+
``midas_defect.contrast_factor``, which already carried a "single source of
|
|
18
|
+
truth, do NOT re-port" contract because ``midas_dfxm`` imported them across a
|
|
19
|
+
package boundary. Two more consumers made that arrangement untenable: a SAXS
|
|
20
|
+
package should not have to depend on an FF-HEDM metrology package to build a
|
|
21
|
+
stiffness matrix. ``midas_defect.contrast_factor`` now re-exports every name,
|
|
22
|
+
so no historical import path broke.
|
|
23
|
+
|
|
24
|
+
ExaDiS itself requires a Kokkos/CMake build and is never a pip dependency. The
|
|
25
|
+
ParaDiS ``.data`` file bridge works without it; the in-process ``pyexadis`` path
|
|
26
|
+
is optional and imported lazily.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
30
|
+
|
|
31
|
+
from .elasticity import (
|
|
32
|
+
bcc_slip_systems,
|
|
33
|
+
cubic_stiffness,
|
|
34
|
+
fcc_slip_systems,
|
|
35
|
+
hexagonal_stiffness,
|
|
36
|
+
)
|
|
37
|
+
from .generate import (
|
|
38
|
+
combine,
|
|
39
|
+
polygon_area_exact_um2,
|
|
40
|
+
prismatic_loop,
|
|
41
|
+
straight_line,
|
|
42
|
+
)
|
|
43
|
+
from .fourier import (
|
|
44
|
+
FourierResult,
|
|
45
|
+
acoustic_tensor,
|
|
46
|
+
isotropic_stiffness,
|
|
47
|
+
loop_small_q_limit,
|
|
48
|
+
prismatic_loop_small_q_limit,
|
|
49
|
+
prismatic_loop_small_q_limit_total,
|
|
50
|
+
q_dot_u_tilde,
|
|
51
|
+
q_dot_u_tilde_per_loop,
|
|
52
|
+
small_angle_amplitude,
|
|
53
|
+
surface_form_factor,
|
|
54
|
+
u_tilde,
|
|
55
|
+
)
|
|
56
|
+
from .network import DislocationNetwork, read_paradis, write_paradis
|
|
57
|
+
from .realspace import (
|
|
58
|
+
SegmentDislocation,
|
|
59
|
+
green_gradient_isotropic,
|
|
60
|
+
lame_from_voigt,
|
|
61
|
+
network_distortion,
|
|
62
|
+
segment_dislocations,
|
|
63
|
+
)
|
|
64
|
+
from .validate import (
|
|
65
|
+
BurgersConservationResult,
|
|
66
|
+
Loop,
|
|
67
|
+
ResolutionReport,
|
|
68
|
+
check_burgers_conservation,
|
|
69
|
+
find_loops,
|
|
70
|
+
loop_area_vectors_um2,
|
|
71
|
+
relaxation_volumes_um3,
|
|
72
|
+
resolution_report,
|
|
73
|
+
validate_network,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
# elasticity
|
|
78
|
+
"bcc_slip_systems",
|
|
79
|
+
"cubic_stiffness",
|
|
80
|
+
"fcc_slip_systems",
|
|
81
|
+
"hexagonal_stiffness",
|
|
82
|
+
# network
|
|
83
|
+
"DislocationNetwork",
|
|
84
|
+
"read_paradis",
|
|
85
|
+
"write_paradis",
|
|
86
|
+
# generators (ExaDiS-free equivalents; see midas_ddd.exadis for the real ones)
|
|
87
|
+
"combine",
|
|
88
|
+
"polygon_area_exact_um2",
|
|
89
|
+
"prismatic_loop",
|
|
90
|
+
"straight_line",
|
|
91
|
+
# Fourier kernel
|
|
92
|
+
"FourierResult",
|
|
93
|
+
"acoustic_tensor",
|
|
94
|
+
"isotropic_stiffness",
|
|
95
|
+
"loop_small_q_limit",
|
|
96
|
+
"prismatic_loop_small_q_limit",
|
|
97
|
+
"prismatic_loop_small_q_limit_total",
|
|
98
|
+
"q_dot_u_tilde",
|
|
99
|
+
"q_dot_u_tilde_per_loop",
|
|
100
|
+
"small_angle_amplitude",
|
|
101
|
+
"surface_form_factor",
|
|
102
|
+
"u_tilde",
|
|
103
|
+
# real-space distortion (ELASTIC part; see the module docstring)
|
|
104
|
+
"SegmentDislocation",
|
|
105
|
+
"green_gradient_isotropic",
|
|
106
|
+
"lame_from_voigt",
|
|
107
|
+
"network_distortion",
|
|
108
|
+
"segment_dislocations",
|
|
109
|
+
# validation
|
|
110
|
+
"BurgersConservationResult",
|
|
111
|
+
"Loop",
|
|
112
|
+
"ResolutionReport",
|
|
113
|
+
"check_burgers_conservation",
|
|
114
|
+
"find_loops",
|
|
115
|
+
"loop_area_vectors_um2",
|
|
116
|
+
"relaxation_volumes_um3",
|
|
117
|
+
"resolution_report",
|
|
118
|
+
"validate_network",
|
|
119
|
+
"__version__",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
# `midas_ddd.exadis` is NOT imported here: it pulls pyexadis, which needs a
|
|
123
|
+
# Kokkos build. Import it explicitly when you have one.
|
midas_ddd/_g5.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DATASET = "smith_cu_sep24"
|
midas_ddd/elasticity.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Anisotropic-elasticity primitives for dislocations (Stroh sextic solution).
|
|
2
|
+
|
|
3
|
+
**Single source of truth.** These primitives used to live in
|
|
4
|
+
``midas_defect.contrast_factor`` / ``midas_defect.contrast_factor_hex``, which
|
|
5
|
+
already carried a "do NOT re-port" contract because ``midas_dfxm.dislocation``
|
|
6
|
+
imports them. They moved down here when a third and fourth consumer appeared
|
|
7
|
+
(``midas_saxs`` and the near-Bragg diffuse forward), so that dislocation
|
|
8
|
+
elasticity has exactly one home and nothing has to depend on an FF-HEDM
|
|
9
|
+
metrology package to get a stiffness matrix. ``midas_defect.contrast_factor``
|
|
10
|
+
re-exports every name below, so every historical import path still works.
|
|
11
|
+
|
|
12
|
+
What is here
|
|
13
|
+
------------
|
|
14
|
+
* Stiffness construction: :func:`cubic_stiffness`, :func:`hexagonal_stiffness`.
|
|
15
|
+
* Voigt ↔ 4-tensor and frame rotation: :func:`_voigt_to_tensor`,
|
|
16
|
+
:func:`_rotate_tensor`, :func:`_slip_frame`.
|
|
17
|
+
* The Stroh sextic eigensolution :func:`_stroh_eig` — 3 roots ``p`` with
|
|
18
|
+
``Im(p) > 0`` plus the amplitude/stress matrices ``A``, ``B``.
|
|
19
|
+
* Crystal → Cartesian geometry for non-cubic cells: :func:`_crystal_A_matrix`,
|
|
20
|
+
:func:`_to_cartesian`.
|
|
21
|
+
* Slip-system tables: :func:`fcc_slip_systems`, :func:`bcc_slip_systems`.
|
|
22
|
+
|
|
23
|
+
The physics reference is unchanged: Teodosiu 1982 / Ting, *Anisotropic
|
|
24
|
+
Elasticity*, as implemented by ANIZC (Borbély, Dragomir-Cernatescu, Ribárik &
|
|
25
|
+
Ungár, *J. Appl. Cryst.* **36** (2003) 160-162).
|
|
26
|
+
|
|
27
|
+
Conventions
|
|
28
|
+
-----------
|
|
29
|
+
* Elastic constants: full 6×6 Voigt stiffness matrix, any consistent unit.
|
|
30
|
+
* Slip coordinate system: ``e3`` = dislocation line, ``e2`` = slip-plane normal,
|
|
31
|
+
``e1 = e2 × e3``. Character ψ = ∠(line, Burgers): 0° = screw, 90° = edge.
|
|
32
|
+
* Burgers vector, slip-plane normal, line and diffraction ``g`` in crystal
|
|
33
|
+
Cartesian axes; for a cubic cell these coincide with the integer ``(hkl)`` /
|
|
34
|
+
``[uvw]`` axes.
|
|
35
|
+
|
|
36
|
+
Differentiability / device
|
|
37
|
+
--------------------------
|
|
38
|
+
Pure torch: the Voigt→4-tensor expansion, the slip-frame rotation and the 6×6
|
|
39
|
+
Stroh eigenproblem (:func:`torch.linalg.eig`) are all differentiable w.r.t. the
|
|
40
|
+
elastic constants. ``complex128`` eig is unsupported on the MPS backend, so this
|
|
41
|
+
path runs on CPU / CUDA.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
from typing import List, Sequence, Tuple
|
|
47
|
+
|
|
48
|
+
import numpy as np
|
|
49
|
+
import torch
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"cubic_stiffness",
|
|
53
|
+
"hexagonal_stiffness",
|
|
54
|
+
"fcc_slip_systems",
|
|
55
|
+
"bcc_slip_systems",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Voigt index map and stiffness construction
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
#: Voigt index for each (i, j) Cartesian pair: 11→0 22→1 33→2 23→3 13→4 12→5.
|
|
64
|
+
_VOIGT_IDX = torch.tensor([[0, 5, 4], [5, 1, 3], [4, 3, 2]])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def cubic_stiffness(
|
|
68
|
+
c11: float, c12: float, c44: float, *,
|
|
69
|
+
dtype: torch.dtype = torch.float64,
|
|
70
|
+
device=None,
|
|
71
|
+
) -> torch.Tensor:
|
|
72
|
+
"""Return the 6×6 Voigt stiffness matrix of a cubic crystal.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
c11, c12, c44
|
|
77
|
+
Cubic elastic constants (GPa, or any consistent unit). ``C`` is
|
|
78
|
+
dimensionless so only their ratios matter.
|
|
79
|
+
"""
|
|
80
|
+
device = torch.device("cpu") if device is None else device
|
|
81
|
+
c11 = torch.as_tensor(c11, dtype=dtype, device=device)
|
|
82
|
+
c12 = torch.as_tensor(c12, dtype=dtype, device=device)
|
|
83
|
+
c44 = torch.as_tensor(c44, dtype=dtype, device=device)
|
|
84
|
+
z = torch.zeros((), dtype=dtype, device=device)
|
|
85
|
+
rows = [
|
|
86
|
+
torch.stack([c11, c12, c12, z, z, z]),
|
|
87
|
+
torch.stack([c12, c11, c12, z, z, z]),
|
|
88
|
+
torch.stack([c12, c12, c11, z, z, z]),
|
|
89
|
+
torch.stack([z, z, z, c44, z, z]),
|
|
90
|
+
torch.stack([z, z, z, z, c44, z]),
|
|
91
|
+
torch.stack([z, z, z, z, z, c44]),
|
|
92
|
+
]
|
|
93
|
+
return torch.stack(rows, dim=0)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _voigt_to_tensor(C6: torch.Tensor) -> torch.Tensor:
|
|
97
|
+
"""Expand a 6×6 Voigt stiffness matrix to the full ``(3,3,3,3)`` tensor."""
|
|
98
|
+
idx = _VOIGT_IDX.to(C6.device)
|
|
99
|
+
return C6[idx[:, :, None, None], idx[None, None, :, :]]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _rotate_tensor(C4: torch.Tensor, M: torch.Tensor) -> torch.Tensor:
|
|
103
|
+
"""Rotate a 4th-rank tensor: ``C'_{ijkl} = M_ia M_jb M_kc M_ld C_{abcd}``."""
|
|
104
|
+
return torch.einsum("ia,jb,kc,ld,abcd->ijkl", M, M, M, M, C4)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _slip_frame(line: torch.Tensor, normal: torch.Tensor) -> torch.Tensor:
|
|
108
|
+
"""Rotation matrix (rows = e1, e2, e3) from crystal to slip coordinates.
|
|
109
|
+
|
|
110
|
+
``e3`` = unit line, ``e2`` = unit slip-plane normal, ``e1 = e2 × e3``. The
|
|
111
|
+
returned ``M`` maps a crystal-frame vector ``v`` to the slip frame via
|
|
112
|
+
``M @ v``. ``normal`` is expected ⊥ ``line`` (a physical slip system); a
|
|
113
|
+
Gram-Schmidt step removes any residual non-orthogonality.
|
|
114
|
+
"""
|
|
115
|
+
e3 = line / torch.linalg.norm(line)
|
|
116
|
+
e2 = normal - (normal @ e3) * e3
|
|
117
|
+
e2 = e2 / torch.linalg.norm(e2)
|
|
118
|
+
e1 = torch.linalg.cross(e2, e3)
|
|
119
|
+
e1 = e1 / torch.linalg.norm(e1)
|
|
120
|
+
return torch.stack([e1, e2, e3], dim=0)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _stroh_eig(
|
|
124
|
+
C4: torch.Tensor,
|
|
125
|
+
*,
|
|
126
|
+
degeneracy_tol: float = 1e-4,
|
|
127
|
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
128
|
+
"""Stroh eigensolution: 3 roots ``p`` (Im>0) and amplitude/stress vectors A,B.
|
|
129
|
+
|
|
130
|
+
Solves the 6×6 fundamental eigenproblem ``N ξ = p ξ`` with
|
|
131
|
+
``ξ = (a, b)``, where for a dislocation line along ``e3``::
|
|
132
|
+
|
|
133
|
+
Q_ik = C_{i1k1}, R_ik = C_{i1k2}, T_ik = C_{i2k2}
|
|
134
|
+
N = [[ -T⁻¹Rᵀ , T⁻¹ ],
|
|
135
|
+
[ R T⁻¹Rᵀ - Q , -R T⁻¹ ]]
|
|
136
|
+
|
|
137
|
+
Returns ``(p, A, B)`` for the three roots with positive imaginary part,
|
|
138
|
+
with columns normalised so ``2 aₐᵀ bₐ = 1`` (Stroh orthonormality).
|
|
139
|
+
"""
|
|
140
|
+
Q = C4[:, 0, :, 0]
|
|
141
|
+
R = C4[:, 0, :, 1]
|
|
142
|
+
T = C4[:, 1, :, 1]
|
|
143
|
+
Tinv = torch.linalg.inv(T)
|
|
144
|
+
RT = R.transpose(-1, -2)
|
|
145
|
+
N1 = -Tinv @ RT
|
|
146
|
+
N3 = R @ Tinv @ RT - Q
|
|
147
|
+
top = torch.cat([N1, Tinv], dim=1)
|
|
148
|
+
bot = torch.cat([N3, N1.transpose(-1, -2)], dim=1)
|
|
149
|
+
N = torch.cat([top, bot], dim=0)
|
|
150
|
+
|
|
151
|
+
p_all, _ = torch.linalg.eig(N)
|
|
152
|
+
mask = p_all.imag > 0
|
|
153
|
+
if int(mask.sum()) != 3:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"expected 3 sextic roots with Im(p)>0, got {int(mask.sum())}; "
|
|
156
|
+
"the stiffness may be elastically isotropic (degenerate roots) or "
|
|
157
|
+
"ill-conditioned."
|
|
158
|
+
)
|
|
159
|
+
p = p_all[mask]
|
|
160
|
+
|
|
161
|
+
# Near-degenerate roots ⇒ the matrix is defective and the simple-eigenvector
|
|
162
|
+
# Stroh path returns unreliable amplitude vectors. This happens in the
|
|
163
|
+
# elastically isotropic limit (Zener ratio → 1), where the roots collapse to
|
|
164
|
+
# p = i. Refuse rather than silently return garbage — a dislocation-contrast
|
|
165
|
+
# analysis is only meaningful for an elastically anisotropic crystal anyway.
|
|
166
|
+
pdiff = torch.abs(p[:, None] - p[None, :])
|
|
167
|
+
pdiff = pdiff + torch.eye(3, dtype=pdiff.dtype, device=pdiff.device) * 9.0
|
|
168
|
+
if float(pdiff.min().detach()) < degeneracy_tol:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
"near-degenerate sextic roots: the crystal is at/near the elastically "
|
|
171
|
+
"isotropic limit (Zener ratio ≈ 1), where the simple-eigenvector Stroh "
|
|
172
|
+
"solution is unreliable. Contrast factors require an anisotropic crystal."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Amplitude vectors a from the 3×3 null space of (Q + p(R+Rᵀ) + p²T):
|
|
176
|
+
Qc, Rc, Tc, RTc = (Q.to(p.dtype), R.to(p.dtype),
|
|
177
|
+
T.to(p.dtype), RT.to(p.dtype))
|
|
178
|
+
eye3 = torch.eye(3, dtype=p.dtype, device=C4.device)
|
|
179
|
+
a_cols, b_cols = [], []
|
|
180
|
+
for k in range(3):
|
|
181
|
+
Mk = Qc + p[k] * (Rc + RTc) + p[k] * p[k] * Tc
|
|
182
|
+
# null vector = singular vector with smallest singular value
|
|
183
|
+
_, _, Vh = torch.linalg.svd(Mk)
|
|
184
|
+
a = Vh[-1].conj()
|
|
185
|
+
b = (RTc + p[k] * Tc) @ a # Stroh stress vector
|
|
186
|
+
s = 1.0 / torch.sqrt(2.0 * (a @ b)) # bilinear (no conjugate)
|
|
187
|
+
a_cols.append(a * s)
|
|
188
|
+
b_cols.append(b * s)
|
|
189
|
+
A = torch.stack(a_cols, dim=1)
|
|
190
|
+
B = torch.stack(b_cols, dim=1)
|
|
191
|
+
return p, A, B
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
# Hexagonal stiffness (moved from midas_defect.contrast_factor_hex)
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
def hexagonal_stiffness(
|
|
199
|
+
c11: float, c12: float, c13: float, c33: float, c44: float, *,
|
|
200
|
+
dtype: torch.dtype = torch.float64, device=None,
|
|
201
|
+
) -> torch.Tensor:
|
|
202
|
+
"""6×6 Voigt stiffness of a hexagonal crystal (``C66 = (C11-C12)/2``)."""
|
|
203
|
+
device = torch.device("cpu") if device is None else device
|
|
204
|
+
t = lambda x: torch.as_tensor(x, dtype=dtype, device=device)
|
|
205
|
+
c11, c12, c13, c33, c44 = t(c11), t(c12), t(c13), t(c33), t(c44)
|
|
206
|
+
c66 = (c11 - c12) / 2.0
|
|
207
|
+
z = torch.zeros((), dtype=dtype, device=device)
|
|
208
|
+
rows = [
|
|
209
|
+
torch.stack([c11, c12, c13, z, z, z]),
|
|
210
|
+
torch.stack([c12, c11, c13, z, z, z]),
|
|
211
|
+
torch.stack([c13, c13, c33, z, z, z]),
|
|
212
|
+
torch.stack([z, z, z, c44, z, z]),
|
|
213
|
+
torch.stack([z, z, z, z, c44, z]),
|
|
214
|
+
torch.stack([z, z, z, z, z, c66]),
|
|
215
|
+
]
|
|
216
|
+
return torch.stack(rows, dim=0)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
# Crystal → Cartesian geometry (any crystal system)
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
#
|
|
223
|
+
# The Stroh core works in a Cartesian slip frame. For a *cubic* cell the integer
|
|
224
|
+
# Miller indices double as Cartesian directions, but for any lower symmetry they
|
|
225
|
+
# do not. We reuse the canonical orthogonalisation ("A") matrix from
|
|
226
|
+
# `midas_stress.tensor.lattice_params_to_A_matrix` (Busing-Levy convention, the
|
|
227
|
+
# same matrix the Fable-Beaudoin strain solver uses) — never a re-ported B
|
|
228
|
+
# matrix. ``A`` maps fractional crystal coordinates to Cartesian, so:
|
|
229
|
+
# * a real-space direction [uvw] (Burgers vector, dislocation line) → A · [uvw]
|
|
230
|
+
# * a reciprocal direction (hkl) (plane normal, diffraction vector) → A⁻ᵀ · (hkl)
|
|
231
|
+
# For cubic, A = a·I, so both maps are the identity up to scale and every result
|
|
232
|
+
# is byte-identical to passing raw Miller indices (see the cubic-identity test).
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _crystal_A_matrix(crystal, *, dtype, device) -> torch.Tensor:
|
|
236
|
+
"""Orthogonalisation matrix A (fractional → Cartesian) for a `Crystal`."""
|
|
237
|
+
from midas_stress.tensor import lattice_params_to_A_matrix
|
|
238
|
+
|
|
239
|
+
lat = crystal.lattice
|
|
240
|
+
latc = torch.tensor(
|
|
241
|
+
[lat.a, lat.b, lat.c, lat.alpha, lat.beta, lat.gamma],
|
|
242
|
+
dtype=dtype, device=device)
|
|
243
|
+
return lattice_params_to_A_matrix(latc)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _to_cartesian(v: torch.Tensor, A: torch.Tensor, kind: str) -> torch.Tensor:
|
|
247
|
+
"""Map a Miller vector to Cartesian: ``direct`` → A·v, ``reciprocal`` → A⁻ᵀ·v."""
|
|
248
|
+
if kind == "direct":
|
|
249
|
+
return A @ v
|
|
250
|
+
if kind == "reciprocal":
|
|
251
|
+
return torch.linalg.solve(A.transpose(-1, -2), v) # A⁻ᵀ · v
|
|
252
|
+
raise ValueError(f"kind must be 'direct' or 'reciprocal', got {kind!r}")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ---------------------------------------------------------------------------
|
|
256
|
+
# Slip-system tables
|
|
257
|
+
# ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
def _gen_slip_systems(
|
|
260
|
+
planes: Sequence[Tuple[int, int, int]],
|
|
261
|
+
burgers_pool: Sequence[Tuple[int, int, int]],
|
|
262
|
+
) -> List[Tuple[Tuple[int, int, int], Tuple[int, int, int]]]:
|
|
263
|
+
"""All (normal, burgers) pairs with burgers ⊥ normal, ± deduplicated."""
|
|
264
|
+
systems = []
|
|
265
|
+
seen = set()
|
|
266
|
+
for n in planes:
|
|
267
|
+
nv = np.array(n, float)
|
|
268
|
+
for b in burgers_pool:
|
|
269
|
+
bv = np.array(b, float)
|
|
270
|
+
if abs(float(nv @ bv)) > 1e-9:
|
|
271
|
+
continue
|
|
272
|
+
key = (n, tuple(sorted((b, tuple(-x for x in b)))))
|
|
273
|
+
if key in seen:
|
|
274
|
+
continue
|
|
275
|
+
seen.add(key)
|
|
276
|
+
systems.append((n, b))
|
|
277
|
+
return systems
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def fcc_slip_systems() -> List[Tuple[Tuple[int, int, int], Tuple[int, int, int]]]:
|
|
281
|
+
"""The 12 FCC ``{111}⟨110⟩`` slip systems as ``(plane_normal, burgers)``."""
|
|
282
|
+
planes = [(1, 1, 1), (1, 1, -1), (1, -1, 1), (-1, 1, 1)] # 4 distinct {111}
|
|
283
|
+
burgers = [(1, -1, 0), (1, 0, -1), (0, 1, -1),
|
|
284
|
+
(1, 1, 0), (1, 0, 1), (0, 1, 1)]
|
|
285
|
+
return _gen_slip_systems(planes, burgers)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def bcc_slip_systems() -> List[Tuple[Tuple[int, int, int], Tuple[int, int, int]]]:
|
|
289
|
+
"""The 12 BCC ``{110}⟨111⟩`` slip systems as ``(plane_normal, burgers)``."""
|
|
290
|
+
planes = [(1, 1, 0), (1, -1, 0), (1, 0, 1), # 6 distinct {110}
|
|
291
|
+
(1, 0, -1), (0, 1, 1), (0, 1, -1)]
|
|
292
|
+
burgers = [(1, 1, 1), (1, 1, -1), (1, -1, 1), (-1, 1, 1)]
|
|
293
|
+
return _gen_slip_systems(planes, burgers)
|
|
294
|
+
|