use ndarray::Data;
use ndarray::prelude::*;
use ndarray_linalg::{Determinant, Eigh, UPLO};
use num_complex::Complex;
use rayon::prelude::*;
use crate::error::{Result, TbError};
use crate::response::config::{
Integration, IntegrationDiagnostics, Parameters, mesh_array, parameters_occupation,
};
use crate::response::linear::integrate_occupied_geometry;
use crate::response::{VertexKernel, global_band_track};
use crate::velocity::Velocity;
use crate::{Gauge, Model, RMatrixData};
#[derive(Clone, Debug, PartialEq)]
pub struct BandQuantumGeometry {
pub metric: Array1<f64>,
pub berry_curvature: Array1<f64>,
pub energies: Array1<f64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct QuantumGeometryMap {
pub metric: Array2<f64>,
pub berry_curvature: Array2<f64>,
pub energies: Array2<f64>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct QuantumGeometryResult {
pub chemical_potentials: Array1<f64>,
pub metric: Array1<f64>,
pub berry_curvature: Array1<f64>,
pub diagnostics: Option<IntegrationDiagnostics>,
}
pub trait QuantumGeometry<const DIM: usize>: Velocity {
fn quantum_geometry_at<S: Data<Elem = f64>>(
&self,
k: &ArrayBase<S, Ix1>,
params: &Parameters<DIM>,
) -> Result<BandQuantumGeometry>;
fn quantum_geometry_on<S: Data<Elem = f64> + Sync>(
&self,
k_points: &ArrayBase<S, Ix2>,
params: &Parameters<DIM>,
) -> Result<QuantumGeometryMap>;
}
impl<const SPIN: bool, const DIM: usize, R: RMatrixData> QuantumGeometry<DIM>
for Model<SPIN, DIM, R>
{
fn quantum_geometry_at<S: Data<Elem = f64>>(
&self,
k: &ArrayBase<S, Ix1>,
params: &Parameters<DIM>,
) -> Result<BandQuantumGeometry> {
if k.len() != DIM {
return Err(TbError::KVectorLengthMismatch {
expected: DIM,
actual: k.len(),
});
}
crate::response::config::validate_direction_matrix(¶ms.direction, 2, DIM)?;
crate::response::config::validate_broadening(params.eta)?;
self.quantum_geometry_at_impl(k, params)
}
fn quantum_geometry_on<S: Data<Elem = f64> + Sync>(
&self,
k_points: &ArrayBase<S, Ix2>,
params: &Parameters<DIM>,
) -> Result<QuantumGeometryMap> {
if k_points.ncols() != DIM {
return Err(TbError::DimensionMismatch {
context: "quantum geometry k-points".into(),
expected: DIM,
found: k_points.ncols(),
});
}
crate::response::config::validate_direction_matrix(¶ms.direction, 2, DIM)?;
crate::response::config::validate_broadening(params.eta)?;
let rows: Vec<Result<BandQuantumGeometry>> = k_points
.axis_iter(Axis(0))
.into_par_iter()
.map(|k| self.quantum_geometry_at_impl(&k, params))
.collect();
let rows: Vec<BandQuantumGeometry> = rows.into_iter().collect::<Result<_>>()?;
let number_of_k_points = rows.len();
let mut metric = Array2::<f64>::zeros((number_of_k_points, self.nsta()));
let mut berry_curvature = Array2::<f64>::zeros((number_of_k_points, self.nsta()));
let mut energies = Array2::<f64>::zeros((number_of_k_points, self.nsta()));
for (index, row) in rows.into_iter().enumerate() {
metric.row_mut(index).assign(&row.metric);
berry_curvature.row_mut(index).assign(&row.berry_curvature);
energies.row_mut(index).assign(&row.energies);
}
Ok(QuantumGeometryMap {
metric,
berry_curvature,
energies,
})
}
}
impl<const SPIN: bool, const DIM: usize, R: RMatrixData> Model<SPIN, DIM, R> {
pub(crate) fn quantum_geometry_at_impl<S: Data<Elem = f64>>(
&self,
k: &ArrayBase<S, Ix1>,
params: &Parameters<DIM>,
) -> Result<BandQuantumGeometry> {
let (projected_velocity, hamiltonian) =
self.gen_v_projected(k, Gauge::Atom, ¶ms.direction);
let (energies, eigenvectors) = hamiltonian.eigh(UPLO::Lower)?;
let bra = eigenvectors.t();
let ket = eigenvectors.mapv(|value| value.conj());
let velocity_a = projected_velocity.index_axis(Axis(0), 0);
let velocity_b = projected_velocity.index_axis(Axis(0), 1);
let a_band = bra.dot(&velocity_a.dot(&ket));
let b_band = bra.dot(&velocity_b.dot(&ket));
let kernel = a_band * b_band.reversed_axes();
let eta_squared = params.eta * params.eta;
let mut metric = Array1::<f64>::zeros(self.nsta());
let mut berry_curvature = Array1::<f64>::zeros(self.nsta());
for band in 0..self.nsta() {
let mut tensor = Complex::new(0.0, 0.0);
for other in 0..self.nsta() {
if band == other {
continue;
}
let difference = energies[band] - energies[other];
tensor += kernel[[band, other]] / (difference * difference + eta_squared);
}
metric[band] = tensor.re;
berry_curvature[band] = -2.0 * tensor.im;
}
Ok(BandQuantumGeometry {
metric,
berry_curvature,
energies,
})
}
pub fn quantum_geometry(&self, params: &Parameters<DIM>) -> Result<QuantumGeometryResult> {
params.validate_rank2()?;
if params.integration == Integration::EnergyCut {
return Err(TbError::InvalidResponseParameter {
parameter: "integration",
message: "quantum_geometry supports Integration::Direct or Simplex, not EnergyCut"
.into(),
});
}
if params.integration == Integration::Simplex && DIM == 1 {
return Err(TbError::InvalidDimension {
dim: DIM,
supported: vec![2, 3],
});
}
let k_mesh = mesh_array(¶ms.kmesh);
let determinant = self.lat.det()?;
let occupation = parameters_occupation(params);
let (metric, berry_curvature, diagnostics) = match params.integration {
Integration::Direct => {
let k_points = crate::kpoints::gen_kmesh::<f64>(&k_mesh)?;
let geometry = self.quantum_geometry_on(&k_points, params)?;
let normalization = 1.0 / k_points.nrows() as f64 / determinant;
let values: Vec<(f64, f64)> = params
.mu
.par_iter()
.map(|&mu| {
let mut metric_sum = 0.0;
let mut berry_sum = 0.0;
for k in 0..k_points.nrows() {
for band in 0..self.nsta() {
let occ =
occupation.value_unchecked(geometry.energies[[k, band]], mu);
metric_sum += geometry.metric[[k, band]] * occ;
berry_sum += geometry.berry_curvature[[k, band]] * occ;
}
}
(metric_sum * normalization, berry_sum * normalization)
})
.collect();
let (metric, berry): (Vec<_>, Vec<_>) = values.into_iter().unzip();
(Array1::from_vec(metric), Array1::from_vec(berry), None)
}
Integration::Simplex => {
let k_points = crate::kpoints::gen_kmesh::<f64>(&k_mesh)?;
let direction_a = params.direction.row(0).to_owned();
let direction_b = params.direction.row(1).to_owned();
let mut vertices: Vec<VertexKernel> = (0..k_points.nrows())
.into_par_iter()
.map(|index| {
self.compute_velocity_kernel(
&k_points.row(index).to_owned(),
&direction_a,
&direction_b,
None,
Gauge::Atom,
None,
)
})
.collect();
global_band_track(&mut vertices, ¶ms.kmesh);
let (metric, berry, unsafe_simplex_count) = integrate_occupied_geometry(
&vertices, &k_mesh, params.eta, ¶ms.mu, occupation,
);
(
metric / determinant,
berry / determinant,
Some(IntegrationDiagnostics {
unsafe_simplex_count,
}),
)
}
Integration::EnergyCut => unreachable!("rejected during validation"),
};
Ok(QuantumGeometryResult {
chemical_potentials: params.mu.clone(),
metric,
berry_curvature,
diagnostics,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::array;
fn massive_dirac_model() -> Model<false, 2> {
let mut model = Model::<false, 2>::tb_model(
array![[1.0, 0.0], [0.0, 1.0]],
array![[0.0, 0.0], [0.0, 0.0]],
None,
)
.unwrap();
model.set_onsite(&array![-0.5, 0.5], None);
model
}
fn qwz_model(mass: f64) -> Model<false, 2> {
let mut model = Model::<false, 2>::tb_model(
array![[1.0, 0.0], [0.0, 1.0]],
array![[0.0, 0.0], [0.0, 0.0]],
None,
)
.unwrap();
model.add_onsite(&array![mass, -mass], None);
model.add_hop(Complex::new(0.0, -0.5), 0, 1, &array![1, 0], None);
model.add_hop(Complex::new(0.0, 0.5), 0, 1, &array![-1, 0], None);
model.add_hop(-0.5, 0, 1, &array![0, 1], None);
model.add_hop(0.5, 0, 1, &array![0, -1], None);
for displacement in [array![1, 0], array![-1, 0], array![0, 1], array![0, -1]] {
model.add_hop(0.5, 0, 0, &displacement, None);
model.add_hop(-0.5, 1, 1, &displacement, None);
}
model
}
#[test]
fn named_band_result_has_real_components() {
let model = massive_dirac_model();
let params = Parameters::rank2([1, 1], [1.0, 0.0], [0.0, 1.0], array![0.0]);
let geometry = model
.quantum_geometry_at(&array![0.0, 0.0], ¶ms)
.unwrap();
assert_eq!(geometry.metric.len(), model.nsta());
assert_eq!(geometry.berry_curvature.len(), model.nsta());
assert_eq!(geometry.energies.len(), model.nsta());
}
#[test]
fn direct_and_simplex_integrate_the_same_geometry() {
let model = qwz_model(-1.0);
let mut params = Parameters::rank2([31, 31], [1.0, 0.0], [0.0, 1.0], array![0.0]);
params.eta = 0.1;
let direct = model.quantum_geometry(¶ms).unwrap();
params.integration = Integration::Simplex;
let simplex = model.quantum_geometry(¶ms).unwrap();
assert!(simplex.diagnostics.is_some());
assert!((direct.metric[0] - simplex.metric[0]).abs() < 5e-3);
assert!((direct.berry_curvature[0] - simplex.berry_curvature[0]).abs() < 5e-3);
}
}