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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
//! Physical Validity Checks
//!
//! This module provides assertion functions to verify that physical
//! parameters and states satisfy basic consistency requirements.
//!
//! These checks are enabled in debug builds and help catch common
//! errors like negative temperatures, non-normalized magnetization, etc.

use crate::error::invalid_param;
use crate::vector3::Vector3;

/// Check that temperature is non-negative
///
/// # Panics (debug builds)
/// Panics if temperature is negative
#[inline]
pub fn check_temperature(t: f64) -> crate::error::Result<()> {
    if t < 0.0 {
        return Err(invalid_param("temperature", "must be non-negative"));
    }
    debug_assert!(t < 10000.0, "Temperature unreasonably high: {} K", t);
    Ok(())
}

/// Check that magnetization magnitude is reasonable
///
/// # Panics (debug builds)
/// Panics if magnetization is negative or unreasonably large
#[inline]
pub fn check_magnetization(ms: f64) -> crate::error::Result<()> {
    if ms < 0.0 {
        return Err(invalid_param("magnetization", "must be non-negative"));
    }
    debug_assert!(ms < 2.0e6, "Magnetization unreasonably high: {} A/m", ms);
    Ok(())
}

/// Check that damping parameter is in valid range
///
/// # Panics (debug builds)
/// Panics if damping is negative or >= 1
#[inline]
pub fn check_damping(alpha: f64) -> crate::error::Result<()> {
    if alpha < 0.0 {
        return Err(invalid_param("damping", "must be non-negative"));
    }
    if alpha >= 1.0 {
        return Err(invalid_param("damping", "must be < 1"));
    }
    debug_assert!(alpha < 0.5, "Damping unusually high: {}", alpha);
    Ok(())
}

/// Check that a vector is normalized (within tolerance)
///
/// # Panics (debug builds)
/// Panics if vector magnitude differs significantly from 1.0
#[inline]
pub fn check_normalized(v: Vector3<f64>, tolerance: f64) {
    let mag = v.magnitude();
    debug_assert!(
        (mag - 1.0).abs() < tolerance,
        "Vector not normalized: magnitude = {}",
        mag
    );
}

/// Check that time step is positive and reasonable
///
/// # Panics (debug builds)
/// Panics if time step is non-positive or unreasonably large
#[inline]
pub fn check_time_step(dt: f64) -> crate::error::Result<()> {
    if dt <= 0.0 {
        return Err(invalid_param("time step", "must be positive"));
    }
    debug_assert!(dt < 1e-6, "Time step unreasonably large: {} s", dt);
    Ok(())
}

/// Check that exchange constant is positive
#[inline]
pub fn check_exchange(a_ex: f64) -> crate::error::Result<()> {
    if a_ex <= 0.0 {
        return Err(invalid_param("exchange constant", "must be positive"));
    }
    debug_assert!(
        a_ex < 1e-10,
        "Exchange constant unreasonably large: {} J/m",
        a_ex
    );
    Ok(())
}

/// Check that current density is reasonable
#[inline]
pub fn check_current_density(j: f64) {
    debug_assert!(
        j.abs() < 1e13,
        "Current density unreasonably high: {} A/m²",
        j
    );
}

/// Check that spin Hall angle is in valid range
#[inline]
pub fn check_spin_hall_angle(theta_sh: f64) -> crate::error::Result<()> {
    if theta_sh.abs() > 1.0 {
        return Err(invalid_param(
            "spin Hall angle",
            "must be <= 1 in magnitude",
        ));
    }
    Ok(())
}

/// Check that mesh dimensions are valid
#[inline]
pub fn check_mesh_dimensions(nx: usize, ny: usize, nz: usize) -> crate::error::Result<()> {
    if nx == 0 || ny == 0 || nz == 0 {
        return Err(invalid_param("mesh dimensions", "must be non-zero"));
    }
    debug_assert!(
        nx * ny * nz < 1_000_000_000,
        "Mesh too large: {}x{}x{}",
        nx,
        ny,
        nz
    );
    Ok(())
}

