use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::orbital_hall::{OrbitalHallMaterial, OrbitalToSpinConverter};
use crate::constants::{E_CHARGE, GAMMA, HBAR};
use crate::error::{Error, Result};
use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct OrbitalTorque {
pub material: OrbitalHallMaterial,
pub converter: OrbitalToSpinConverter,
pub ms: f64,
pub thickness_fm: f64,
pub damping_like_efficiency: f64,
pub field_like_efficiency: f64,
}
impl OrbitalTorque {
pub fn new(
material: OrbitalHallMaterial,
converter: OrbitalToSpinConverter,
ms: f64,
thickness_fm: f64,
damping_like_efficiency: f64,
field_like_efficiency: f64,
) -> Result<Self> {
if ms <= 0.0 {
return Err(Error::InvalidParameter {
param: "ms".to_string(),
reason: "saturation magnetization must be positive".to_string(),
});
}
if thickness_fm <= 0.0 {
return Err(Error::InvalidParameter {
param: "thickness_fm".to_string(),
reason: "FM thickness must be positive".to_string(),
});
}
if !(0.0..=1.0).contains(&damping_like_efficiency) {
return Err(Error::InvalidParameter {
param: "damping_like_efficiency".to_string(),
reason: "must be between 0 and 1".to_string(),
});
}
if !(0.0..=1.0).contains(&field_like_efficiency) {
return Err(Error::InvalidParameter {
param: "field_like_efficiency".to_string(),
reason: "must be between 0 and 1".to_string(),
});
}
Ok(Self {
material,
converter,
ms,
thickness_fm,
damping_like_efficiency,
field_like_efficiency,
})
}
pub fn cr_pt_cofeb() -> Self {
Self {
material: OrbitalHallMaterial::chromium(),
converter: OrbitalToSpinConverter::cr_pt(),
ms: 1.0e6,
thickness_fm: 2.0e-9,
damping_like_efficiency: 0.8,
field_like_efficiency: 0.2,
}
}
pub fn ti_pt_cofeb() -> Self {
Self {
material: OrbitalHallMaterial::titanium(),
converter: OrbitalToSpinConverter::ti_pt(),
ms: 1.0e6,
thickness_fm: 2.0e-9,
damping_like_efficiency: 0.75,
field_like_efficiency: 0.25,
}
}
pub fn cu_pt_cofeb() -> Self {
Self {
material: OrbitalHallMaterial::copper(),
converter: OrbitalToSpinConverter::cu_pt(),
ms: 1.0e6,
thickness_fm: 2.0e-9,
damping_like_efficiency: 0.7,
field_like_efficiency: 0.15,
}
}
fn torque_prefactor(&self) -> f64 {
let hbar_over_2e = HBAR / (2.0 * E_CHARGE);
let eta_ls = self.converter.conversion_efficiency;
let theta_oh = self.material.orbital_hall_angle;
hbar_over_2e * GAMMA * eta_ls * theta_oh / (self.ms * self.thickness_fm)
}
pub fn compute_torques(
&self,
m: Vector3<f64>,
j_charge: f64,
current_direction: Vector3<f64>,
) -> (Vector3<f64>, Vector3<f64>) {
let normal = Vector3::unit_z();
let sigma = current_direction.cross(&normal);
let sigma_mag = sigma.magnitude();
let sigma = if sigma_mag > 1e-30 {
sigma * (1.0 / sigma_mag)
} else {
Vector3::zero()
};
let prefactor = self.torque_prefactor() * j_charge;
let m_cross_sigma = m.cross(&sigma);
let tau_dl = m.cross(&m_cross_sigma) * (prefactor * self.damping_like_efficiency);
let tau_fl = m_cross_sigma * (prefactor * self.field_like_efficiency);
(tau_dl, tau_fl)
}
#[inline]
pub fn damping_like_field(
&self,
j_charge: f64,
m: Vector3<f64>,
current_direction: Vector3<f64>,
) -> Vector3<f64> {
let normal = Vector3::unit_z();
let sigma = current_direction.cross(&normal);
let sigma_mag = sigma.magnitude();
let sigma = if sigma_mag > 1e-30 {
sigma * (1.0 / sigma_mag)
} else {
Vector3::zero()
};
let hbar_over_2e = HBAR / (2.0 * E_CHARGE);
let eta_ls = self.converter.conversion_efficiency;
let theta_oh = self.material.orbital_hall_angle;
let h_prefactor =
hbar_over_2e * j_charge * eta_ls * theta_oh * self.damping_like_efficiency
/ (self.ms * self.thickness_fm);
let m_cross_sigma = m.cross(&sigma);
m.cross(&m_cross_sigma) * h_prefactor
}
#[inline]
pub fn field_like_field(
&self,
j_charge: f64,
_m: Vector3<f64>,
current_direction: Vector3<f64>,
) -> Vector3<f64> {
let normal = Vector3::unit_z();
let sigma = current_direction.cross(&normal);
let sigma_mag = sigma.magnitude();
let sigma = if sigma_mag > 1e-30 {
sigma * (1.0 / sigma_mag)
} else {
Vector3::zero()
};
let hbar_over_2e = HBAR / (2.0 * E_CHARGE);
let eta_ls = self.converter.conversion_efficiency;
let theta_oh = self.material.orbital_hall_angle;
let h_prefactor = hbar_over_2e * j_charge * eta_ls * theta_oh * self.field_like_efficiency
/ (self.ms * self.thickness_fm);
sigma * h_prefactor
}
pub fn ot_to_sot_ratio(
&self,
j_charge: f64,
m: Vector3<f64>,
current_direction: Vector3<f64>,
) -> f64 {
let (tau_dl_ot, _) = self.compute_torques(m, j_charge, current_direction);
let theta_sh_pt: f64 = 0.07;
let transparency_pt: f64 = 0.5;
let thickness_pt: f64 = 5.0e-9;
let lambda_sd_pt: f64 = 1.5e-9;
let tanh_factor = (thickness_pt / lambda_sd_pt).tanh();
let theta_eff_pt = theta_sh_pt * tanh_factor * transparency_pt;
let hbar_over_2e = HBAR / (2.0 * E_CHARGE);
let sot_prefactor =
hbar_over_2e * j_charge * theta_eff_pt * GAMMA / (self.ms * self.thickness_fm);
let normal = Vector3::unit_z();
let sigma = current_direction.cross(&normal);
let sigma_mag = sigma.magnitude();
let sigma = if sigma_mag > 1e-30 {
sigma * (1.0 / sigma_mag)
} else {
return 0.0;
};
let m_cross_sigma = m.cross(&sigma);
let tau_dl_sot = m.cross(&m_cross_sigma) * sot_prefactor;
let sot_mag = tau_dl_sot.magnitude();
if sot_mag > 1e-30 {
tau_dl_ot.magnitude() / sot_mag
} else {
0.0
}
}
pub fn critical_switching_current(&self, h_k: f64) -> f64 {
let eta_ls = self.converter.conversion_efficiency;
let theta_oh = self.material.orbital_hall_angle;
let effective_angle = (eta_ls * theta_oh * self.damping_like_efficiency).abs();
if effective_angle < 1e-30 {
return f64::MAX;
}
let h_eff = h_k + self.ms;
(2.0 * E_CHARGE / HBAR) * (self.ms * self.thickness_fm / effective_angle) * h_eff
}
pub fn switching_efficiency_vs_sot(&self, h_k: f64) -> f64 {
let j_c_ot = self.critical_switching_current(h_k);
let theta_sh_pt: f64 = 0.07;
let transparency_pt: f64 = 0.5;
let thickness_pt: f64 = 5.0e-9;
let lambda_sd_pt: f64 = 1.5e-9;
let tanh_factor = (thickness_pt / lambda_sd_pt).tanh();
let theta_eff_pt = (theta_sh_pt * tanh_factor * transparency_pt).abs();
let h_eff = h_k + self.ms;
let j_c_sot =
(2.0 * E_CHARGE / HBAR) * (self.ms * self.thickness_fm / theta_eff_pt) * h_eff;
if j_c_ot > 1e-30 {
j_c_sot / j_c_ot
} else {
0.0
}
}
}
impl fmt::Display for OrbitalTorque {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"OrbitalTorque({}/{}, M_s={:.2e} A/m, d={:.1} nm, DL={:.2}, FL={:.2})",
self.material.name,
self.converter.interface_type,
self.ms,
self.thickness_fm * 1e9,
self.damping_like_efficiency,
self.field_like_efficiency
)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrbitalRashba {
pub alpha_or: f64,
pub interface: &'static str,
pub relaxation_time: f64,
pub l_effective: f64,
}
impl OrbitalRashba {
pub fn new(
alpha_or: f64,
interface: &'static str,
relaxation_time: f64,
l_effective: f64,
) -> Result<Self> {
if relaxation_time <= 0.0 {
return Err(Error::InvalidParameter {
param: "relaxation_time".to_string(),
reason: "must be positive".to_string(),
});
}
if l_effective < 0.0 {
return Err(Error::InvalidParameter {
param: "l_effective".to_string(),
reason: "must be non-negative".to_string(),
});
}
Ok(Self {
alpha_or,
interface,
relaxation_time,
l_effective,
})
}
pub fn cu_oxide() -> Self {
Self {
alpha_or: 2.0,
interface: "Cu/oxide",
relaxation_time: 1.0e-14,
l_effective: 2.0,
}
}
pub fn ag_bi() -> Self {
Self {
alpha_or: 3.5,
interface: "Ag/Bi",
relaxation_time: 0.8e-14,
l_effective: 1.0,
}
}
pub fn al_oxide() -> Self {
Self {
alpha_or: 1.0,
interface: "Al/oxide",
relaxation_time: 0.5e-14,
l_effective: 1.0,
}
}
pub fn orbital_edelstein_coefficient(&self) -> f64 {
let alpha_si = self.alpha_or * E_CHARGE * 1e-10;
alpha_si * self.relaxation_time / (E_CHARGE * HBAR)
}
pub fn orbital_accumulation(
&self,
j_charge: f64,
current_direction: Vector3<f64>,
) -> Vector3<f64> {
let coeff = self.orbital_edelstein_coefficient();
let z_hat = Vector3::unit_z();
let j_vec = current_direction * j_charge;
j_vec.cross(&z_hat) * coeff
}
pub fn orbital_splitting(&self, k_magnitude: f64) -> f64 {
let alpha_si = self.alpha_or * E_CHARGE * 1e-10;
alpha_si * k_magnitude
}
pub fn splitting_at_fermi(&self, k_fermi: f64) -> f64 {
let splitting_j = self.orbital_splitting(k_fermi);
splitting_j / E_CHARGE
}
pub fn orbital_to_spin_rashba_ratio(&self, alpha_spin_rashba: f64) -> f64 {
if alpha_spin_rashba.abs() > 1e-30 {
self.alpha_or / alpha_spin_rashba
} else {
f64::MAX
}
}
}
impl fmt::Display for OrbitalRashba {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"OrbitalRashba({}: alpha_OR={:.2} eV*A, tau={:.2e} s, L_eff={:.1})",
self.interface, self.alpha_or, self.relaxation_time, self.l_effective
)
}
}
pub fn cr_pt_cofeb_system() -> OrbitalTorque {
OrbitalTorque::cr_pt_cofeb()
}
pub fn ti_pt_cofeb_system() -> OrbitalTorque {
OrbitalTorque::ti_pt_cofeb()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_orbital_torque_magnitude() {
let ot = OrbitalTorque::cr_pt_cofeb();
let m = Vector3::new(0.0, 0.0, 1.0); let j = 1.0e11; let dir = Vector3::new(1.0, 0.0, 0.0);
let (tau_dl, tau_fl) = ot.compute_torques(m, j, dir);
assert!(tau_dl.magnitude() > 0.0, "DL torque should be non-zero");
assert!(tau_fl.magnitude() > 0.0, "FL torque should be non-zero");
assert!(
tau_dl.magnitude() > tau_fl.magnitude(),
"DL torque ({:.2e}) should exceed FL torque ({:.2e})",
tau_dl.magnitude(),
tau_fl.magnitude()
);
}
#[test]
fn test_orbital_torque_vs_sot_comparison() {
let ot = OrbitalTorque::cr_pt_cofeb();
let m = Vector3::new(0.1, 0.0, 1.0).normalize();
let j = 1.0e11;
let dir = Vector3::new(1.0, 0.0, 0.0);
let ratio = ot.ot_to_sot_ratio(j, m, dir);
assert!(ratio > 0.0, "OT/SOT ratio should be positive: {}", ratio);
assert!(ratio.is_finite(), "OT/SOT ratio should be finite");
}
#[test]
fn test_orbital_torque_perpendicular_to_m() {
let ot = OrbitalTorque::cr_pt_cofeb();
let m = Vector3::new(0.1, 0.2, 0.97).normalize();
let j = 1.0e11;
let dir = Vector3::new(1.0, 0.0, 0.0);
let (tau_dl, _) = ot.compute_torques(m, j, dir);
let dot_m = tau_dl.dot(&m);
assert!(
dot_m.abs() < 1e-6,
"DL torque should be perpendicular to m, dot = {}",
dot_m
);
}
#[test]
fn test_light_metal_enhancement() {
let cr_ot = OrbitalTorque::cr_pt_cofeb();
let cu_ot = OrbitalTorque::cu_pt_cofeb();
let m = Vector3::new(0.0, 0.0, 1.0);
let j = 1.0e11;
let dir = Vector3::new(1.0, 0.0, 0.0);
let (tau_cr, _) = cr_ot.compute_torques(m, j, dir);
let (tau_cu, _) = cu_ot.compute_torques(m, j, dir);
assert!(
tau_cr.magnitude() > tau_cu.magnitude(),
"Cr should produce larger OT than Cu: Cr={:.2e}, Cu={:.2e}",
tau_cr.magnitude(),
tau_cu.magnitude()
);
}
#[test]
fn test_critical_switching_current() {
let ot = OrbitalTorque::cr_pt_cofeb();
let h_k = 5.0e4;
let j_c = ot.critical_switching_current(h_k);
assert!(j_c > 0.0, "Critical current should be positive");
assert!(j_c.is_finite(), "Critical current should be finite");
assert!(
j_c > 1.0e10,
"Critical current should be > 10^10 A/m^2, got {:.2e}",
j_c
);
}
#[test]
fn test_switching_efficiency_comparison() {
let ot = OrbitalTorque::cr_pt_cofeb();
let h_k = 5.0e4;
let ratio = ot.switching_efficiency_vs_sot(h_k);
assert!(ratio > 0.0, "Switching efficiency ratio should be positive");
assert!(
ratio.is_finite(),
"Switching efficiency ratio should be finite"
);
}
#[test]
fn test_damping_like_field_magnitude() {
let ot = OrbitalTorque::cr_pt_cofeb();
let m = Vector3::new(0.0, 0.1, 1.0).normalize();
let j = 1.0e11;
let dir = Vector3::new(1.0, 0.0, 0.0);
let h_dl = ot.damping_like_field(j, m, dir);
assert!(h_dl.magnitude() > 0.0, "DL field should be non-zero");
assert!(h_dl.magnitude().is_finite(), "DL field should be finite");
}
#[test]
fn test_orbital_rashba_splitting() {
let or = OrbitalRashba::cu_oxide();
let k_fermi = 1.36e10;
let splitting_ev = or.splitting_at_fermi(k_fermi);
assert!(splitting_ev > 0.0, "Splitting should be positive");
assert!(
splitting_ev < 10.0,
"Splitting should be < 10 eV for physical reasonableness"
);
let expected = 2.0 * 1.36e10 * 1e-10;
let rel_err = ((splitting_ev - expected) / expected).abs();
assert!(
rel_err < 1e-6,
"Splitting mismatch: got {}, expected {}",
splitting_ev,
expected
);
}
#[test]
fn test_orbital_rashba_vs_spin_rashba() {
let or = OrbitalRashba::cu_oxide();
let alpha_sr = 0.05;
let ratio = or.orbital_to_spin_rashba_ratio(alpha_sr);
assert!(
ratio > 10.0,
"Orbital Rashba should be >> spin Rashba: ratio = {}",
ratio
);
}
#[test]
fn test_orbital_edelstein_accumulation() {
let or = OrbitalRashba::cu_oxide();
let j = 1.0e11;
let dir = Vector3::new(1.0, 0.0, 0.0);
let accumulation = or.orbital_accumulation(j, dir);
assert!(
accumulation.y < 0.0,
"Accumulation should be along -y for current along x"
);
assert!(
accumulation.x.abs() < 1e-30,
"Accumulation x should be zero"
);
assert!(
accumulation.z.abs() < 1e-30,
"Accumulation z should be zero"
);
}
#[test]
fn test_orbital_torque_parameter_validation() {
let valid = OrbitalTorque::new(
OrbitalHallMaterial::chromium(),
OrbitalToSpinConverter::cr_pt(),
1.0e6,
2.0e-9,
0.8,
0.2,
);
assert!(valid.is_ok(), "Valid parameters should be accepted");
let bad_ms = OrbitalTorque::new(
OrbitalHallMaterial::chromium(),
OrbitalToSpinConverter::cr_pt(),
0.0,
2.0e-9,
0.8,
0.2,
);
assert!(bad_ms.is_err(), "Zero Ms should be rejected");
let bad_d = OrbitalTorque::new(
OrbitalHallMaterial::chromium(),
OrbitalToSpinConverter::cr_pt(),
1.0e6,
-1.0e-9,
0.8,
0.2,
);
assert!(bad_d.is_err(), "Negative thickness should be rejected");
let bad_dl = OrbitalTorque::new(
OrbitalHallMaterial::chromium(),
OrbitalToSpinConverter::cr_pt(),
1.0e6,
2.0e-9,
1.5,
0.2,
);
assert!(bad_dl.is_err(), "DL efficiency > 1 should be rejected");
}
#[test]
fn test_orbital_rashba_parameter_validation() {
let valid = OrbitalRashba::new(2.0, "test", 1.0e-14, 2.0);
assert!(valid.is_ok(), "Valid Rashba parameters should be accepted");
let bad_tau = OrbitalRashba::new(2.0, "test", 0.0, 2.0);
assert!(bad_tau.is_err(), "Zero relaxation time should be rejected");
let bad_l = OrbitalRashba::new(2.0, "test", 1.0e-14, -1.0);
assert!(bad_l.is_err(), "Negative L_eff should be rejected");
}
#[test]
fn test_current_direction_dependence_torque() {
let ot = OrbitalTorque::cr_pt_cofeb();
let m = Vector3::new(0.0, 0.0, 1.0);
let j = 1.0e11;
let dir_x = Vector3::new(1.0, 0.0, 0.0);
let (tau_x, _) = ot.compute_torques(m, j, dir_x);
let dir_y = Vector3::new(0.0, 1.0, 0.0);
let (tau_y, _) = ot.compute_torques(m, j, dir_y);
let rel_diff = ((tau_x.magnitude() - tau_y.magnitude()) / tau_x.magnitude()).abs();
assert!(
rel_diff < 1e-6,
"Torque magnitude should be same for x and y current: x={:.2e}, y={:.2e}",
tau_x.magnitude(),
tau_y.magnitude()
);
let dot = tau_x.dot(&tau_y);
assert!(
dot.abs() / (tau_x.magnitude() * tau_y.magnitude()) < 1e-6,
"Torques for x and y current should be orthogonal"
);
let dir_z = Vector3::new(0.0, 0.0, 1.0);
let (tau_z, _) = ot.compute_torques(m, j, dir_z);
assert!(
tau_z.magnitude() < 1e-20,
"Current along z should give zero torque"
);
}
#[test]
fn test_display_formats() {
let ot = OrbitalTorque::cr_pt_cofeb();
let display = format!("{}", ot);
assert!(
display.contains("Cr"),
"Display should contain material name"
);
let or = OrbitalRashba::cu_oxide();
let display = format!("{}", or);
assert!(
display.contains("Cu/oxide"),
"Display should contain interface"
);
}
}