spintronics 0.3.0

Pure Rust library for simulating spin dynamics, spin current generation, and conversion phenomena in magnetic and topological materials
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
//! Spin-Orbit Torque (SOT) Effects
//!
//! This module implements spin-orbit torque, which arises from spin-orbit coupling
//! in heavy-metal/ferromagnet bilayers. SOT can be used to manipulate magnetization
//! electrically without an external magnetic field.
//!
//! ## Physics Background
//!
//! When charge current flows through a heavy metal (e.g., Pt, Ta, W) with strong
//! spin-orbit coupling, it generates a transverse spin current via the Spin Hall Effect.
//! This spin current diffuses into an adjacent ferromagnetic layer and exerts torque
//! on the magnetization.
//!
//! The SOT consists of two components:
//!
//! 1. **Field-like Torque**: τ_FL = -γ H_FL × m
//!    - Equivalent to an effective magnetic field
//!    - Proportional to m × (y × m) where y is the spin polarization direction
//!
//! 2. **Damping-like Torque**: τ_DL = -γ H_DL × (m × y)
//!    - Can overcome Gilbert damping
//!    - Enables current-driven magnetization switching
//!
//! ## Key References
//!
//! - I. M. Miron et al., "Perpendicular switching of a single ferromagnetic layer
//!   induced by in-plane current injection", Nature 476, 189 (2011)
//! - L. Liu et al., "Spin-Torque Switching with the Giant Spin Hall Effect of Tantalum",
//!   Science 336, 555 (2012)
//! - K. Garello et al., "Symmetry and magnitude of spin–orbit torques in ferromagnetic
//!   heterostructures", Nat. Nanotechnology 8, 587 (2013)

use std::fmt;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::constants::HBAR;
use crate::vector3::Vector3;

/// Spin-orbit torque calculator for heavy-metal/ferromagnet bilayers
///
/// # Example
/// ```
/// use spintronics::effect::sot::SpinOrbitTorque;
///
/// // Create Pt/CoFeB SOT system
/// let sot = SpinOrbitTorque::platinum_cofeb();
///
/// // Calculate effective spin Hall efficiency
/// let eff = sot.effective_spin_hall_efficiency();
///
/// // Efficiency should be positive and less than θ_SH
/// assert!(eff > 0.0);
/// assert!(eff < sot.theta_sh.abs());
///
/// // Tantalum has larger (negative) spin Hall angle
/// let sot_ta = SpinOrbitTorque::tantalum_cofeb();
/// assert!(sot_ta.theta_sh.abs() > sot.theta_sh.abs());
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SpinOrbitTorque {
    /// Spin Hall angle of the heavy metal (dimensionless)
    /// Pt: ~0.07, Ta: ~-0.15, W: ~-0.3
    pub theta_sh: f64,

    /// Heavy metal resistivity [Ω·m]
    pub resistivity: f64,

    /// Heavy metal layer thickness \[m\]
    pub thickness: f64,

    /// Spin diffusion length in heavy metal \[m\]
    pub lambda_sd: f64,

    /// Interfacial spin transparency (0 to 1)
    pub transparency: f64,
}

impl Default for SpinOrbitTorque {
    /// Default to Pt/CoFeB parameters
    fn default() -> Self {
        Self::platinum_cofeb()
    }
}

impl SpinOrbitTorque {
    /// Create SOT parameters for Pt/CoFeB interface
    ///
    /// Based on experimental measurements from Miron et al., Nature 476, 189 (2011)
    pub fn platinum_cofeb() -> Self {
        Self {
            theta_sh: 0.07,      // Spin Hall angle for Pt
            resistivity: 2.0e-7, // Ω·m
            thickness: 5.0e-9,   // 5 nm Pt
            lambda_sd: 1.5e-9,   // 1.5 nm spin diffusion length
            transparency: 0.5,   // Moderate transparency
        }
    }

    /// Create SOT parameters for Ta/CoFeB interface
    ///
    /// Based on Liu et al., Science 336, 555 (2012)
    pub fn tantalum_cofeb() -> Self {
        Self {
            theta_sh: -0.15, // Negative spin Hall angle for Ta (β-phase)
            resistivity: 1.8e-7,
            thickness: 5.0e-9,
            lambda_sd: 1.2e-9,
            transparency: 0.6,
        }
    }

