spintronics 0.3.0

Pure Rust library for simulating spin dynamics, spin current generation, and conversion phenomena in magnetic and topological materials
Documentation
//! Spin pumping detection via inverse spin Hall effect
//!
//! This module implements the detection of magnon-driven spin currents
//! using the spin pumping effect and ISHE, as demonstrated by Saitoh et al.

use std::f64::consts::PI;

use super::chain::SpinChain;
use crate::constants::HBAR;
use crate::vector3::Vector3;

/// Spin pumping detector using inverse spin Hall effect
///
/// Detects spin current generated by magnetization dynamics and converts
/// it to a measurable voltage via the ISHE in an adjacent normal metal.
#[derive(Debug, Clone)]
pub struct SpinPumpingDetector {
    /// Position index in the spin chain where detection occurs
    pub position_idx: usize,

    /// Real part of spin-mixing conductance [Ω⁻¹ m⁻²]
    /// Typical values: 1e18 to 1e20 for YIG/Pt, Py/Pt interfaces
    pub g_r: f64,

    /// Spin Hall angle of the detector material (dimensionless)
    /// Pt: ~0.08, Ta: ~0.12, W: ~-0.3
    pub theta_sh: f64,

    /// Resistivity of the detector material [Ω·m]
    /// Pt: ~2.0e-7, Ta: ~1.8e-7
    pub rho: f64,

    /// Detector strip width \[m\]
    /// Used to convert electric field to measurable voltage
    pub strip_width: f64,

    /// Interface normal direction (normalized)
    /// Direction from ferromagnet to normal metal
    pub interface_normal: Vector3<f64>,

    /// Previous magnetization (for calculating dm/dt)
    prev_m: Option<Vector3<f64>>,
}

impl SpinPumpingDetector {
    /// Create a new spin pumping detector
    ///
    /// # Arguments
    /// * `position_idx` - Index in the spin chain where detection occurs
    /// * `g_r` - Spin-mixing conductance [Ω⁻¹ m⁻²]
    /// * `theta_sh` - Spin Hall angle
    /// * `rho` - Resistivity [Ω·m]
    /// * `strip_width` - Width of detector strip \[m\]
    pub fn new(position_idx: usize, g_r: f64, theta_sh: f64, rho: f64, strip_width: f64) -> Self {
        Self {
            position_idx,
            g_r,
            theta_sh,
            rho,
            strip_width,
            interface_normal: Vector3::new(0.0, 1.0, 0.0),
            prev_m: None,
        }
    }

    /// Create a YIG/Pt detector at specified position
    pub fn yig_pt(position_idx: usize, strip_width: f64) -> Self {
        Self::new(
            position_idx,
            1.0e19, // g_r for YIG/Pt
            0.08,   // θ_SH for Pt
            2.0e-7, // ρ for Pt
            strip_width,
        )
    }

    /// Create a Permalloy/Pt detector
    pub fn py_pt(position_idx: usize, strip_width: f64) -> Self {
        Self::new(
            position_idx,
            5.0e19, // g_r for Py/Pt (higher than YIG/Pt)
            0.08,   // θ_SH for Pt
            2.0e-7, // ρ for Pt
            strip_width,
        )
    }

