use crate::error::{Result, TbError};
use crate::find_R;
use crate::Model;
use ndarray::prelude::*;
use ndarray::*;
use num_complex::Complex;
use std::f64::consts::TAU;
const FLUX_QUANTUM_T_M2: f64 = 4.135_667_696e-15_f64;
const MU_B_EV_PER_T: f64 = 5.788_381_8060e-5_f64;
pub trait MagneticField {
fn add_magnetic_field(&self, mag_dir: usize, expand: [usize; 2], phi_total: isize) -> Result<Self>
where
Self: Sized;
}
impl MagneticField for Model {
fn add_magnetic_field(&self, mag_dir: usize, expand: [usize; 2], phi_total: isize) -> Result<Self> {
validate_dimensions(self.dim_r(), mag_dir, expand)?;
let perp = perpendicular_dirs(self.dim_r(), mag_dir)?;
let mut u = Array2::<f64>::eye(self.dim_r());
u[[perp[0], perp[0]]] = expand[0] as f64;
u[[perp[1], perp[1]]] = expand[1] as f64;
let super_model = self.make_supercell(&u)?;
let d1 = perp[0];
let d2 = perp[1];
let norb = super_model.norb();
let total_basis = super_model.nsta();
let dim_r = super_model.dim_r();
let mut new_ham = super_model.ham.clone();
let mut new_rmatrix = super_model.rmatrix.clone();
if phi_total != 0 {
for i_r in 0..super_model.hamR.nrows() {
let r_vec = super_model.hamR.row(i_r);
let r1 = r_vec[d1];
let r2 = r_vec[d2];
let mut ham_slice = new_ham.slice_mut(s![i_r, .., ..]);
let mut r_slice = new_rmatrix.slice_mut(s![i_r, .., .., ..]);
for i in 0..total_basis {
let orb_i = i % norb;
let u1_i = super_model.orb[[orb_i, d1]];
let u2_i = super_model.orb[[orb_i, d2]];
for j in 0..total_basis {
let orb_j = j % norb;
let u1_j = super_model.orb[[orb_j, d1]];
let u2_j = super_model.orb[[orb_j, d2]];
let phase = peierls_phase_periodic_landau(
phi_total,
u1_i,
u2_i,
u1_j,
u2_j,
r1,
r2,
);
let peierls = Complex::new(phase.cos(), phase.sin());
ham_slice[[i, j]] *= peierls;
for alpha in 0..dim_r {
r_slice[[alpha, i, j]] *= peierls;
}
}
}
}
}
if super_model.spin && phi_total != 0 {
let b_cart_tesla = magnetic_field_cartesian(&self.lat, self.dim_r(), mag_dir, expand, phi_total)?;
let zeeman = zeeman_block_cartesian(b_cart_tesla, 2.0);
let zero_r = Array1::<isize>::zeros(super_model.dim_r());
let onsite_index = find_R(&super_model.hamR, &zero_r)
.ok_or_else(|| TbError::Other("R = 0 block not found in magnetic supercell".to_string()))?;
let mut ham0 = new_ham.slice_mut(s![onsite_index, .., ..]);
add_zeeman_term(&mut ham0, norb, zeeman);
}
let mut out = super_model;
out.ham = new_ham;
out.rmatrix = new_rmatrix;
Ok(out)
}
}
fn peierls_phase_periodic_landau(
phi_total: isize,
u1_i: f64,
u2_i: f64,
u1_j: f64,
u2_j: f64,
r1: isize,
r2: isize,
) -> f64 {
let v1_j = u1_j + r1 as f64;
let v2_j = u2_j + r2 as f64;
let reduced_line_integral_in_flux_quanta =
0.5 * (u1_i + v1_j) * (v2_j - u2_i) - (r1 as f64) * v2_j;
-TAU * (phi_total as f64) * reduced_line_integral_in_flux_quanta
}
fn add_zeeman_term(
ham0: &mut ArrayViewMut2<'_, Complex<f64>>,
norb: usize,
z: [[Complex<f64>; 2]; 2],
) {
for orb in 0..norb {
let up = orb;
let dn = orb + norb;
ham0[[up, up]] += z[0][0];
ham0[[up, dn]] += z[0][1];
ham0[[dn, up]] += z[1][0];
ham0[[dn, dn]] += z[1][1];
}
}
fn zeeman_block_cartesian(b_cart_tesla: [f64; 3], g_factor: f64) -> [[Complex<f64>; 2]; 2] {
let pref = 0.5 * g_factor * MU_B_EV_PER_T;
let bx = pref * b_cart_tesla[0];
let by = pref * b_cart_tesla[1];
let bz = pref * b_cart_tesla[2];
[
[Complex::new(bz, 0.0), Complex::new(bx, -by)],
[Complex::new(bx, by), Complex::new(-bz, 0.0)],
]
}
fn magnetic_field_cartesian(
lat: &Array2<f64>,
dim_r: usize,
mag_dir: usize,
expand: [usize; 2],
phi_total: isize,
) -> Result<[f64; 3]> {
let perp = perpendicular_dirs(dim_r, mag_dir)?;
let flux_per_primitive = phi_total as f64 / (expand[0] as f64 * expand[1] as f64);
if dim_r == 2 {
let a1 = pad_to_3(lat.row(perp[0]));
let a2 = pad_to_3(lat.row(perp[1]));
let area_vec = cross3(a1, a2);
let area_a2 = norm3(area_vec);
if area_a2.abs() < 1e-14 {
return Err(TbError::Other("2D lattice area is numerically zero".to_string()));
}
let b_mag = flux_per_primitive * FLUX_QUANTUM_T_M2 / (area_a2 * 1e-20);
Ok([0.0, 0.0, b_mag])
} else {
let a_mag = pad_to_3(lat.row(mag_dir));
let a_p1 = pad_to_3(lat.row(perp[0]));
let a_p2 = pad_to_3(lat.row(perp[1]));
let omega = dot3(a_mag, cross3(a_p1, a_p2));
if omega.abs() < 1e-14 {
return Err(TbError::Other(
"oriented primitive volume is numerically zero".to_string(),
));
}
let beta = flux_per_primitive * FLUX_QUANTUM_T_M2 / (omega * 1e-20);
Ok([beta * a_mag[0], beta * a_mag[1], beta * a_mag[2]])
}
}
fn validate_dimensions(dim_r: usize, mag_dir: usize, expand: [usize; 2]) -> Result<()> {
if expand[0] == 0 || expand[1] == 0 {
return Err(TbError::InvalidSupercellSize(0));
}
match dim_r {
2 => {
if mag_dir != 2 {
return Err(TbError::InvalidDirection { index: mag_dir, dim: dim_r });
}
}
3 => {
if mag_dir >= 3 {
return Err(TbError::InvalidDirection { index: mag_dir, dim: dim_r });
}
}
_ => {
return Err(TbError::InvalidDimension {
dim: dim_r,
supported: vec![2, 3],
});
}
}
Ok(())
}
fn perpendicular_dirs(dim_r: usize, mag_dir: usize) -> Result<[usize; 2]> {
match dim_r {
2 => {
if mag_dir == 2 {
Ok([0, 1])
} else {
Err(TbError::InvalidDirection { index: mag_dir, dim: dim_r })
}
}
3 => match mag_dir {
0 => Ok([1, 2]),
1 => Ok([2, 0]),
2 => Ok([0, 1]),
_ => Err(TbError::InvalidDirection { index: mag_dir, dim: dim_r }),
},
_ => Err(TbError::InvalidDimension {
dim: dim_r,
supported: vec![2, 3],
}),
}
}
fn pad_to_3(v: ArrayView1<'_, f64>) -> [f64; 3] {
match v.len() {
0 => [0.0, 0.0, 0.0],
1 => [v[0], 0.0, 0.0],
2 => [v[0], v[1], 0.0],
_ => [v[0], v[1], v[2]],
}
}
fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn norm3(v: [f64; 3]) -> f64 {
dot3(v, v).sqrt()
}