    /// Create SOT parameters for W/CoFeB interface
    ///
    /// W has the largest spin Hall angle among common heavy metals
    pub fn tungsten_cofeb() -> Self {
        Self {
            theta_sh: -0.3, // Very large negative spin Hall angle
            resistivity: 2.5e-7,
            thickness: 5.0e-9,
            lambda_sd: 1.0e-9,
            transparency: 0.4,
        }
    }

    /// Calculate the effective spin Hall efficiency
    ///
    /// Accounts for finite thickness effects and spin backflow
    pub fn effective_spin_hall_efficiency(&self) -> f64 {
        let tanh_factor = (self.thickness / self.lambda_sd).tanh();
        self.theta_sh * tanh_factor * self.transparency
    }

    /// Calculate damping-like effective field from current density
    ///
    /// # Arguments
    /// * `j_charge` - Charge current density in heavy metal \[A/m²\]
    /// * `m` - Normalized magnetization vector
    /// * `current_direction` - Direction of current flow (normalized)
    /// * `ms` - Saturation magnetization \[A/m\]
    ///
    /// # Returns
    /// Damping-like effective field H_DL \[A/m\]
    ///
    /// Formula: H_DL = (ℏ/(2e)) * (j_e * θ_SH) / (M_s * t_FM) * (m × y)
    ///
    /// # Example
    /// ```
    /// use spintronics::effect::sot::SpinOrbitTorque;
    /// use spintronics::Vector3;
    ///
    /// // Pt/CoFeB bilayer
    /// let sot = SpinOrbitTorque::platinum_cofeb();
    ///
    /// // Perpendicular magnetization (slightly tilted)
    /// let m = Vector3::new(0.0, 0.1, 0.995).normalize();
    /// // Current along x-axis
    /// let j_current = Vector3::new(1.0, 0.0, 0.0);
    /// // Current density: 10^11 A/m² (typical for SOT switching)
    /// let j_e = 1.0e11;
    /// // CoFeB saturation magnetization
    /// let ms = 1.0e6; // 1 MA/m
    ///
    /// // Calculate damping-like torque field
    /// let h_dl = sot.damping_like_field(j_e, m, j_current, ms);
    ///
    /// // H_DL should be perpendicular to m
    /// assert!(h_dl.dot(&m).abs() < 1e-6);
    ///
    /// // Magnitude should be physically reasonable (kA/m range)
    /// assert!(h_dl.magnitude() > 0.0);
    /// assert!(h_dl.magnitude() < 1.0e5);
    /// ```
    #[inline]
    pub fn damping_like_field(
        &self,
        j_charge: f64,
        m: Vector3<f64>,
        current_direction: Vector3<f64>,
        ms: f64,
    ) -> Vector3<f64> {
        // Spin polarization direction (perpendicular to current and normal to interface)
        // Assuming current flows in-plane and normal is z-direction
        let normal = Vector3::new(0.0, 0.0, 1.0);
        let spin_polarization = current_direction.cross(&normal).normalize();

        // Effective spin Hall angle (including backflow and transparency)
        let theta_eff = self.effective_spin_hall_efficiency();

        // Prefactor: (ℏ/(2e)) * j * θ_SH / (M_s * t)
        let e_charge = 1.602e-19; // Elementary charge \[C\]
        let prefactor = (HBAR / (2.0 * e_charge)) * j_charge * theta_eff / (ms * self.thickness);

        // H_DL = prefactor * (m × σ) where σ is spin polarization direction
        m.cross(&spin_polarization) * prefactor
    }

    /// Calculate field-like effective field from current density
    ///
    /// The field-like term is typically smaller than damping-like term
    /// and its origin is still under debate (Rashba effect, interfacial effects, etc.)
    ///
    /// # Arguments
    /// * `j_charge` - Charge current density \[A/m²\]
    /// * `m` - Normalized magnetization vector
    /// * `current_direction` - Direction of current flow (normalized)
    /// * `ms` - Saturation magnetization \[A/m\]
    /// * `field_like_ratio` - Ratio of field-like to damping-like torque (typically 0.1-0.3)
    ///
    /// # Returns
    /// Field-like effective field H_FL \[A/m\]
    #[inline]
    pub fn field_like_field(
        &self,
        j_charge: f64,
        m: Vector3<f64>,
        current_direction: Vector3<f64>,
        ms: f64,
        field_like_ratio: f64,
    ) -> Vector3<f64> {
        // H_FL = -field_like_ratio * H_DL × m
        let h_dl = self.damping_like_field(j_charge, m, current_direction, ms);
        h_dl.cross(&m) * (-field_like_ratio)
    }

