use ndarray::prelude::*;
use ndarray_linalg::*;
use rayon::prelude::*;
use crate::Gauge;
use crate::Model;
use crate::RMatrixData;
use crate::error::Result;
use crate::thermodynamics::Occupation;
use super::config::{Integration, Parameters, mesh_array, parameters_occupation, validate_sorted};
use super::energy_cut::{integrate_fermi_cut_2d, integrate_fermi_cut_3d};
use super::kernel::quadrature_occupied_geometry_simplex;
use super::tracking::{build_tetrahedra_3d, build_triangles_2d, global_band_track};
use super::types::{SIMPLEX_GAP_TOL, VertexKernel};
#[derive(Clone, Debug, PartialEq)]
pub struct HallConductivityResult {
pub chemical_potentials: Array1<f64>,
pub conductivity: Array1<f64>,
}
impl HallConductivityResult {
pub fn single(&self) -> Option<f64> {
(self.conductivity.len() == 1).then(|| self.conductivity[0])
}
}
pub(crate) fn integrate_occupied_geometry(
all_pts: &[VertexKernel],
k_mesh: &Array1<usize>,
eta: f64,
chemical_potentials: &Array1<f64>,
occupation: Occupation,
) -> (Array1<f64>, Array1<f64>, usize) {
let dim = k_mesh.len();
let mut total_g = Array1::<f64>::zeros(chemical_potentials.len());
let mut total_o = Array1::<f64>::zeros(chemical_potentials.len());
let mut unsafe_count = 0usize;
match dim {
2 => {
let (nx, ny) = (k_mesh[0], k_mesh[1]);
let inv_nx = 1.0 / nx as f64;
let inv_ny = 1.0 / ny as f64;
for ix in 0..nx {
for iy in 0..ny {
let sims = build_triangles_2d(ix, iy, nx, ny, inv_nx, inv_ny, all_pts);
for sim in &sims {
if sim.diag.min_gap < SIMPLEX_GAP_TOL {
unsafe_count += 1;
}
let (g, o) = quadrature_occupied_geometry_simplex(
sim,
eta,
chemical_potentials,
occupation,
);
total_g += &g;
total_o += &o;
}
}
}
}
3 => {
let (nx, ny, nz) = (k_mesh[0], k_mesh[1], k_mesh[2]);
let inv_nx = 1.0 / nx as f64;
let inv_ny = 1.0 / ny as f64;
let inv_nz = 1.0 / nz as f64;
for ix in 0..nx {
for iy in 0..ny {
for iz in 0..nz {
let sims = build_tetrahedra_3d(
ix, iy, iz, nx, ny, nz, inv_nx, inv_ny, inv_nz, all_pts,
);
for sim in &sims {
if sim.diag.min_gap < SIMPLEX_GAP_TOL {
unsafe_count += 1;
}
let (g, o) = quadrature_occupied_geometry_simplex(
sim,
eta,
chemical_potentials,
occupation,
);
total_g += &g;
total_o += &o;
}
}
}
}
}
_ => panic!("linear::integrate: only dim=2,3 supported, got dim={dim}"),
}
(total_g, total_o, unsafe_count)
}
impl<const SPIN: bool, const DIM: usize, R: RMatrixData> Model<SPIN, DIM, R> {
pub fn hall_conductivity(&self, params: &Parameters<DIM>) -> Result<HallConductivityResult> {
params.validate_rank2()?;
let spin = params.spin;
if !SPIN && let Some(direction) = spin {
return Err(crate::TbError::SpinNotAllowed(direction));
}
match params.integration {
Integration::Direct | Integration::EnergyCut => {}
Integration::Simplex => {
return Err(crate::TbError::InvalidResponseParameter {
parameter: "integration",
message:
"hall_conductivity supports Integration::Direct or EnergyCut, not Simplex"
.into(),
});
}
}
if params.integration == Integration::EnergyCut {
validate_sorted(¶ms.mu, "mu")?;
if DIM != 2 && DIM != 3 {
return Err(crate::TbError::InvalidDimension {
dim: DIM,
supported: vec![2, 3],
});
}
}
let k_mesh = mesh_array(¶ms.kmesh);
let determinant = self.lat.det()?;
let dir_a = params.direction.row(0).to_owned();
let dir_b = params.direction.row(1).to_owned();
let occupation = parameters_occupation(params);
let conductivity = match params.integration {
Integration::Direct => {
let kvec: Array2<f64> = crate::kpoints::gen_kmesh(&k_mesh)?;
let nk = kvec.nrows();
let band_data: Vec<Result<_>> = kvec
.axis_iter(Axis(0))
.into_par_iter()
.map(|k| self.berry_curvature_at_impl(&k, params))
.collect();
let band_data = band_data.into_iter().collect::<Result<Vec<_>>>()?;
let values: Vec<f64> = params
.mu
.par_iter()
.map(|&mu| {
let sum: f64 = band_data
.iter()
.map(|bands| {
bands
.berry_curvature
.iter()
.zip(&bands.energies)
.map(|(&omega, &energy)| {
omega * occupation.value_unchecked(energy, mu)
})
.sum::<f64>()
})
.sum();
sum / nk as f64 / determinant
})
.collect();
Array1::from_vec(values)
}
Integration::EnergyCut => {
let chemical_potentials = Array1::from_iter(params.mu.iter().copied());
let kvec = crate::kpoints::gen_kmesh(&k_mesh)?;
let mut all_pts: Vec<VertexKernel> = (0..kvec.nrows())
.into_par_iter()
.map(|ik| {
self.compute_velocity_kernel(
&kvec.row(ik).to_owned(),
&dir_a,
&dir_b,
None,
Gauge::Atom,
spin,
)
})
.collect();
global_band_track(&mut all_pts, ¶ms.kmesh);
let width = occupation.energy_width()?;
let sigma = match DIM {
2 => integrate_fermi_cut_2d(
&all_pts,
&k_mesh,
&chemical_potentials,
width,
params.eta,
),
3 => integrate_fermi_cut_3d(
&all_pts,
&k_mesh,
&chemical_potentials,
width,
params.eta,
),
_ => {
return Err(crate::TbError::InvalidDimension {
dim: DIM,
supported: vec![2, 3],
});
}
};
sigma / determinant
}
Integration::Simplex => unreachable!("rejected during validation"),
};
Ok(HallConductivityResult {
chemical_potentials: params.mu.clone(),
conductivity,
})
}
}