use crate::fitters::RealMatrixFitter;
use crate::freq::MatsubaraFreq;
use crate::gemm::GemmBackendHandle;
use crate::traits::{Statistics, StatisticsType};
use mdarray::DTensor;
use num_complex::Complex;
use std::marker::PhantomData;
pub fn gtau_single_pole<S: StatisticsType>(tau: f64, omega: f64, beta: f64) -> f64 {
match S::STATISTICS {
Statistics::Fermionic => fermionic_single_pole(tau, omega, beta),
Statistics::Bosonic => bosonic_single_pole(tau, omega, beta),
}
}
pub fn fermionic_single_pole(tau: f64, omega: f64, beta: f64) -> f64 {
use crate::taufuncs::normalize_tau;
use crate::traits::Fermionic;
let (tau_normalized, sign) = normalize_tau::<Fermionic>(tau, beta);
let value = if omega >= 0.0 {
-(-omega * tau_normalized).exp() / (1.0 + (-beta * omega).exp())
} else {
-(omega * (beta - tau_normalized)).exp() / (1.0 + (beta * omega).exp())
};
sign * value
}
pub fn bosonic_single_pole(tau: f64, omega: f64, beta: f64) -> f64 {
use crate::taufuncs::normalize_tau;
use crate::traits::Bosonic;
let tau_normalized = normalize_tau::<Bosonic>(tau, beta).0;
if omega >= 0.0 {
(-omega * tau_normalized).exp() / (1.0 - (-beta * omega).exp())
} else {
-(omega * (beta - tau_normalized)).exp() / (1.0 - (beta * omega).exp())
}
}
pub fn giwn_single_pole<S: StatisticsType>(
matsubara_freq: &MatsubaraFreq<S>,
omega: f64,
beta: f64,
) -> Complex<f64> {
let wn = matsubara_freq.value(beta);
let denominator = Complex::new(0.0, 1.0) * wn - Complex::new(omega, 0.0);
Complex::new(1.0, 0.0) / denominator
}
pub struct DiscreteLehmannRepresentation<S>
where
S: StatisticsType,
{
pub poles: Vec<f64>,
pub beta: f64,
pub wmax: f64,
kernel: crate::kernel::LogisticKernel,
kernel_ypower: i32,
pub accuracy: f64,
pub regularizers: Vec<f64>,
pole_weights: Vec<f64>,
fitmat: DTensor<f64, 2>,
fitter: RealMatrixFitter,
_phantom: PhantomData<S>,
}
impl<S> DiscreteLehmannRepresentation<S>
where
S: StatisticsType,
{
pub fn kernel_ypower(&self) -> i32 {
self.kernel_ypower
}
pub fn pole_weights(&self) -> &[f64] {
&self.pole_weights
}
pub fn with_poles<K>(
basis: &impl crate::basis_trait::Basis<S, Kernel = K>,
poles: Vec<f64>,
) -> Self
where
S: 'static,
K: crate::kernel::KernelProperties + Clone,
{
use crate::kernel::LogisticKernel;
let beta = basis.beta();
let wmax = basis.wmax();
let accuracy = basis.accuracy();
let kernel_ypower = basis.kernel().ypower();
let v_at_poles = basis.evaluate_omega(&poles); let s = basis.svals();
let basis_size = basis.size();
let n_poles = poles.len();
let fitmat = DTensor::<f64, 2>::from_fn([basis_size, n_poles], |idx| {
let l = idx[0];
let i = idx[1];
-s[l] * v_at_poles[[i, l]]
});
let fitter = RealMatrixFitter::new(fitmat.clone());
let lambda = beta * wmax;
let logistic_kernel = LogisticKernel::new(lambda);
let regularizers: Vec<f64> = poles
.iter()
.map(|&pole| basis.kernel().regularizer::<S>(beta, pole))
.collect();
let pole_weight_scale = wmax.powi(2 * kernel_ypower);
let pole_weights: Vec<f64> = regularizers
.iter()
.map(|®ularizer| regularizer / pole_weight_scale)
.collect();
Self {
poles,
beta,
wmax,
kernel: logistic_kernel,
kernel_ypower,
accuracy,
regularizers,
pole_weights,
fitmat,
fitter,
_phantom: PhantomData,
}
}
fn zero_pole_tau_limit(&self) -> f64 {
match self.kernel_ypower {
0 => -0.5,
1 => -1.0 / (self.beta * self.wmax * self.wmax),
_ => panic!(
"DLR tau evaluation does not support kernel ypower = {}",
self.kernel_ypower
),
}
}
fn zero_pole_matsubara_limit(&self) -> f64 {
match self.kernel_ypower {
0 => -0.5 * self.beta,
1 => -1.0 / (self.wmax * self.wmax),
_ => panic!(
"DLR Matsubara evaluation does not support kernel ypower = {}",
self.kernel_ypower
),
}
}
pub fn new<K>(basis: &impl crate::basis_trait::Basis<S, Kernel = K>) -> Self
where
S: 'static,
K: crate::kernel::KernelProperties + Clone,
{
let poles = basis.default_omega_sampling_points();
let basis_size = basis.size();
if basis_size > poles.len() {
eprintln!(
"Warning: Number of default poles ({}) is less than basis size ({}). \
This may happen if not enough precision is left in the polynomial.",
poles.len(),
basis_size
);
}
assert!(
basis_size <= poles.len(),
"The number of poles must be greater than or equal to the basis size"
);
Self::with_poles(basis, poles)
}
pub fn from_ir_nd<T>(
&self,
backend: Option<&GemmBackendHandle>,
gl: &mdarray::Tensor<T, mdarray::DynRank>,
dim: usize,
) -> mdarray::Tensor<T, mdarray::DynRank>
where
T: num_complex::ComplexFloat
+ faer_traits::ComplexField
+ From<f64>
+ Copy
+ Default
+ 'static,
{
use mdarray::{DTensor, Shape};
let mut gl_shape = vec![];
gl.shape().with_dims(|dims| {
gl_shape.extend_from_slice(dims);
});
let basis_size = gl_shape[dim];
assert_eq!(
basis_size,
self.fitmat.shape().0,
"IR basis size mismatch: expected {}, got {}",
self.fitmat.shape().0,
basis_size
);
let gl_dim0 = crate::sampling::movedim(gl, dim, 0);
let extra_size = gl_dim0.len() / basis_size;
let gl_2d_dyn = gl_dim0.reshape(&[basis_size, extra_size][..]).to_tensor();
let gl_2d = DTensor::<T, 2>::from_fn([basis_size, extra_size], |idx| {
gl_2d_dyn[&[idx[0], idx[1]][..]]
});
let g_dlr_2d = self.fitter.fit_2d_generic::<T>(backend, &gl_2d);
let n_poles = self.poles.len();
let mut g_dlr_shape = vec![n_poles];
gl_dim0.shape().with_dims(|dims| {
for i in 1..dims.len() {
g_dlr_shape.push(dims[i]);
}
});
let g_dlr_dim0 = g_dlr_2d.into_dyn().reshape(&g_dlr_shape[..]).to_tensor();
crate::sampling::movedim(&g_dlr_dim0, 0, dim)
}
pub fn to_ir_nd<T>(
&self,
backend: Option<&GemmBackendHandle>,
g_dlr: &mdarray::Tensor<T, mdarray::DynRank>,
dim: usize,
) -> mdarray::Tensor<T, mdarray::DynRank>
where
T: num_complex::ComplexFloat
+ faer_traits::ComplexField
+ From<f64>
+ Copy
+ Default
+ 'static,
{
use mdarray::{DTensor, Shape};
let mut g_dlr_shape = vec![];
g_dlr.shape().with_dims(|dims| {
g_dlr_shape.extend_from_slice(dims);
});
let n_poles = g_dlr_shape[dim];
assert_eq!(
n_poles,
self.poles.len(),
"DLR size mismatch: expected {}, got {}",
self.poles.len(),
n_poles
);
let g_dlr_dim0 = crate::sampling::movedim(g_dlr, dim, 0);
let extra_size = g_dlr_dim0.len() / n_poles;
let g_dlr_2d_dyn = g_dlr_dim0.reshape(&[n_poles, extra_size][..]).to_tensor();
let g_dlr_2d = DTensor::<T, 2>::from_fn([n_poles, extra_size], |idx| {
g_dlr_2d_dyn[&[idx[0], idx[1]][..]]
});
let gl_2d = self.fitter.evaluate_2d_generic::<T>(backend, &g_dlr_2d);
let basis_size = self.fitmat.shape().0;
let mut gl_shape = vec![basis_size];
g_dlr_dim0.shape().with_dims(|dims| {
for i in 1..dims.len() {
gl_shape.push(dims[i]);
}
});
let gl_dim0 = gl_2d.into_dyn().reshape(&gl_shape[..]).to_tensor();
crate::sampling::movedim(&gl_dim0, 0, dim)
}
}
impl<S> crate::basis_trait::Basis<S> for DiscreteLehmannRepresentation<S>
where
S: StatisticsType + 'static,
{
type Kernel = crate::kernel::LogisticKernel;
fn kernel(&self) -> &Self::Kernel {
&self.kernel
}
fn beta(&self) -> f64 {
self.beta
}
fn wmax(&self) -> f64 {
self.wmax
}
fn lambda(&self) -> f64 {
self.beta * self.wmax
}
fn size(&self) -> usize {
self.poles.len()
}
fn accuracy(&self) -> f64 {
self.accuracy
}
fn significance(&self) -> Vec<f64> {
vec![1.0; self.poles.len()]
}
fn svals(&self) -> Vec<f64> {
vec![1.0; self.poles.len()]
}
fn default_tau_sampling_points(&self) -> Vec<f64> {
unimplemented!(
"DLR does not directly support default tau sampling points; \
use the underlying IR basis"
)
}
fn default_matsubara_sampling_points(
&self,
_positive_only: bool,
) -> Vec<crate::freq::MatsubaraFreq<S>> {
unimplemented!(
"DLR does not directly support default Matsubara sampling points; \
use the underlying IR basis"
)
}
fn evaluate_tau(&self, tau: &[f64]) -> mdarray::DTensor<f64, 2> {
use crate::taufuncs::normalize_tau;
use mdarray::DTensor;
let n_points = tau.len();
let n_poles = self.poles.len();
DTensor::<f64, 2>::from_fn([n_points, n_poles], |idx| {
let tau_val = tau[idx[0]];
let pole = self.poles[idx[1]];
let pole_weight = self.pole_weights[idx[1]];
match S::STATISTICS {
Statistics::Fermionic => {
gtau_single_pole::<S>(tau_val, pole, self.beta) * pole_weight
}
Statistics::Bosonic => {
if pole == 0.0 {
self.zero_pole_tau_limit()
} else if pole > 0.0 {
let tau_norm = normalize_tau::<S>(tau_val, self.beta).0;
let denominator = -(-self.beta * pole).exp_m1();
-(-tau_norm * pole).exp() * pole_weight / denominator
} else {
let tau_norm = normalize_tau::<S>(tau_val, self.beta).0;
let denominator = -(self.beta * pole).exp_m1();
(pole * (self.beta - tau_norm)).exp() * pole_weight / denominator
}
}
}
})
}
fn evaluate_matsubara(
&self,
freqs: &[crate::freq::MatsubaraFreq<S>],
) -> mdarray::DTensor<num_complex::Complex<f64>, 2> {
use mdarray::DTensor;
use num_complex::Complex;
let n_points = freqs.len();
let n_poles = self.poles.len();
DTensor::<Complex<f64>, 2>::from_fn([n_points, n_poles], |idx| {
let freq = &freqs[idx[0]];
let pole = self.poles[idx[1]];
let pole_weight = self.pole_weights[idx[1]];
let iv = freq.value_imaginary(self.beta);
if S::STATISTICS == Statistics::Bosonic && pole == 0.0 {
if crate::freq::is_zero(freq) {
Complex::new(self.zero_pole_matsubara_limit(), 0.0)
} else {
Complex::new(0.0, 0.0)
}
} else {
Complex::new(pole_weight, 0.0) / (iv - Complex::new(pole, 0.0))
}
})
}
fn evaluate_omega(&self, _omega: &[f64]) -> mdarray::DTensor<f64, 2> {
unimplemented!(
"evaluate_omega is not well-defined for DLR; \
use the underlying IR basis for real-frequency evaluation"
)
}
fn default_omega_sampling_points(&self) -> Vec<f64> {
self.poles.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::{Bosonic, Fermionic};
fn test_periodicity_generic<S: StatisticsType>(expected_sign: f64, stat_name: &str) {
let beta = 1.0;
let omega = 5.0;
for tau in [0.1, 0.3, 0.7] {
let g_tau = gtau_single_pole::<S>(tau, omega, beta);
let g_tau_minus_beta = gtau_single_pole::<S>(tau - beta, omega, beta);
let expected = expected_sign * g_tau;
assert!(
(expected - g_tau_minus_beta).abs() < 1e-14,
"{} periodicity violated at τ={}: G(τ)={}, G(τ-β)={}, expected={}",
stat_name,
tau,
g_tau,
g_tau_minus_beta,
expected
);
}
}
#[test]
fn test_fermionic_antiperiodicity() {
test_periodicity_generic::<Fermionic>(-1.0, "Fermionic");
}
#[test]
fn test_bosonic_periodicity() {
test_periodicity_generic::<Bosonic>(1.0, "Bosonic");
}
#[test]
fn test_generic_function_matches_specific() {
let beta = 1.0;
let omega = 5.0;
let tau = 0.5;
let g_f_specific = fermionic_single_pole(tau, omega, beta);
let g_f_generic = gtau_single_pole::<Fermionic>(tau, omega, beta);
let g_b_specific = bosonic_single_pole(tau, omega, beta);
let g_b_generic = gtau_single_pole::<Bosonic>(tau, omega, beta);
assert!(
(g_f_specific - g_f_generic).abs() < 1e-14,
"Fermionic: specific={}, generic={}",
g_f_specific,
g_f_generic
);
assert!(
(g_b_specific - g_b_generic).abs() < 1e-14,
"Bosonic: specific={}, generic={}",
g_b_specific,
g_b_generic
);
}
}
#[cfg(test)]
#[path = "dlr_tests.rs"]
mod dlr_tests;