    /// Calculate total SOT effective field
    ///
    /// Returns (H_DL, H_FL) tuple
    pub fn total_field(
        &self,
        j_charge: f64,
        m: Vector3<f64>,
        current_direction: Vector3<f64>,
        ms: f64,
        field_like_ratio: f64,
    ) -> (Vector3<f64>, Vector3<f64>) {
        let h_dl = self.damping_like_field(j_charge, m, current_direction, ms);
        let h_fl = self.field_like_field(j_charge, m, current_direction, ms, field_like_ratio);
        (h_dl, h_fl)
    }

    /// Calculate critical switching current density
    ///
    /// For perpendicular magnetization switching via damping-like SOT
    /// Requires small in-plane field to break symmetry
    ///
    /// Formula: j_c = (2 e / ℏ) * (M_s * t_FM / θ_SH) * (H_k + 2πM_s)
    ///
    /// # Arguments
    /// * `ms` - Saturation magnetization \[A/m\]
    /// * `h_k` - Perpendicular anisotropy field \[A/m\]
    ///
    /// # Returns
    /// Critical current density \[A/m²\]
    ///
    /// # Example
    /// ```
    /// use spintronics::effect::sot::SpinOrbitTorque;
    ///
    /// // Pt/CoFeB system with moderate anisotropy
    /// let sot = SpinOrbitTorque::platinum_cofeb();
    ///
    /// // CoFeB with moderate perpendicular magnetic anisotropy
    /// let ms = 1.0e6;    // 1 MA/m saturation magnetization
    /// let h_k = 5.0e4;   // 50 kA/m anisotropy field (moderate PMA)
    ///
    /// // Calculate critical current for magnetization switching
    /// // j_c = (2e/ℏ) × (M_s × t / θ_SH) × (H_k + M_s)
    /// let j_c = sot.critical_current_density(ms, h_k);
    ///
    /// // Critical current should be positive and finite
    /// assert!(j_c > 0.0);
    /// assert!(j_c.is_finite());
    ///
    /// // Typical experimental range: 10^11 to 10^13 A/m²
    /// // (varies with material stack, anisotropy, and efficiency)
    /// assert!(j_c > 1.0e10);
    ///
    /// println!("Critical switching current: {:.2e} A/m²", j_c);
    /// ```
    #[allow(dead_code)]
    pub fn critical_current_density(&self, ms: f64, h_k: f64) -> f64 {
        let e_charge = 1.602e-19;
        let theta_eff = self.effective_spin_hall_efficiency();

        // Demagnetization field for thin film: H_demag = M_s
        let h_eff = h_k + ms;

        (2.0 * e_charge / HBAR) * (ms * self.thickness / theta_eff.abs()) * h_eff
    }

    /// Builder method to set spin Hall angle
    pub fn with_theta_sh(mut self, theta_sh: f64) -> Self {
        self.theta_sh = theta_sh;
        self
    }

    /// Builder method to set resistivity
    pub fn with_resistivity(mut self, rho: f64) -> Self {
        self.resistivity = rho;
        self
    }

    /// Builder method to set heavy metal thickness
    pub fn with_thickness(mut self, thickness: f64) -> Self {
        self.thickness = thickness;
        self
    }

    /// Builder method to set spin diffusion length
    pub fn with_lambda_sd(mut self, lambda_sd: f64) -> Self {
        self.lambda_sd = lambda_sd;
        self
    }

    /// Builder method to set interface transparency
    pub fn with_transparency(mut self, transparency: f64) -> Self {
        self.transparency = transparency.clamp(0.0, 1.0);
        self
    }
}

