use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::vector3::Vector3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MagneticOrdering {
Ferromagnetic,
Antiferromagnetic,
Ferrimagnetic,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Magnetic2D {
pub name: String,
pub ordering: MagneticOrdering,
pub critical_temperature: f64,
pub magnetic_moment: f64,
pub anisotropy_energy: f64,
pub easy_axis: Vector3<f64>,
pub exchange_j: f64,
pub spin_orbit_coupling: f64,
pub num_layers: usize,
pub lattice_constant: f64,
}
impl Default for Magnetic2D {
fn default() -> Self {
Self::cri3(1)
}
}
impl Magnetic2D {
pub fn cri3(num_layers: usize) -> Self {
let tc = match num_layers {
1 => 45.0, 2 => 40.0, _ => 61.0, };
Self {
name: "CrI₃".to_string(),
ordering: MagneticOrdering::Ferromagnetic,
critical_temperature: tc,
magnetic_moment: 3.0, anisotropy_energy: 0.25, easy_axis: Vector3::new(0.0, 0.0, 1.0), exchange_j: 2.5, spin_orbit_coupling: 10.0, num_layers,
lattice_constant: 6.867, }
}
pub fn crbr3(num_layers: usize) -> Self {
let tc = if num_layers == 1 { 34.0 } else { 37.5 };
Self {
name: "CrBr₃".to_string(),
ordering: MagneticOrdering::Ferromagnetic,
critical_temperature: tc,
magnetic_moment: 3.0,
anisotropy_energy: 0.18,
easy_axis: Vector3::new(0.0, 0.0, 1.0),
exchange_j: 1.8,
spin_orbit_coupling: 8.0,
num_layers,
lattice_constant: 6.294,
}
}
pub fn fe3gete2(num_layers: usize) -> Self {
let tc = 220.0 + (num_layers as f64 - 1.0) * 20.0;
Self {
name: "Fe₃GeTe₂".to_string(),
ordering: MagneticOrdering::Ferromagnetic,
critical_temperature: tc.min(330.0), magnetic_moment: 1.5, anisotropy_energy: 0.8, easy_axis: Vector3::new(0.0, 0.0, 1.0),
exchange_j: 15.0, spin_orbit_coupling: 20.0, num_layers,
lattice_constant: 3.99,
}
}
pub fn mnbi2te4(num_layers: usize) -> Self {
let tn = 24.0;
Self {
name: "MnBi₂Te₄".to_string(),
ordering: MagneticOrdering::Antiferromagnetic,
critical_temperature: tn,
magnetic_moment: 5.0, anisotropy_energy: 0.35,
easy_axis: Vector3::new(0.0, 0.0, 1.0),
exchange_j: -2.0, spin_orbit_coupling: 50.0, num_layers,
lattice_constant: 4.33,
}
}
pub fn crcl3(num_layers: usize) -> Self {
let tc = 17.0;
Self {
name: "CrCl₃".to_string(),
ordering: MagneticOrdering::Ferromagnetic,
critical_temperature: tc,
magnetic_moment: 3.0,
anisotropy_energy: 0.12,
easy_axis: Vector3::new(0.0, 0.0, 1.0),
exchange_j: 1.2,
spin_orbit_coupling: 5.0,
num_layers,
lattice_constant: 5.954,
}
}
pub fn vse2(num_layers: usize) -> Self {
let tc = if num_layers == 1 { 300.0 } else { 470.0 };
Self {
name: "VSe₂".to_string(),
ordering: MagneticOrdering::Ferromagnetic,
critical_temperature: tc,
magnetic_moment: 0.6, anisotropy_energy: 0.05,
easy_axis: Vector3::new(0.0, 0.0, 1.0),
exchange_j: 25.0, spin_orbit_coupling: 15.0,
num_layers,
lattice_constant: 3.356,
}
}
pub fn magnetization_at_temperature(&self, temperature: f64) -> f64 {
if temperature >= self.critical_temperature {
0.0
} else {
let reduced_temp = temperature / self.critical_temperature;
(1.0 - reduced_temp).powf(0.5) }
}
pub fn is_room_temperature_ferromagnet(&self) -> bool {
self.ordering == MagneticOrdering::Ferromagnetic && self.critical_temperature >= 300.0
}
#[allow(dead_code)]
pub fn coercive_field(&self) -> f64 {
let mu_b = 9.274e-24; let k_energy_si = self.anisotropy_energy * 1.602e-22; let m_moment = self.magnetic_moment * mu_b;
2.0 * k_energy_si / m_moment }
pub fn is_suitable_for_spintronics(&self) -> bool {
self.critical_temperature > 100.0 && self.anisotropy_energy > 0.1 && self.magnetic_moment > 0.5 }
pub fn with_layers(mut self, num_layers: usize) -> Self {
self.num_layers = num_layers;
self
}
pub fn with_tc(mut self, tc: f64) -> Self {
self.critical_temperature = tc;
self
}
}
impl fmt::Display for MagneticOrdering {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MagneticOrdering::Ferromagnetic => write!(f, "FM"),
MagneticOrdering::Antiferromagnetic => write!(f, "AFM"),
MagneticOrdering::Ferrimagnetic => write!(f, "FiM"),
}
}
}
impl fmt::Display for Magnetic2D {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} [{}]: {} layer(s), T_c={:.0} K, μ={:.1} μ_B",
self.name,
self.ordering,
self.num_layers,
self.critical_temperature,
self.magnetic_moment
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cri3_monolayer() {
let cri3 = Magnetic2D::cri3(1);
assert_eq!(cri3.ordering, MagneticOrdering::Ferromagnetic);
assert_eq!(cri3.num_layers, 1);
assert!(cri3.critical_temperature > 40.0);
assert!(cri3.critical_temperature < 50.0);
}
#[test]
fn test_cri3_layer_dependence() {
let mono = Magnetic2D::cri3(1);
let bulk = Magnetic2D::cri3(5);
assert!(bulk.critical_temperature > mono.critical_temperature);
}
#[test]
fn test_fe3gete2_high_tc() {
let fe3gete2 = Magnetic2D::fe3gete2(5); assert!(fe3gete2.critical_temperature > 200.0);
assert!(fe3gete2.is_room_temperature_ferromagnet());
}
#[test]
fn test_mnbi2te4_antiferromagnet() {
let mnbi2te4 = Magnetic2D::mnbi2te4(1);
assert_eq!(mnbi2te4.ordering, MagneticOrdering::Antiferromagnetic);
assert!(mnbi2te4.spin_orbit_coupling > 40.0); }
#[test]
fn test_vse2_room_temperature() {
let vse2_mono = Magnetic2D::vse2(1);
assert!(vse2_mono.is_room_temperature_ferromagnet());
}
#[test]
fn test_magnetization_temperature_dependence() {
let cri3 = Magnetic2D::cri3(1);
let m_zero = cri3.magnetization_at_temperature(0.0);
let m_half = cri3.magnetization_at_temperature(cri3.critical_temperature * 0.5);
let m_above_tc = cri3.magnetization_at_temperature(cri3.critical_temperature + 10.0);
assert!((m_zero - 1.0).abs() < 0.01);
assert!(m_half > 0.0 && m_half < 1.0);
assert_eq!(m_above_tc, 0.0);
}
#[test]
fn test_room_temperature_classification() {
let cri3 = Magnetic2D::cri3(1);
let fe3gete2 = Magnetic2D::fe3gete2(5);
assert!(!cri3.is_room_temperature_ferromagnet());
assert!(fe3gete2.is_room_temperature_ferromagnet());
}
#[test]
fn test_spintronics_suitability() {
let fe3gete2 = Magnetic2D::fe3gete2(3);
let weak_mag = Magnetic2D::cri3(1).with_tc(50.0);
assert!(fe3gete2.is_suitable_for_spintronics());
assert!(!weak_mag.is_suitable_for_spintronics());
}
#[test]
fn test_builder_pattern() {
let custom = Magnetic2D::cri3(1).with_layers(3).with_tc(100.0);
assert_eq!(custom.num_layers, 3);
assert_eq!(custom.critical_temperature, 100.0);
}
#[test]
fn test_all_chromium_halides() {
let cri3 = Magnetic2D::cri3(1);
let crbr3 = Magnetic2D::crbr3(1);
let crcl3 = Magnetic2D::crcl3(1);
assert_eq!(cri3.magnetic_moment, 3.0);
assert_eq!(crbr3.magnetic_moment, 3.0);
assert_eq!(crcl3.magnetic_moment, 3.0);
assert!(cri3.critical_temperature > crcl3.critical_temperature);
}
#[test]
fn test_easy_axis_orientation() {
let cri3 = Magnetic2D::cri3(1);
assert!((cri3.easy_axis.z - 1.0).abs() < 1e-10);
assert!(cri3.easy_axis.x.abs() < 1e-10);
assert!(cri3.easy_axis.y.abs() < 1e-10);
}
}