Skip to main content

sparse_ir/
freq.rs

1//! Matsubara frequency implementation for SparseIR
2//!
3//! This module provides Matsubara frequency types for both fermionic and
4//! bosonic statistics, matching the C++ implementation in freq.hpp.
5
6use num_complex::Complex64;
7use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
8use std::fmt;
9use std::ops::{Add, Neg, Sub};
10
11use crate::traits::{Bosonic, Fermionic, Statistics, StatisticsType};
12
13/// Matsubara frequency for a specific statistics type
14///
15/// This represents a Matsubara frequency ω_n = (2n + ζ)π/β where:
16/// - n is the Matsubara index (integer)
17/// - ζ is the statistics parameter (1 for fermionic, 0 for bosonic)
18/// - β is the inverse temperature
19///
20/// The statistics type S is checked at compile time to ensure type safety.
21#[derive(Debug, Clone, Copy)]
22pub struct MatsubaraFreq<S: StatisticsType> {
23    n: i64,
24    _phantom: std::marker::PhantomData<S>,
25}
26
27// Type aliases for convenience
28pub type FermionicFreq = MatsubaraFreq<Fermionic>;
29pub type BosonicFreq = MatsubaraFreq<Bosonic>;
30
31impl<S: StatisticsType> MatsubaraFreq<S> {
32    /// Get the Matsubara index n
33    pub fn n(&self) -> i64 {
34        self.n
35    }
36
37    /// Create a new Matsubara frequency
38    ///
39    /// # Arguments
40    /// * `n` - The Matsubara index
41    ///
42    /// # Returns
43    /// * `Ok(MatsubaraFreq)` if the frequency is valid for the statistics type
44    /// * `Err(String)` if the frequency is not allowed (e.g., even n for fermionic)
45    ///
46    /// # Examples
47    /// ```
48    /// use sparse_ir::freq::{FermionicFreq, BosonicFreq};
49    ///
50    /// let fermionic = FermionicFreq::new(1).unwrap();  // OK: odd n for fermionic
51    /// let bosonic = BosonicFreq::new(0).unwrap();      // OK: even n for bosonic
52    /// ```
53    pub fn new(n: i64) -> Result<Self, String> {
54        // Check if the frequency is allowed for this statistics type
55        let allowed = match S::STATISTICS {
56            Statistics::Fermionic => n % 2 != 0, // Fermionic: odd n only
57            Statistics::Bosonic => n % 2 == 0,   // Bosonic: even n only
58        };
59
60        if !allowed {
61            return Err(format!(
62                "Frequency n={} is not allowed for {} statistics",
63                n,
64                S::STATISTICS.as_str()
65            ));
66        }
67
68        Ok(Self {
69            n,
70            _phantom: std::marker::PhantomData,
71        })
72    }
73
74    /// Create a Matsubara frequency without validation
75    ///
76    /// # Safety
77    /// This function bypasses the validation in `new()`. Only use when you're
78    /// certain the frequency is valid for the statistics type.
79    pub unsafe fn new_unchecked(n: i64) -> Self {
80        Self {
81            n,
82            _phantom: std::marker::PhantomData,
83        }
84    }
85
86    /// Get the Matsubara index
87    pub fn get_n(&self) -> i64 {
88        self.n
89    }
90
91    /// Compute the real frequency value: n * π / β
92    ///
93    /// # Arguments
94    /// * `beta` - Inverse temperature
95    ///
96    /// # Returns
97    /// The real frequency value
98    pub fn value(&self, beta: f64) -> f64 {
99        self.n as f64 * std::f64::consts::PI / beta
100    }
101
102    /// Compute the imaginary frequency value: i * n * π / β
103    ///
104    /// # Arguments
105    /// * `beta` - Inverse temperature
106    ///
107    /// # Returns
108    /// The imaginary frequency value as a complex number
109    pub fn value_imaginary(&self, beta: f64) -> Complex64 {
110        Complex64::new(0.0, self.value(beta))
111    }
112
113    /// Get the statistics type
114    pub fn statistics(&self) -> Statistics {
115        S::STATISTICS
116    }
117
118    /// Convert to i64 (for compatibility with C++ operator long long())
119    pub fn into_i64(self) -> i64 {
120        self.n
121    }
122}
123
124// Default implementations
125impl Default for FermionicFreq {
126    fn default() -> Self {
127        // Default fermionic frequency is n=1 (smallest positive odd frequency)
128        unsafe { Self::new_unchecked(1) }
129    }
130}
131
132impl Default for BosonicFreq {
133    fn default() -> Self {
134        // Default bosonic frequency is n=0 (zero frequency)
135        unsafe { Self::new_unchecked(0) }
136    }
137}
138
139// Conversion to i64
140impl<S: StatisticsType> From<MatsubaraFreq<S>> for i64 {
141    fn from(freq: MatsubaraFreq<S>) -> Self {
142        freq.n
143    }
144}
145
146// Operator overloading: Addition
147// Note: For fermionic frequencies, adding two odd numbers gives an even number,
148// which is NOT a valid fermionic frequency. Use with care.
149impl<S: StatisticsType> Add for MatsubaraFreq<S> {
150    type Output = Self;
151
152    fn add(self, other: Self) -> Self {
153        let result = self.n + other.n;
154        debug_assert!(
155            Self::new(result).is_ok(),
156            "MatsubaraFreq::Add produced invalid frequency n={} for {:?} statistics",
157            result,
158            S::STATISTICS,
159        );
160        unsafe { Self::new_unchecked(result) }
161    }
162}
163
164// Operator overloading: Subtraction
165// Note: Same caveat as Add regarding parity.
166impl<S: StatisticsType> Sub for MatsubaraFreq<S> {
167    type Output = Self;
168
169    fn sub(self, other: Self) -> Self {
170        let result = self.n - other.n;
171        debug_assert!(
172            Self::new(result).is_ok(),
173            "MatsubaraFreq::Sub produced invalid frequency n={} for {:?} statistics",
174            result,
175            S::STATISTICS,
176        );
177        unsafe { Self::new_unchecked(result) }
178    }
179}
180
181// Operator overloading: Negation
182impl<S: StatisticsType> Neg for MatsubaraFreq<S> {
183    type Output = Self;
184
185    fn neg(self) -> Self {
186        // Negation preserves parity, so this is always valid
187        unsafe { Self::new_unchecked(-self.n) }
188    }
189}
190
191// Comparison operators
192impl<S: StatisticsType> PartialEq for MatsubaraFreq<S> {
193    fn eq(&self, other: &Self) -> bool {
194        self.n == other.n
195    }
196}
197
198impl<S: StatisticsType> Eq for MatsubaraFreq<S> {}
199
200impl<S: StatisticsType> PartialOrd for MatsubaraFreq<S> {
201    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
202        Some(self.cmp(other))
203    }
204}
205
206impl<S: StatisticsType> Ord for MatsubaraFreq<S> {
207    fn cmp(&self, other: &Self) -> Ordering {
208        self.n.cmp(&other.n)
209    }
210}
211
212// Hash implementation (needed for some collections)
213impl<S: StatisticsType> std::hash::Hash for MatsubaraFreq<S> {
214    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
215        self.n.hash(state);
216    }
217}
218
219// Display implementation
220impl<S: StatisticsType> fmt::Display for MatsubaraFreq<S> {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        match self.n {
223            0 => write!(f, "0"),
224            1 => write!(f, "π/β"),
225            -1 => write!(f, "-π/β"),
226            n => write!(f, "{}π/β", n),
227        }
228    }
229}
230
231// Utility functions
232/// Get the sign of a Matsubara frequency
233#[allow(dead_code)]
234pub(crate) fn sign<S: StatisticsType>(freq: &MatsubaraFreq<S>) -> i32 {
235    if freq.n > 0 {
236        1
237    } else if freq.n < 0 {
238        -1
239    } else {
240        0
241    }
242}
243
244/// Get the fermionic sign based on statistics type
245#[allow(dead_code)]
246pub(crate) fn fermionic_sign<S: StatisticsType>() -> i32 {
247    match S::STATISTICS {
248        Statistics::Fermionic => -1,
249        Statistics::Bosonic => 1,
250    }
251}
252
253/// Create a zero frequency (bosonic only)
254#[allow(dead_code)]
255pub(crate) fn zero() -> BosonicFreq {
256    unsafe { BosonicFreq::new_unchecked(0) }
257}
258
259/// Check if a frequency is zero
260#[allow(dead_code)]
261pub(crate) fn is_zero<S: StatisticsType>(freq: &MatsubaraFreq<S>) -> bool {
262    match S::STATISTICS {
263        Statistics::Fermionic => false, // Fermionic frequencies are never zero
264        Statistics::Bosonic => freq.n == 0,
265    }
266}
267
268/// Compare two Matsubara frequencies of potentially different statistics types
269#[allow(dead_code)]
270pub(crate) fn is_less<S1: StatisticsType, S2: StatisticsType>(
271    a: &MatsubaraFreq<S1>,
272    b: &MatsubaraFreq<S2>,
273) -> bool {
274    a.get_n() < b.get_n()
275}
276
277/// Factory function to create Statistics from zeta value
278#[allow(dead_code)]
279pub(crate) fn create_statistics(zeta: i64) -> Result<Statistics, String> {
280    match zeta {
281        1 => Ok(Statistics::Fermionic),
282        0 => Ok(Statistics::Bosonic),
283        _ => Err(format!("Unknown statistics type: zeta={}", zeta)),
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn test_fermionic_frequency_creation() {
293        // Valid fermionic frequencies (odd n)
294        let freq1 = FermionicFreq::new(1).unwrap();
295        assert_eq!(freq1.get_n(), 1);
296
297        let freq3 = FermionicFreq::new(3).unwrap();
298        assert_eq!(freq3.get_n(), 3);
299
300        let freq_neg1 = FermionicFreq::new(-1).unwrap();
301        assert_eq!(freq_neg1.get_n(), -1);
302
303        // Invalid fermionic frequencies (even n)
304        assert!(FermionicFreq::new(0).is_err());
305        assert!(FermionicFreq::new(2).is_err());
306        assert!(FermionicFreq::new(-2).is_err());
307    }
308
309    #[test]
310    fn test_bosonic_frequency_creation() {
311        // Valid bosonic frequencies (even n)
312        let freq0 = BosonicFreq::new(0).unwrap();
313        assert_eq!(freq0.get_n(), 0);
314
315        let freq2 = BosonicFreq::new(2).unwrap();
316        assert_eq!(freq2.get_n(), 2);
317
318        let freq_neg2 = BosonicFreq::new(-2).unwrap();
319        assert_eq!(freq_neg2.get_n(), -2);
320
321        // Invalid bosonic frequencies (odd n)
322        assert!(BosonicFreq::new(1).is_err());
323        assert!(BosonicFreq::new(3).is_err());
324        assert!(BosonicFreq::new(-1).is_err());
325    }
326
327    #[test]
328    fn test_frequency_values() {
329        let beta = 2.0;
330        let pi = std::f64::consts::PI;
331
332        let fermionic = FermionicFreq::new(1).unwrap();
333        assert!((fermionic.value(beta) - pi / 2.0).abs() < 1e-14);
334
335        let bosonic = BosonicFreq::new(0).unwrap();
336        assert_eq!(bosonic.value(beta), 0.0);
337
338        let bosonic2 = BosonicFreq::new(2).unwrap();
339        assert!((bosonic2.value(beta) - pi).abs() < 1e-14);
340    }
341
342    #[test]
343    fn test_imaginary_values() {
344        let beta = 2.0;
345        let pi = std::f64::consts::PI;
346
347        let fermionic = FermionicFreq::new(1).unwrap();
348        let imag = fermionic.value_imaginary(beta);
349        assert!((imag.re - 0.0).abs() < 1e-14);
350        assert!((imag.im - pi / 2.0).abs() < 1e-14);
351    }
352
353    #[test]
354    fn test_operator_overloading() {
355        // Bosonic: even + even = even (valid)
356        let bfreq2 = BosonicFreq::new(2).unwrap();
357        let bfreq4 = BosonicFreq::new(4).unwrap();
358
359        let sum = bfreq2 + bfreq4;
360        assert_eq!(sum.get_n(), 6);
361
362        let diff = bfreq4 - bfreq2;
363        assert_eq!(diff.get_n(), 2);
364
365        // Negation preserves parity (valid for both statistics)
366        let freq1 = FermionicFreq::new(1).unwrap();
367        let neg = -freq1;
368        assert_eq!(neg.get_n(), -1);
369
370        let neg_b = -bfreq2;
371        assert_eq!(neg_b.get_n(), -2);
372    }
373
374    #[test]
375    fn test_comparison_operators() {
376        let freq1 = FermionicFreq::new(1).unwrap();
377        let freq3 = FermionicFreq::new(3).unwrap();
378        let freq1_copy = FermionicFreq::new(1).unwrap();
379
380        assert_eq!(freq1, freq1_copy);
381        assert_ne!(freq1, freq3);
382        assert!(freq1 < freq3);
383        assert!(freq3 > freq1);
384        assert!(freq1 <= freq1_copy);
385        assert!(freq1 >= freq1_copy);
386    }
387
388    #[test]
389    fn test_utility_functions() {
390        let fermionic = FermionicFreq::new(1).unwrap();
391        let bosonic = BosonicFreq::new(0).unwrap();
392        let bosonic2 = BosonicFreq::new(2).unwrap();
393
394        // Sign function
395        assert_eq!(sign(&fermionic), 1);
396        assert_eq!(sign(&bosonic), 0);
397        assert_eq!(sign(&-fermionic), -1);
398
399        // Fermionic sign
400        assert_eq!(fermionic_sign::<Fermionic>(), -1);
401        assert_eq!(fermionic_sign::<Bosonic>(), 1);
402
403        // Zero frequency
404        let zero_freq = zero();
405        assert_eq!(zero_freq.get_n(), 0);
406
407        // Is zero
408        assert!(!is_zero(&fermionic));
409        assert!(is_zero(&bosonic));
410        assert!(!is_zero(&bosonic2));
411
412        // Is less
413        assert!(is_less(&fermionic, &bosonic2));
414        assert!(!is_less(&bosonic2, &fermionic));
415    }
416
417    #[test]
418    fn test_statistics_creation() {
419        assert_eq!(create_statistics(1).unwrap(), Statistics::Fermionic);
420        assert_eq!(create_statistics(0).unwrap(), Statistics::Bosonic);
421        assert!(create_statistics(2).is_err());
422    }
423
424    #[test]
425    fn test_display() {
426        let freq1 = FermionicFreq::new(1).unwrap();
427        let freq_neg1 = FermionicFreq::new(-1).unwrap();
428        let freq0 = BosonicFreq::new(0).unwrap();
429        let freq2 = BosonicFreq::new(2).unwrap();
430
431        assert_eq!(format!("{}", freq1), "π/β");
432        assert_eq!(format!("{}", freq_neg1), "-π/β");
433        assert_eq!(format!("{}", freq0), "0");
434        assert_eq!(format!("{}", freq2), "2π/β");
435    }
436
437    #[test]
438    fn test_default_implementations() {
439        let default_fermionic = FermionicFreq::default();
440        assert_eq!(default_fermionic.get_n(), 1);
441
442        let default_bosonic = BosonicFreq::default();
443        assert_eq!(default_bosonic.get_n(), 0);
444    }
445
446    #[test]
447    fn test_conversion_to_i64() {
448        let freq = FermionicFreq::new(3).unwrap();
449        let n: i64 = freq.into();
450        assert_eq!(n, 3);
451
452        let n_direct = freq.into_i64();
453        assert_eq!(n_direct, 3);
454    }
455
456    #[test]
457    fn test_sign() {
458        let fermionic_pos = FermionicFreq::new(3).unwrap();
459        assert_eq!(sign(&fermionic_pos), 1);
460
461        let fermionic_neg = FermionicFreq::new(-5).unwrap();
462        assert_eq!(sign(&fermionic_neg), -1);
463
464        let bosonic_zero = BosonicFreq::new(0).unwrap();
465        assert_eq!(sign(&bosonic_zero), 0);
466
467        let bosonic_pos = BosonicFreq::new(4).unwrap();
468        assert_eq!(sign(&bosonic_pos), 1);
469    }
470
471    #[test]
472    fn test_fermionic_sign() {
473        assert_eq!(fermionic_sign::<Fermionic>(), -1);
474        assert_eq!(fermionic_sign::<Bosonic>(), 1);
475    }
476
477    #[test]
478    fn test_zero() {
479        let zero_freq = zero();
480        assert_eq!(zero_freq.get_n(), 0);
481        assert_eq!(zero_freq.value(1.0), 0.0);
482    }
483
484    #[test]
485    fn test_is_zero() {
486        // Fermionic frequencies are never zero
487        let fermionic = FermionicFreq::new(1).unwrap();
488        assert!(!is_zero(&fermionic));
489
490        // Bosonic zero frequency
491        let bosonic_zero = BosonicFreq::new(0).unwrap();
492        assert!(is_zero(&bosonic_zero));
493
494        // Non-zero bosonic frequency
495        let bosonic_nonzero = BosonicFreq::new(2).unwrap();
496        assert!(!is_zero(&bosonic_nonzero));
497    }
498
499    #[test]
500    fn test_is_less() {
501        let freq1 = FermionicFreq::new(1).unwrap();
502        let freq3 = FermionicFreq::new(3).unwrap();
503        assert!(is_less(&freq1, &freq3));
504        assert!(!is_less(&freq3, &freq1));
505
506        // Compare different statistics types
507        let fermionic = FermionicFreq::new(1).unwrap();
508        let bosonic = BosonicFreq::new(2).unwrap();
509        assert!(is_less(&fermionic, &bosonic));
510
511        // Same frequency
512        assert!(!is_less(&freq1, &freq1));
513    }
514
515    #[test]
516    fn test_create_statistics() {
517        // Fermionic (zeta = 1)
518        let fermionic = create_statistics(1).unwrap();
519        assert_eq!(fermionic, Statistics::Fermionic);
520
521        // Bosonic (zeta = 0)
522        let bosonic = create_statistics(0).unwrap();
523        assert_eq!(bosonic, Statistics::Bosonic);
524
525        // Invalid zeta
526        assert!(create_statistics(2).is_err());
527        assert!(create_statistics(-1).is_err());
528    }
529}