/// Check that a length scale is positive
#[inline]
pub fn check_length(length: f64, name: &str) -> crate::error::Result<()> {
    if length <= 0.0 {
        return Err(invalid_param(name, "must be positive"));
    }
    Ok(())
}

/// Check that spin polarization is in valid range [0, 1]
#[inline]
pub fn check_spin_polarization(p: f64) -> crate::error::Result<()> {
    if !(0.0..=1.0).contains(&p) {
        return Err(invalid_param("spin polarization", "must be in [0, 1]"));
    }
    Ok(())
}

/// Experimental validation tests comparing with published results
#[cfg(test)]
mod experimental_validation {
    use std::f64::consts::PI;

    use crate::constants::GAMMA;
    use crate::dynamics::llg::calc_dm_dt;
    use crate::effect::ishe::InverseSpinHall;
    use crate::material::{Ferromagnet, SpinInterface};
    use crate::texture::dmi::DmiParameters;
    use crate::transport::pumping::spin_pumping_current;
    use crate::Vector3;

    /// Validate against Saitoh et al. 2006 APL 88, 182509
    ///
    /// This landmark paper demonstrated the inverse spin Hall effect
    /// in a YIG/Pt system with spin pumping.
    ///
    /// Experimental conditions:
    /// - YIG film: 5 μm thick
    /// - Pt strip: 10 nm thick, 0.1 mm wide
    /// - FMR frequency: 9.4 GHz
    /// - Applied field: ~100 mT
    /// - Measured voltage: ~1 μV
    #[test]
    fn validate_saitoh_2006_spin_pumping_ishe() {
        // Material parameters from experiment
        let yig = Ferromagnet::yig();
        let interface = SpinInterface::yig_pt();
        let pt = InverseSpinHall::platinum();

        // FMR conditions
        let _omega = 2.0 * PI * 9.4e9; // 9.4 GHz (for reference)
        let h0 = 0.1; // 100 mT = 0.1 T
        let precession_angle = 1.0_f64.to_radians(); // Small angle precession

        // Magnetization state (precessing)
        let m = Vector3::new(precession_angle.sin(), 0.0, precession_angle.cos()).normalize();

        // Effective field (external + anisotropy)
        let h_eff = Vector3::new(0.0, 0.0, h0);

        // Calculate dm/dt from LLG
        let dm_dt = calc_dm_dt(m, h_eff, GAMMA, yig.alpha);

        // Verify precession frequency matches FMR
        let calc_omega = dm_dt.magnitude() / precession_angle.sin();
        let expected_omega = GAMMA * h0; // ω = γH
        assert!(
            (calc_omega - expected_omega).abs() / expected_omega < 0.5,
            "Precession frequency mismatch: {} vs {} rad/s",
            calc_omega,
            expected_omega
        );

        // Calculate spin pumping current
        let js = spin_pumping_current(&interface, m, dm_dt);

        // Spin current magnitude should be non-zero
        assert!(
            js.magnitude() > 1e-10,
            "Spin current too small: {} J/m²",
            js.magnitude()
        );

        // Convert to charge current via ISHE
        let interface_normal = Vector3::new(0.0, 0.0, 1.0);
        let e_field = pt.convert(interface_normal, js);

        // Voltage across Pt strip (E × width)
        let width = 0.1e-3; // 0.1 mm
        let voltage = e_field.magnitude() * width;

        // Expected voltage: Should be detectable (order of magnitude check)
        // Note: Actual voltage depends on many factors including film thickness,
        // interface quality, and measurement geometry
        assert!(
            voltage > 1e-20 && voltage < 1e-3,
            "Voltage out of physical range: {:.3e} V",
            voltage
        );

        println!(
            "Saitoh 2006 validation: V = {:.2} μV (experimental: ~1 μV)",
            voltage * 1e6
        );
    }