impl fmt::Display for SpinOrbitTorque {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "SpinOrbitTorque(θ_SH={:.3}, t={:.1} nm, λ_sd={:.1} nm, T={:.2})",
            self.theta_sh,
            self.thickness * 1e9,
            self.lambda_sd * 1e9,
            self.transparency
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_platinum_sot() {
        let sot = SpinOrbitTorque::platinum_cofeb();
        assert!(sot.theta_sh > 0.0); // Positive for Pt
        assert!(sot.theta_sh < 0.1);
    }

    #[test]
    fn test_tantalum_sot() {
        let sot = SpinOrbitTorque::tantalum_cofeb();
        assert!(sot.theta_sh < 0.0); // Negative for Ta
        assert!(sot.theta_sh.abs() > 0.1);
    }

    #[test]
    fn test_tungsten_sot() {
        let sot = SpinOrbitTorque::tungsten_cofeb();
        assert!(sot.theta_sh < -0.2); // Large negative for W
    }

    #[test]
    fn test_effective_spin_hall_efficiency() {
        let sot = SpinOrbitTorque::platinum_cofeb();
        let theta_eff = sot.effective_spin_hall_efficiency();

        // Should be smaller than bare theta_sh due to backflow and transparency
        assert!(theta_eff < sot.theta_sh);
        assert!(theta_eff > 0.0);
    }

    #[test]
    fn test_damping_like_field() {
        let sot = SpinOrbitTorque::platinum_cofeb();

        let m = Vector3::new(0.0, 0.1, 1.0).normalize(); // Nearly perpendicular
        let j_charge = 1.0e11; // 10^11 A/m² (typical experimental value)
        let current_dir = Vector3::new(1.0, 0.0, 0.0); // Current along x
        let ms = 1.0e6; // 1 MA/m

        let h_dl = sot.damping_like_field(j_charge, m, current_dir, ms);

        // H_DL should be perpendicular to both m and spin polarization direction
        assert!(h_dl.dot(&m).abs() < 1e-6);

        // H_DL magnitude should be reasonable (order of kA/m for typical j)
        let h_magnitude = h_dl.magnitude();
        assert!(h_magnitude > 0.0);
        assert!(h_magnitude < 1.0e5); // Should be less than 100 kA/m
    }

    #[test]
    fn test_field_like_field() {
        let sot = SpinOrbitTorque::platinum_cofeb();

        let m = Vector3::new(0.0, 0.0, 1.0); // Perpendicular
        let j_charge = 1.0e11;
        let current_dir = Vector3::new(1.0, 0.0, 0.0);
        let ms = 1.0e6;
        let fl_ratio = 0.2;

        let h_fl = sot.field_like_field(j_charge, m, current_dir, ms, fl_ratio);

        // H_FL magnitude should be smaller than H_DL
        let h_dl = sot.damping_like_field(j_charge, m, current_dir, ms);
        assert!(h_fl.magnitude() < h_dl.magnitude());
    }

    #[test]
    fn test_critical_current() {
        let sot = SpinOrbitTorque::platinum_cofeb();

        let ms = 1.0e6; // 1 MA/m
        let h_k = 1.0e5; // 100 kA/m (perpendicular anisotropy field)

        let j_c = sot.critical_current_density(ms, h_k);

        // Critical current should be positive and reasonable
        // Typical experimental values range from 10^10 to 10^14 A/m²
        // depending on material stack, anisotropy, and interface quality
        assert!(j_c > 0.0);
        assert!(j_c > 1.0e10); // Lower bound: should be at least 10^10 A/m²
                               // Note: High-anisotropy materials can require j_c > 10^13 A/m²
    }

    #[test]
    fn test_total_field() {
        let sot = SpinOrbitTorque::platinum_cofeb();

        let m = Vector3::new(0.0, 0.1, 1.0).normalize();
        let j_charge = 1.0e11;
        let current_dir = Vector3::new(1.0, 0.0, 0.0);
        let ms = 1.0e6;
        let fl_ratio = 0.2;

        let (h_dl, h_fl) = sot.total_field(j_charge, m, current_dir, ms, fl_ratio);

        // Both fields should be non-zero
        assert!(h_dl.magnitude() > 0.0);
        assert!(h_fl.magnitude() > 0.0);

        // Damping-like should be larger
        assert!(h_dl.magnitude() > h_fl.magnitude());
    }
}