use ndarray::array;
use ndarray::{Array1, Array2, ArrayView1};
use crate::SpinDirection;
use crate::error::{Result, TbError};
use crate::thermodynamics::Occupation;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Integration {
#[default]
Direct,
Simplex,
EnergyCut,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FieldSymmetry {
Ordered,
#[default]
Symmetrized,
}
#[derive(Clone, Debug)]
pub struct Parameters<const DIM: usize> {
#[allow(non_snake_case)]
pub T: Array1<f64>,
pub mu: Array1<f64>,
pub eta: f64,
pub kmesh: [usize; DIM],
pub omega: Array1<f64>,
pub spin: Option<SpinDirection>,
pub direction: Array2<f64>,
pub integration: Integration,
pub field_symmetry: FieldSymmetry,
}
impl<const DIM: usize> Parameters<DIM> {
pub fn new(kmesh: [usize; DIM], direction: Array2<f64>, mu: Array1<f64>) -> Self {
Self {
T: array![0.0],
mu,
eta: 1e-3,
kmesh,
omega: array![0.0],
spin: None,
direction,
integration: Integration::Direct,
field_symmetry: FieldSymmetry::Symmetrized,
}
}
pub fn at_mu(kmesh: [usize; DIM], direction: Array2<f64>, mu: f64) -> Self {
Self::new(kmesh, direction, array![mu])
}
pub fn rank2(
kmesh: [usize; DIM],
direction_a: [f64; DIM],
direction_b: [f64; DIM],
mu: Array1<f64>,
) -> Self {
Self::new(kmesh, direction_matrix(&[direction_a, direction_b]), mu)
}
pub fn rank3(
kmesh: [usize; DIM],
current: [f64; DIM],
field_1: [f64; DIM],
field_2: [f64; DIM],
mu: Array1<f64>,
) -> Self {
Self::new(kmesh, direction_matrix(&[current, field_1, field_2]), mu)
}
pub fn with_temperature(mut self, kelvin: f64) -> Self {
self.T = array![kelvin];
self
}
pub fn with_spin(mut self, spin: SpinDirection) -> Self {
self.spin = Some(spin);
self
}
pub fn with_frequency(mut self, omega: f64) -> Self {
self.omega = array![omega];
self
}
pub fn with_integration(mut self, integration: Integration) -> Self {
self.integration = integration;
self
}
pub(crate) fn validate_rank2(&self) -> Result<()> {
validate_k_mesh(&self.kmesh)?;
validate_direction_matrix(&self.direction, 2, DIM)?;
validate_chemical_potentials(&self.mu)?;
validate_broadening(self.eta)?;
validate_temperature(&self.T)
}
pub(crate) fn validate_rank3(&self) -> Result<()> {
validate_k_mesh(&self.kmesh)?;
validate_direction_matrix(&self.direction, 3, DIM)?;
validate_chemical_potentials(&self.mu)?;
validate_temperature(&self.T)
}
}
pub(crate) fn direction_matrix<const N: usize, const DIM: usize>(
rows: &[[f64; DIM]; N],
) -> Array2<f64> {
let mut matrix = Array2::<f64>::zeros((N, DIM));
for (index, row) in rows.iter().enumerate() {
matrix.row_mut(index).assign(&ArrayView1::from(row));
}
matrix
}
pub(crate) fn parameters_occupation<const DIM: usize>(params: &Parameters<DIM>) -> Occupation {
if params.T[0] <= 0.0 {
Occupation::ZeroTemperature
} else {
Occupation::FermiDirac {
temperature_kelvin: params.T[0],
}
}
}
pub(crate) fn validate_temperature(values: &Array1<f64>) -> Result<()> {
if values.is_empty() {
return Err(TbError::InvalidResponseParameter {
parameter: "T",
message: "must contain at least one value".into(),
});
}
if values
.iter()
.any(|value| !value.is_finite() || *value < 0.0)
{
return Err(TbError::InvalidResponseParameter {
parameter: "T",
message: "all values must be finite and non-negative".into(),
});
}
Ok(())
}
pub(crate) fn validate_direction_matrix(
direction: &Array2<f64>,
rank: usize,
dim: usize,
) -> Result<()> {
if direction.nrows() != rank {
return Err(TbError::DimensionMismatch {
context: "direction".into(),
expected: rank,
found: direction.nrows(),
});
}
if direction.ncols() != dim {
return Err(TbError::DimensionMismatch {
context: "direction".into(),
expected: dim,
found: direction.ncols(),
});
}
for row in direction.rows() {
if row.iter().any(|value| !value.is_finite()) {
return Err(TbError::InvalidResponseParameter {
parameter: "direction",
message: "all components must be finite".into(),
});
}
if row.iter().all(|value| *value == 0.0) {
return Err(TbError::InvalidResponseParameter {
parameter: "direction",
message: "each direction row must not be the zero vector".into(),
});
}
}
Ok(())
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct IntegrationDiagnostics {
pub unsafe_simplex_count: usize,
}
pub(crate) fn validate_k_mesh<const DIM: usize>(k_mesh: &[usize; DIM]) -> Result<()> {
if !(1..=3).contains(&DIM) {
return Err(TbError::InvalidDimension {
dim: DIM,
supported: vec![1, 2, 3],
});
}
if k_mesh.contains(&0) {
return Err(TbError::InvalidKmeshDimensions(Array1::from_vec(
k_mesh.to_vec(),
)));
}
Ok(())
}
pub(crate) fn mesh_array<const DIM: usize>(k_mesh: &[usize; DIM]) -> Array1<usize> {
Array1::from_vec(k_mesh.to_vec())
}
pub(crate) fn validate_broadening(broadening: f64) -> Result<()> {
if !broadening.is_finite() || broadening < 0.0 {
return Err(TbError::InvalidResponseParameter {
parameter: "broadening",
message: "must be finite and non-negative".into(),
});
}
Ok(())
}
pub(crate) fn validate_chemical_potentials(values: &Array1<f64>) -> Result<()> {
if values.is_empty() {
return Err(TbError::InvalidResponseParameter {
parameter: "chemical_potentials",
message: "must contain at least one value".into(),
});
}
if values.iter().any(|value| !value.is_finite()) {
return Err(TbError::InvalidResponseParameter {
parameter: "chemical_potentials",
message: "all values must be finite".into(),
});
}
Ok(())
}
pub(crate) fn validate_sorted(values: &Array1<f64>, parameter: &'static str) -> Result<()> {
if values
.iter()
.zip(values.iter().skip(1))
.any(|(left, right)| left > right)
{
return Err(TbError::InvalidResponseParameter {
parameter,
message: "must be sorted in ascending order".into(),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::{array, s};
#[test]
fn sorted_validation_handles_strided_arrays() {
let descending = array![0.0, 1.0, 2.0].slice_move(s![..;-1]);
assert!(validate_sorted(&descending, "values").is_err());
}
#[test]
fn direction_matrix_builds_rows_and_rank3_validation() {
let matrix = direction_matrix::<3, 2>(&[[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]);
assert_eq!(matrix.dim(), (3, 2));
assert!(validate_direction_matrix(&matrix, 3, 2).is_ok());
assert!(validate_direction_matrix(&matrix, 2, 2).is_err());
}
#[test]
fn parameters_rank2_rank3_construct_direction_rows() {
let rank2 = Parameters::rank2([4, 4], [1.0, 0.0], [0.0, 1.0], array![0.0]);
assert_eq!(rank2.direction.dim(), (2, 2));
assert!(rank2.validate_rank2().is_ok());
let rank3 = Parameters::rank3([4, 4], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0], array![0.0]);
assert_eq!(rank3.direction.dim(), (3, 2));
assert!(rank3.validate_rank3().is_ok());
assert!(rank3.validate_rank2().is_err());
}
}