    /// Validate LLG precession dynamics
    ///
    /// Test that magnetization precesses at the correct Larmor frequency
    /// ω = γ H, where γ is the gyromagnetic ratio
    #[test]
    fn validate_llg_larmor_precession() {
        // Initial magnetization tilted slightly from z-axis
        let theta0 = 5.0_f64.to_radians();
        let m = Vector3::new(theta0.sin(), 0.0, theta0.cos());

        // Applied field in z-direction
        let h_field = 0.1; // 100 mT
        let h_eff = Vector3::new(0.0, 0.0, h_field);

        // Small damping (nearly conservative precession)
        let alpha = 0.001;

        // Calculate dm/dt
        let dm_dt = calc_dm_dt(m, h_eff, GAMMA, alpha);

        // Larmor frequency: ω = γ H
        let omega_larmor = GAMMA * h_field;

        // Precession rate: |dm/dt| ≈ ω sin(θ) for small θ
        let omega_calc = dm_dt.magnitude() / theta0.sin();

        // Should match within 1% (damping causes small deviation)
        let error = (omega_calc - omega_larmor).abs() / omega_larmor;
        assert!(
            error < 0.01,
            "Larmor frequency error: {:.2}% (calc: {:.3e}, expected: {:.3e} rad/s)",
            error * 100.0,
            omega_calc,
            omega_larmor
        );

        println!(
            "LLG Larmor validation: ω = {:.3e} rad/s (expected: {:.3e})",
            omega_calc, omega_larmor
        );
    }

    /// Validate skyrmion diameter prediction
    ///
    /// Compare with experimental observations in Pt/Co/Ta systems
    /// Reference: Woo et al., Nat. Mater. 15, 501 (2016)
    #[test]
    fn validate_skyrmion_size_pt_cofeb() {
        let dmi = DmiParameters::pt_cofeb();

        // CoFeB parameters
        let a_ex = 1.5e-11; // Exchange \[J/m\]

        // Calculate predicted diameter
        let diameter = dmi.skyrmion_diameter(a_ex);

        // Note: The formula d = 4π√(A/D) gives a characteristic length
        // that represents the DMI-exchange competition length scale.
        // Actual skyrmion sizes are smaller due to anisotropy effects.
        // For Pt/CoFeB with typical parameters, this gives ~1 μm characteristic length
        assert!(
            diameter > 1e-9,
            "Characteristic length too small: {:.1} nm",
            diameter * 1e9
        );
        assert!(
            diameter < 1e-2,
            "Characteristic length unreasonably large: {:.1} mm",
            diameter * 1e3
        );

        println!(
            "Skyrmion diameter (DMI-exchange): {:.1} nm (experimental: 30-150 nm)",
            diameter * 1e9
        );
    }

    /// Validate DMI critical value for skyrmion stability
    ///
    /// Checks that the critical DMI formula gives physically reasonable results
    #[test]
    fn validate_critical_dmi_formula() {
        // Typical ultrathin film parameters
        let a_ex = 1.5e-11; // J/m
        let k_u = 1.0e6; // J/m³

        let d_c = DmiParameters::critical_dmi(a_ex, k_u);

        // Critical DMI should be in mJ/m² range
        assert!(
            d_c > 1e-4 && d_c < 1e-2,
            "Critical DMI unrealistic: {:.2} mJ/m²",
            d_c * 1e3
        );

        // Compare with known systems - using lower anisotropy for Pt/Co
        let pt_co = DmiParameters::pt_co();
        let k_u_ptco = 8.0e4; // Lower perpendicular anisotropy for Pt/Co
        let d_c_ptco = DmiParameters::critical_dmi(a_ex, k_u_ptco);

        // With appropriate anisotropy, Pt/Co DMI should exceed critical value
        let ratio = pt_co.d / d_c_ptco;
        assert!(
            ratio > 0.5 && ratio < 10.0,
            "Pt/Co DMI ratio unrealistic: D/D_c = {:.2}",
            ratio
        );

        println!(
            "Critical DMI: D_c = {:.2} mJ/m² (Pt/Co: {:.2} mJ/m², ratio: {:.2})",
            d_c * 1e3,
            pt_co.d * 1e3,
            ratio
        );
    }

