use std::f64::consts::PI;
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 Helicity {
Neel,
Bloch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Chirality {
Clockwise,
CounterClockwise,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Skyrmion {
pub center: (f64, f64),
pub radius: f64,
pub helicity: Helicity,
pub chirality: Chirality,
pub topological_charge: i32,
}
impl Default for Skyrmion {
fn default() -> Self {
Self::new(
(0.0, 0.0),
50.0e-9,
Helicity::Neel,
Chirality::CounterClockwise,
)
}
}
impl Skyrmion {
pub fn new(center: (f64, f64), radius: f64, helicity: Helicity, chirality: Chirality) -> Self {
let topological_charge = match chirality {
Chirality::CounterClockwise => -1,
Chirality::Clockwise => 1,
};
Self {
center,
radius,
helicity,
chirality,
topological_charge,
}
}
pub fn magnetization_at(&self, x: f64, y: f64, wall_width: f64) -> Vector3<f64> {
let dx = x - self.center.0;
let dy = y - self.center.1;
let r = (dx * dx + dy * dy).sqrt();
let phi = dy.atan2(dx);
let mz = if wall_width > 0.0 {
((r - self.radius) / wall_width).tanh()
} else if r < self.radius {
-1.0 } else {
1.0 };
let m_inplane = (1.0 - mz * mz).sqrt().max(0.0);
let theta = match self.helicity {
Helicity::Neel => phi, Helicity::Bloch => phi + PI / 2.0, };
let sign = match self.chirality {
Chirality::CounterClockwise => 1.0,
Chirality::Clockwise => -1.0,
};
Vector3::new(
m_inplane * (sign * theta).cos(),
m_inplane * (sign * theta).sin(),
mz,
)
}
pub fn topological_charge_density(&self, x: f64, y: f64, wall_width: f64, dx: f64) -> f64 {
let m = self.magnetization_at(x, y, wall_width);
let m_xp = self.magnetization_at(x + dx, y, wall_width);
let m_yp = self.magnetization_at(x, y + dx, wall_width);
let dm_dx = (m_xp - m) * (1.0 / dx);
let dm_dy = (m_yp - m) * (1.0 / dx);
let cross = dm_dx.cross(&dm_dy);
m.dot(&cross) / (4.0 * PI)
}
pub fn with_center(mut self, center: (f64, f64)) -> Self {
self.center = center;
self
}
pub fn with_radius(mut self, radius: f64) -> Self {
self.radius = radius;
self
}
pub fn with_helicity(mut self, helicity: Helicity) -> Self {
self.helicity = helicity;
self
}
pub fn with_chirality(mut self, chirality: Chirality) -> Self {
self.chirality = chirality;
self.topological_charge = match chirality {
Chirality::CounterClockwise => -1,
Chirality::Clockwise => 1,
};
self
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SkyrmionLattice {
pub skyrmions: Vec<Skyrmion>,
pub lattice_constant: f64,
pub lattice_type: LatticeType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LatticeType {
Square,
Hexagonal,
}
impl SkyrmionLattice {
pub fn square(
nx: usize,
ny: usize,
lattice_constant: f64,
skyrmion_radius: f64,
helicity: Helicity,
chirality: Chirality,
) -> Self {
let mut skyrmions = Vec::new();
for i in 0..nx {
for j in 0..ny {
let x = i as f64 * lattice_constant;
let y = j as f64 * lattice_constant;
skyrmions.push(Skyrmion::new((x, y), skyrmion_radius, helicity, chirality));
}
}
Self {
skyrmions,
lattice_constant,
lattice_type: LatticeType::Square,
}
}
pub fn hexagonal(
nx: usize,
ny: usize,
lattice_constant: f64,
skyrmion_radius: f64,
helicity: Helicity,
chirality: Chirality,
) -> Self {
let mut skyrmions = Vec::new();
let dy = lattice_constant * (3.0_f64).sqrt() / 2.0;
for i in 0..nx {
for j in 0..ny {
let x = i as f64 * lattice_constant + (j % 2) as f64 * lattice_constant / 2.0;
let y = j as f64 * dy;
skyrmions.push(Skyrmion::new((x, y), skyrmion_radius, helicity, chirality));
}
}
Self {
skyrmions,
lattice_constant,
lattice_type: LatticeType::Hexagonal,
}
}
pub fn total_topological_charge(&self) -> i32 {
self.skyrmions.iter().map(|s| s.topological_charge).sum()
}
}
impl fmt::Display for Helicity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Helicity::Neel => write!(f, "Néel"),
Helicity::Bloch => write!(f, "Bloch"),
}
}
}
impl fmt::Display for Chirality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Chirality::Clockwise => write!(f, "CW"),
Chirality::CounterClockwise => write!(f, "CCW"),
}
}
}
impl fmt::Display for Skyrmion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Skyrmion[{} {}]: r={:.1} nm, Q={}",
self.helicity,
self.chirality,
self.radius * 1e9,
self.topological_charge
)
}
}
impl fmt::Display for LatticeType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LatticeType::Square => write!(f, "Square"),
LatticeType::Hexagonal => write!(f, "Hexagonal"),
}
}
}
impl fmt::Display for SkyrmionLattice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SkyrmionLattice[{}]: {} skyrmions, a={:.1} nm, Q_tot={}",
self.lattice_type,
self.skyrmions.len(),
self.lattice_constant * 1e9,
self.total_topological_charge()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_skyrmion_creation() {
let sk = Skyrmion::new(
(0.0, 0.0),
10.0e-9,
Helicity::Neel,
Chirality::CounterClockwise,
);
assert_eq!(sk.topological_charge, -1);
}
#[test]
fn test_magnetization_at_center() {
let sk = Skyrmion::new(
(0.0, 0.0),
10.0e-9,
Helicity::Neel,
Chirality::CounterClockwise,
);
let m = sk.magnetization_at(0.0, 0.0, 2.0e-9);
assert!(m.z < 0.0);
assert!((m.magnitude() - 1.0).abs() < 0.1);
}
#[test]
fn test_magnetization_far_from_center() {
let sk = Skyrmion::new(
(0.0, 0.0),
10.0e-9,
Helicity::Neel,
Chirality::CounterClockwise,
);
let m = sk.magnetization_at(100.0e-9, 0.0, 2.0e-9);
assert!(m.z > 0.5);
}
#[test]
fn test_square_lattice() {
let lattice = SkyrmionLattice::square(
3,
3,
50.0e-9,
10.0e-9,
Helicity::Neel,
Chirality::CounterClockwise,
);
assert_eq!(lattice.skyrmions.len(), 9);
assert_eq!(lattice.total_topological_charge(), -9);
}
#[test]
fn test_hexagonal_lattice() {
let lattice = SkyrmionLattice::hexagonal(
4,
4,
50.0e-9,
10.0e-9,
Helicity::Bloch,
Chirality::Clockwise,
);
assert_eq!(lattice.skyrmions.len(), 16);
assert_eq!(lattice.lattice_type, LatticeType::Hexagonal);
}
#[test]
fn test_default_skyrmion() {
let sk = Skyrmion::default();
assert_eq!(sk.helicity, Helicity::Neel);
assert_eq!(sk.chirality, Chirality::CounterClockwise);
assert_eq!(sk.topological_charge, -1);
assert_eq!(sk.center, (0.0, 0.0));
assert_eq!(sk.radius, 50.0e-9);
}
}