    /// Detect spin current and calculate ISHE voltage
    ///
    /// Returns the ISHE-generated voltage \[V\] based on:
    /// 1. Spin pumping: J_s = (ℏ/4π) g_r (m × dm/dt)
    /// 2. ISHE: E = ρ θ_SH (j_s × σ)
    ///
    /// # Arguments
    /// * `chain` - Spin chain being measured
    /// * `dt` - Time step used for calculating dm/dt
    ///
    /// # Returns
    /// Voltage signal in Volts
    pub fn detect(&mut self, chain: &SpinChain, dt: f64) -> f64 {
        let m_curr = chain.spins[self.position_idx];

        // Calculate dm/dt
        let dm_dt = if let Some(prev_m) = self.prev_m {
            (m_curr - prev_m) * (1.0 / dt)
        } else {
            // First call, no previous magnetization
            self.prev_m = Some(m_curr);
            return 0.0;
        };

        // Update previous magnetization
        self.prev_m = Some(m_curr);

        // Calculate spin current density using Saitoh's formula
        // J_s = (ℏ/4π) g_r (m × dm/dt)
        let js_vector = m_curr.cross(&dm_dt);
        let prefactor = HBAR / (4.0 * PI);
        let js_magnitude = prefactor * self.g_r * js_vector.magnitude();

        // ISHE voltage generation
        // E = ρ θ_SH |J_s|
        // V = E × width
        let e_field = self.rho * self.theta_sh * js_magnitude;
        e_field * self.strip_width
    }

    /// Get instantaneous spin current density \[J/m²\]
    ///
    /// This returns the magnitude of the spin current without ISHE conversion
    pub fn spin_current_density(&self, chain: &SpinChain, dt: f64) -> f64 {
        if let Some(prev_m) = self.prev_m {
            let m_curr = chain.spins[self.position_idx];
            let dm_dt = (m_curr - prev_m) * (1.0 / dt);
            let js_vector = m_curr.cross(&dm_dt);
            let prefactor = HBAR / (4.0 * PI);
            prefactor * self.g_r * js_vector.magnitude()
        } else {
            0.0
        }
    }

    /// Reset the detector state
    ///
    /// Call this when starting a new simulation
    pub fn reset(&mut self) {
        self.prev_m = None;
    }

    /// Get conversion efficiency (voltage per unit spin current)
    pub fn conversion_efficiency(&self) -> f64 {
        self.rho * self.theta_sh * self.strip_width
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::magnon::chain::{ChainParameters, SpinChain};

    #[test]
    fn test_detector_creation() {
        let detector = SpinPumpingDetector::yig_pt(50, 1.0e-3);
        assert_eq!(detector.position_idx, 50);
        assert!(detector.g_r > 0.0);
        assert!(detector.theta_sh > 0.0);
    }

    #[test]
    fn test_no_dynamics_no_signal() {
        let chain = SpinChain::new(100, ChainParameters::default());
        let mut detector = SpinPumpingDetector::yig_pt(50, 1.0e-3);

        // First call initializes
        let v1 = detector.detect(&chain, 1.0e-13);
        assert_eq!(v1, 0.0);

        // No change in magnetization -> no signal
        let v2 = detector.detect(&chain, 1.0e-13);
        assert!(v2.abs() < 1e-30);
    }

    #[test]
    fn test_magnetization_change_produces_signal() {
        let mut chain = SpinChain::new(100, ChainParameters::default());
        let mut detector = SpinPumpingDetector::yig_pt(50, 1.0e-3);

        // Initialize
        detector.detect(&chain, 1.0e-13);

        // Change magnetization
        chain.spins[50] = Vector3::new(0.9, 0.1, 0.0).normalize();

        // Should detect non-zero voltage
        let voltage = detector.detect(&chain, 1.0e-13);
        assert!(voltage.abs() > 0.0);
    }

    #[test]
    fn test_conversion_efficiency() {
        let detector = SpinPumpingDetector::yig_pt(50, 1.0e-3);
        let efficiency = detector.conversion_efficiency();
        assert!(efficiency > 0.0);

        // Larger strip width -> higher voltage
        let detector2 = SpinPumpingDetector::yig_pt(50, 2.0e-3);
        assert!(detector2.conversion_efficiency() > efficiency);
    }

    #[test]
    fn test_reset() {
        let chain = SpinChain::new(100, ChainParameters::default());
        let mut detector = SpinPumpingDetector::yig_pt(50, 1.0e-3);

        detector.detect(&chain, 1.0e-13);
        assert!(detector.prev_m.is_some());

        detector.reset();
        assert!(detector.prev_m.is_none());
    }
}