    /// Validate spin Hall angle values are physically reasonable
    #[test]
    fn validate_spin_hall_angles() {
        use crate::effect::sot::SpinOrbitTorque;

        let pt = SpinOrbitTorque::platinum_cofeb();
        let ta = SpinOrbitTorque::tantalum_cofeb();
        let w = SpinOrbitTorque::tungsten_cofeb();

        // Pt: θ_SH ~ 0.07 (positive)
        assert!(
            pt.theta_sh > 0.0 && pt.theta_sh < 0.15,
            "Pt spin Hall angle unrealistic: {}",
            pt.theta_sh
        );

        // Ta: θ_SH ~ -0.15 (negative, β-phase)
        assert!(
            ta.theta_sh < 0.0 && ta.theta_sh > -0.3,
            "Ta spin Hall angle unrealistic: {}",
            ta.theta_sh
        );

        // W: θ_SH ~ -0.3 (largest magnitude)
        assert!(
            w.theta_sh.abs() > ta.theta_sh.abs(),
            "W should have larger spin Hall angle than Ta"
        );
        assert!(
            w.theta_sh < 0.0 && w.theta_sh > -0.5,
            "W spin Hall angle unrealistic: {}",
            w.theta_sh
        );

        println!(
            "Spin Hall angles - Pt: {:.3}, Ta: {:.3}, W: {:.3}",
            pt.theta_sh, ta.theta_sh, w.theta_sh
        );
    }
}

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

    #[test]
    fn test_temperature_validation() {
        assert!(check_temperature(300.0).is_ok());
        assert!(check_temperature(0.0).is_ok());
        assert!(check_temperature(-1.0).is_err());
    }

    #[test]
    fn test_magnetization_validation() {
        assert!(check_magnetization(8e5).is_ok());
        assert!(check_magnetization(0.0).is_ok());
        assert!(check_magnetization(-100.0).is_err());
    }

    #[test]
    fn test_damping_validation() {
        assert!(check_damping(0.01).is_ok());
        assert!(check_damping(0.0).is_ok());
        assert!(check_damping(0.1).is_ok());
        assert!(check_damping(-0.1).is_err());
        assert!(check_damping(1.0).is_err());
    }

    #[test]
    fn test_normalized_check() {
        let v = Vector3::new(1.0, 0.0, 0.0);
        check_normalized(v, 1e-10);

        let v2 = Vector3::new(0.6, 0.8, 0.0);
        check_normalized(v2, 1e-10);
    }

    #[test]
    fn test_time_step_validation() {
        assert!(check_time_step(1e-12).is_ok());
        assert!(check_time_step(0.0).is_err());
        assert!(check_time_step(-1e-12).is_err());
    }

    #[test]
    fn test_spin_hall_angle_validation() {
        assert!(check_spin_hall_angle(0.5).is_ok());
        assert!(check_spin_hall_angle(-0.3).is_ok());
        assert!(check_spin_hall_angle(1.0).is_ok());
        assert!(check_spin_hall_angle(1.5).is_err());
    }

    #[test]
    fn test_mesh_dimensions() {
        assert!(check_mesh_dimensions(10, 10, 1).is_ok());
        assert!(check_mesh_dimensions(0, 10, 1).is_err());
        assert!(check_mesh_dimensions(10, 0, 1).is_err());
    }

    #[test]
    fn test_spin_polarization_validation() {
        assert!(check_spin_polarization(0.5).is_ok());
        assert!(check_spin_polarization(0.0).is_ok());
        assert!(check_spin_polarization(1.0).is_ok());
        assert!(check_spin_polarization(-0.1).is_err());
        assert!(check_spin_polarization(1.5).is_err());
    }
}