use std::f64::consts::PI;
use std::fmt;
use crate::error::{Error, Result};
pub const PHI_0: f64 = 4.136e-15;
#[derive(Debug, Clone)]
pub struct TopologicalHall {
pub name: String,
pub hall_coefficient: f64,
pub anomalous_hall_coeff: f64,
pub resistivity: f64,
pub magnetization: f64,
pub skyrmion_diameter: f64,
}
impl Default for TopologicalHall {
fn default() -> Self {
Self::mnsi()
}
}
impl TopologicalHall {
pub fn mnsi() -> Self {
Self {
name: "MnSi".to_string(),
hall_coefficient: 1.5e-10, anomalous_hall_coeff: 2.0e-8, resistivity: 1.0e-5, magnetization: 1.8e5, skyrmion_diameter: 18.0, }
}
pub fn mnge() -> Self {
Self {
name: "MnGe".to_string(),
hall_coefficient: 2.0e-10,
anomalous_hall_coeff: 3.0e-8,
resistivity: 2.0e-5,
magnetization: 1.5e5,
skyrmion_diameter: 3.0, }
}
pub fn fege() -> Self {
Self {
name: "FeGe".to_string(),
hall_coefficient: 1.8e-10,
anomalous_hall_coeff: 2.5e-8,
resistivity: 1.2e-5,
magnetization: 3.84e5, skyrmion_diameter: 70.0, }
}
pub fn co2mnga() -> Self {
Self {
name: "Co₂MnGa".to_string(),
hall_coefficient: 1.0e-10,
anomalous_hall_coeff: 1.5e-8,
resistivity: 5.0e-6,
magnetization: 6.0e5,
skyrmion_diameter: 50.0,
}
}
pub fn topological_hall_resistivity(
&self,
skyrmion_density: f64,
topological_charge: f64,
) -> f64 {
let alpha = self.hall_coefficient * 1.0e-4; alpha * skyrmion_density * topological_charge
}
pub fn topological_hall_angle(&self, skyrmion_density: f64, topological_charge: f64) -> f64 {
let rho_the = self.topological_hall_resistivity(skyrmion_density, topological_charge);
(rho_the / self.resistivity).atan()
}
pub fn emergent_magnetic_field(&self, topological_charge: f64) -> f64 {
let phi_0 = 4.136e-15; let radius = self.skyrmion_diameter * 0.5 * 1e-9; let area = PI * radius * radius;
(phi_0 * topological_charge.abs() / area) * 1e-4 }
pub fn estimate_skyrmion_density(&self, measured_rho_the: f64, assumed_charge: f64) -> f64 {
let alpha = self.hall_coefficient * 1.0e-4;
measured_rho_the / (alpha * assumed_charge)
}
pub fn hall_voltage(
&self,
current: f64,
thickness: f64,
skyrmion_density: f64,
topological_charge: f64,
) -> f64 {
let rho_the = self.topological_hall_resistivity(skyrmion_density, topological_charge);
let thickness_cm = thickness * 1e-7; (rho_the / thickness_cm) * current
}
pub fn is_the_measurable(&self, skyrmion_density: f64) -> bool {
let rho_the = self.topological_hall_resistivity(skyrmion_density, 1.0);
rho_the / self.resistivity > 0.01
}
pub fn with_skyrmion_diameter(mut self, diameter: f64) -> Self {
self.skyrmion_diameter = diameter;
self
}
pub fn with_hall_coefficient(mut self, r0: f64) -> Self {
self.hall_coefficient = r0;
self
}
pub fn emergent_field_from_solid_angle(solid_angle: f64, area: f64) -> Result<f64> {
if !area.is_finite() || area <= 0.0 {
return Err(Error::InvalidParameter {
param: "area".to_string(),
reason: "area must be finite and strictly positive".to_string(),
});
}
if !solid_angle.is_finite() {
return Err(Error::InvalidParameter {
param: "solid_angle".to_string(),
reason: "solid angle must be finite".to_string(),
});
}
Ok(PHI_0 * solid_angle / (4.0 * PI * area))
}
pub fn hall_resistivity_from_charge_density(&self, signed_charge_density: f64) -> f64 {
self.topological_hall_resistivity(
signed_charge_density.abs(),
signed_charge_density.signum(),
)
}
}
impl fmt::Display for TopologicalHall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: R₀={:.2e} Ω·cm/T, d_sk={:.1} nm, M_s={:.2e} A/m",
self.name, self.hall_coefficient, self.skyrmion_diameter, self.magnetization
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mnsi() {
let mnsi = TopologicalHall::mnsi();
assert_eq!(mnsi.name, "MnSi");
assert!(mnsi.skyrmion_diameter > 15.0);
assert!(mnsi.skyrmion_diameter < 20.0);
}
#[test]
fn test_mnge_small_skyrmions() {
let mnge = TopologicalHall::mnge();
assert!(mnge.skyrmion_diameter < 5.0);
}
#[test]
fn test_topological_hall_resistivity() {
let mnsi = TopologicalHall::mnsi();
let n_sk = 1.0e14; let q = 1.0;
let rho_the = mnsi.topological_hall_resistivity(n_sk, q);
assert!(rho_the > 0.0);
assert!(rho_the.is_finite());
}
#[test]
fn test_topological_hall_angle() {
let mnsi = TopologicalHall::mnsi();
let n_sk = 5.0e14;
let theta = mnsi.topological_hall_angle(n_sk, 1.0);
assert!(theta > 0.0);
assert!(theta.is_finite());
}
#[test]
fn test_emergent_magnetic_field() {
let mnsi = TopologicalHall::mnsi();
let b_eff = mnsi.emergent_magnetic_field(1.0);
assert!(b_eff > 0.0);
assert!(b_eff.is_finite());
}
#[test]
fn test_estimate_skyrmion_density() {
let mnsi = TopologicalHall::mnsi();
let n_sk_actual = 1.0e14;
let rho_the = mnsi.topological_hall_resistivity(n_sk_actual, 1.0);
let n_sk_estimated = mnsi.estimate_skyrmion_density(rho_the, 1.0);
assert!((n_sk_estimated - n_sk_actual).abs() / n_sk_actual < 0.01);
}
#[test]
fn test_hall_voltage() {
let mnsi = TopologicalHall::mnsi();
let current = 1.0e-6; let thickness = 100.0; let n_sk = 1.0e14;
let v_h = mnsi.hall_voltage(current, thickness, n_sk, 1.0);
assert!(v_h > 0.0);
assert!(v_h.is_finite());
}
#[test]
fn test_chirality_sign() {
let mnsi = TopologicalHall::mnsi();
let n_sk = 1.0e14;
let rho_plus = mnsi.topological_hall_resistivity(n_sk, 1.0);
let rho_minus = mnsi.topological_hall_resistivity(n_sk, -1.0);
assert!((rho_plus + rho_minus).abs() < 1e-15);
}
#[test]
fn test_the_measurability() {
let mnsi = TopologicalHall::mnsi();
let high_density = 1.0e15; let low_density = 1.0e6;
assert!(mnsi.is_the_measurable(high_density));
assert!(!mnsi.is_the_measurable(low_density));
}
#[test]
fn test_density_dependence() {
let mnsi = TopologicalHall::mnsi();
let n_sk1 = 1.0e14;
let n_sk2 = 2.0e14;
let rho1 = mnsi.topological_hall_resistivity(n_sk1, 1.0);
let rho2 = mnsi.topological_hall_resistivity(n_sk2, 1.0);
assert!((rho2 / rho1 - 2.0).abs() < 0.01);
}
#[test]
fn test_builder_pattern() {
let custom = TopologicalHall::mnsi()
.with_skyrmion_diameter(100.0)
.with_hall_coefficient(5.0e-10);
assert_eq!(custom.skyrmion_diameter, 100.0);
assert_eq!(custom.hall_coefficient, 5.0e-10);
}
#[test]
fn test_emergent_field_from_solid_angle_matches_legacy_up_to_documented_prefactor() {
let mnsi = TopologicalHall::mnsi();
let q = 1.0;
let radius = mnsi.skyrmion_diameter * 0.5 * 1e-9; let area = PI * radius * radius;
let omega_full_skyrmion = 4.0 * PI * q;
let legacy = mnsi.emergent_magnetic_field(q);
let mine = TopologicalHall::emergent_field_from_solid_angle(omega_full_skyrmion, area)
.expect("area must be valid");
assert!(
(legacy - mine * 1e-4).abs() / legacy.abs() < 1e-9,
"legacy = {:.6e}, mine*1e-4 = {:.6e}",
legacy,
mine * 1e-4
);
}
#[test]
fn test_emergent_field_from_solid_angle_rejects_invalid_inputs() {
assert!(TopologicalHall::emergent_field_from_solid_angle(1.0, 0.0).is_err());
assert!(TopologicalHall::emergent_field_from_solid_angle(1.0, -1.0).is_err());
assert!(TopologicalHall::emergent_field_from_solid_angle(1.0, f64::INFINITY).is_err());
assert!(TopologicalHall::emergent_field_from_solid_angle(f64::NAN, 1.0).is_err());
}
#[test]
fn test_hall_resistivity_from_charge_density_matches_direct_formula() {
let mnsi = TopologicalHall::mnsi();
let n_sk = 2.5e14;
let direct = mnsi.topological_hall_resistivity(n_sk, 1.0);
let from_density = mnsi.hall_resistivity_from_charge_density(n_sk);
assert!((direct - from_density).abs() < 1e-30);
let direct_neg = mnsi.topological_hall_resistivity(n_sk, -1.0);
let from_density_neg = mnsi.hall_resistivity_from_charge_density(-n_sk);
assert!((direct_neg - from_density_neg).abs() < 1e-30);
}
#[test]
fn test_hall_resistivity_from_charge_density_zero_is_zero() {
let mnsi = TopologicalHall::mnsi();
assert_eq!(mnsi.hall_resistivity_from_charge_density(0.0), 0.0);
}
}