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
147impl<S: StatisticsType> Add for MatsubaraFreq<S> {
148    type Output = Self;
149
150    fn add(self, other: Self) -> Self {
151        unsafe { Self::new_unchecked(self.n + other.n) }
152    }
153}
154
155// Operator overloading: Subtraction
156impl<S: StatisticsType> Sub for MatsubaraFreq<S> {
157    type Output = Self;
158
159    fn sub(self, other: Self) -> Self {
160        unsafe { Self::new_unchecked(self.n - other.n) }
161    }
162}
163
164// Operator overloading: Negation
165impl<S: StatisticsType> Neg for MatsubaraFreq<S> {
166    type Output = Self;
167
168    fn neg(self) -> Self {
169        unsafe { Self::new_unchecked(-self.n) }
170    }
171}
172
173// Comparison operators
174impl<S: StatisticsType> PartialEq for MatsubaraFreq<S> {
175    fn eq(&self, other: &Self) -> bool {
176        self.n == other.n
177    }
178}
179
180impl<S: StatisticsType> Eq for MatsubaraFreq<S> {}
181
182impl<S: StatisticsType> PartialOrd for MatsubaraFreq<S> {
183    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
184        Some(self.cmp(other))
185    }
186}
187
188impl<S: StatisticsType> Ord for MatsubaraFreq<S> {
189    fn cmp(&self, other: &Self) -> Ordering {
190        self.n.cmp(&other.n)
191    }
192}
193
194// Hash implementation (needed for some collections)
195impl<S: StatisticsType> std::hash::Hash for MatsubaraFreq<S> {
196    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
197        self.n.hash(state);
198    }
199}
200
201// Display implementation
202impl<S: StatisticsType> fmt::Display for MatsubaraFreq<S> {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        match self.n {
205            0 => write!(f, "0"),
206            1 => write!(f, "π/β"),
207            -1 => write!(f, "-π/β"),
208            n => write!(f, "{}π/β", n),
209        }
210    }
211}
212
213// Utility functions
214/// Get the sign of a Matsubara frequency
215#[allow(dead_code)]
216pub(crate) fn sign<S: StatisticsType>(freq: &MatsubaraFreq<S>) -> i32 {
217    if freq.n > 0 {
218        1
219    } else if freq.n < 0 {
220        -1
221    } else {
222        0
223    }
224}
225
226/// Get the fermionic sign based on statistics type
227#[allow(dead_code)]
228pub(crate) fn fermionic_sign<S: StatisticsType>() -> i32 {
229    match S::STATISTICS {
230        Statistics::Fermionic => -1,
231        Statistics::Bosonic => 1,
232    }
233}
234
235/// Create a zero frequency (bosonic only)
236#[allow(dead_code)]
237pub(crate) fn zero() -> BosonicFreq {
238    unsafe { BosonicFreq::new_unchecked(0) }
239}
240
241/// Check if a frequency is zero
242#[allow(dead_code)]
243pub(crate) fn is_zero<S: StatisticsType>(freq: &MatsubaraFreq<S>) -> bool {
244    match S::STATISTICS {
245        Statistics::Fermionic => false, // Fermionic frequencies are never zero
246        Statistics::Bosonic => freq.n == 0,
247    }
248}
249
250/// Compare two Matsubara frequencies of potentially different statistics types
251#[allow(dead_code)]
252pub(crate) fn is_less<S1: StatisticsType, S2: StatisticsType>(
253    a: &MatsubaraFreq<S1>,
254    b: &MatsubaraFreq<S2>,
255) -> bool {
256    a.get_n() < b.get_n()
257}
258
259/// Factory function to create Statistics from zeta value
260#[allow(dead_code)]
261pub(crate) fn create_statistics(zeta: i64) -> Result<Statistics, String> {
262    match zeta {
263        1 => Ok(Statistics::Fermionic),
264        0 => Ok(Statistics::Bosonic),
265        _ => Err(format!("Unknown statistics type: zeta={}", zeta)),
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_fermionic_frequency_creation() {
275        // Valid fermionic frequencies (odd n)
276        let freq1 = FermionicFreq::new(1).unwrap();
277        assert_eq!(freq1.get_n(), 1);
278
279        let freq3 = FermionicFreq::new(3).unwrap();
280        assert_eq!(freq3.get_n(), 3);
281
282        let freq_neg1 = FermionicFreq::new(-1).unwrap();
283        assert_eq!(freq_neg1.get_n(), -1);
284
285        // Invalid fermionic frequencies (even n)
286        assert!(FermionicFreq::new(0).is_err());
287        assert!(FermionicFreq::new(2).is_err());
288        assert!(FermionicFreq::new(-2).is_err());
289    }
290
291    #[test]
292    fn test_bosonic_frequency_creation() {
293        // Valid bosonic frequencies (even n)
294        let freq0 = BosonicFreq::new(0).unwrap();
295        assert_eq!(freq0.get_n(), 0);
296
297        let freq2 = BosonicFreq::new(2).unwrap();
298        assert_eq!(freq2.get_n(), 2);
299
300        let freq_neg2 = BosonicFreq::new(-2).unwrap();
301        assert_eq!(freq_neg2.get_n(), -2);
302
303        // Invalid bosonic frequencies (odd n)
304        assert!(BosonicFreq::new(1).is_err());
305        assert!(BosonicFreq::new(3).is_err());
306        assert!(BosonicFreq::new(-1).is_err());
307    }
308
309    #[test]
310    fn test_frequency_values() {
311        let beta = 2.0;
312        let pi = std::f64::consts::PI;
313
314        let fermionic = FermionicFreq::new(1).unwrap();
315        assert!((fermionic.value(beta) - pi / 2.0).abs() < 1e-14);
316
317        let bosonic = BosonicFreq::new(0).unwrap();
318        assert_eq!(bosonic.value(beta), 0.0);
319
320        let bosonic2 = BosonicFreq::new(2).unwrap();
321        assert!((bosonic2.value(beta) - pi).abs() < 1e-14);
322    }
323
324    #[test]
325    fn test_imaginary_values() {
326        let beta = 2.0;
327        let pi = std::f64::consts::PI;
328
329        let fermionic = FermionicFreq::new(1).unwrap();
330        let imag = fermionic.value_imaginary(beta);
331        assert!((imag.re - 0.0).abs() < 1e-14);
332        assert!((imag.im - pi / 2.0).abs() < 1e-14);
333    }
334
335    #[test]
336    fn test_operator_overloading() {
337        let freq1 = FermionicFreq::new(1).unwrap();
338        let freq3 = FermionicFreq::new(3).unwrap();
339
340        // Addition
341        let sum = freq1 + freq3;
342        assert_eq!(sum.get_n(), 4);
343
344        // Subtraction
345        let diff = freq3 - freq1;
346        assert_eq!(diff.get_n(), 2);
347
348        // Negation
349        let neg = -freq1;
350        assert_eq!(neg.get_n(), -1);
351    }
352
353    #[test]
354    fn test_comparison_operators() {
355        let freq1 = FermionicFreq::new(1).unwrap();
356        let freq3 = FermionicFreq::new(3).unwrap();
357        let freq1_copy = FermionicFreq::new(1).unwrap();
358
359        assert_eq!(freq1, freq1_copy);
360        assert_ne!(freq1, freq3);
361        assert!(freq1 < freq3);
362        assert!(freq3 > freq1);
363        assert!(freq1 <= freq1_copy);
364        assert!(freq1 >= freq1_copy);
365    }
366
367    #[test]
368    fn test_utility_functions() {
369        let fermionic = FermionicFreq::new(1).unwrap();
370        let bosonic = BosonicFreq::new(0).unwrap();
371        let bosonic2 = BosonicFreq::new(2).unwrap();
372
373        // Sign function
374        assert_eq!(sign(&fermionic), 1);
375        assert_eq!(sign(&bosonic), 0);
376        assert_eq!(sign(&-fermionic), -1);
377
378        // Fermionic sign
379        assert_eq!(fermionic_sign::<Fermionic>(), -1);
380        assert_eq!(fermionic_sign::<Bosonic>(), 1);
381
382        // Zero frequency
383        let zero_freq = zero();
384        assert_eq!(zero_freq.get_n(), 0);
385
386        // Is zero
387        assert!(!is_zero(&fermionic));
388        assert!(is_zero(&bosonic));
389        assert!(!is_zero(&bosonic2));
390
391        // Is less
392        assert!(is_less(&fermionic, &bosonic2));
393        assert!(!is_less(&bosonic2, &fermionic));
394    }
395
396    #[test]
397    fn test_statistics_creation() {
398        assert_eq!(create_statistics(1).unwrap(), Statistics::Fermionic);
399        assert_eq!(create_statistics(0).unwrap(), Statistics::Bosonic);
400        assert!(create_statistics(2).is_err());
401    }
402
403    #[test]
404    fn test_display() {
405        let freq1 = FermionicFreq::new(1).unwrap();
406        let freq_neg1 = FermionicFreq::new(-1).unwrap();
407        let freq0 = BosonicFreq::new(0).unwrap();
408        let freq2 = BosonicFreq::new(2).unwrap();
409
410        assert_eq!(format!("{}", freq1), "π/β");
411        assert_eq!(format!("{}", freq_neg1), "-π/β");
412        assert_eq!(format!("{}", freq0), "0");
413        assert_eq!(format!("{}", freq2), "2π/β");
414    }
415
416    #[test]
417    fn test_default_implementations() {
418        let default_fermionic = FermionicFreq::default();
419        assert_eq!(default_fermionic.get_n(), 1);
420
421        let default_bosonic = BosonicFreq::default();
422        assert_eq!(default_bosonic.get_n(), 0);
423    }
424
425    #[test]
426    fn test_conversion_to_i64() {
427        let freq = FermionicFreq::new(3).unwrap();
428        let n: i64 = freq.into();
429        assert_eq!(n, 3);
430
431        let n_direct = freq.into_i64();
432        assert_eq!(n_direct, 3);
433    }
434
435    #[test]
436    fn test_sign() {
437        let fermionic_pos = FermionicFreq::new(3).unwrap();
438        assert_eq!(sign(&fermionic_pos), 1);
439
440        let fermionic_neg = FermionicFreq::new(-5).unwrap();
441        assert_eq!(sign(&fermionic_neg), -1);
442
443        let bosonic_zero = BosonicFreq::new(0).unwrap();
444        assert_eq!(sign(&bosonic_zero), 0);
445
446        let bosonic_pos = BosonicFreq::new(4).unwrap();
447        assert_eq!(sign(&bosonic_pos), 1);
448    }
449
450    #[test]
451    fn test_fermionic_sign() {
452        assert_eq!(fermionic_sign::<Fermionic>(), -1);
453        assert_eq!(fermionic_sign::<Bosonic>(), 1);
454    }
455
456    #[test]
457    fn test_zero() {
458        let zero_freq = zero();
459        assert_eq!(zero_freq.get_n(), 0);
460        assert_eq!(zero_freq.value(1.0), 0.0);
461    }
462
463    #[test]
464    fn test_is_zero() {
465        // Fermionic frequencies are never zero
466        let fermionic = FermionicFreq::new(1).unwrap();
467        assert!(!is_zero(&fermionic));
468
469        // Bosonic zero frequency
470        let bosonic_zero = BosonicFreq::new(0).unwrap();
471        assert!(is_zero(&bosonic_zero));
472
473        // Non-zero bosonic frequency
474        let bosonic_nonzero = BosonicFreq::new(2).unwrap();
475        assert!(!is_zero(&bosonic_nonzero));
476    }
477
478    #[test]
479    fn test_is_less() {
480        let freq1 = FermionicFreq::new(1).unwrap();
481        let freq3 = FermionicFreq::new(3).unwrap();
482        assert!(is_less(&freq1, &freq3));
483        assert!(!is_less(&freq3, &freq1));
484
485        // Compare different statistics types
486        let fermionic = FermionicFreq::new(1).unwrap();
487        let bosonic = BosonicFreq::new(2).unwrap();
488        assert!(is_less(&fermionic, &bosonic));
489
490        // Same frequency
491        assert!(!is_less(&freq1, &freq1));
492    }
493
494    #[test]
495    fn test_create_statistics() {
496        // Fermionic (zeta = 1)
497        let fermionic = create_statistics(1).unwrap();
498        assert_eq!(fermionic, Statistics::Fermionic);
499
500        // Bosonic (zeta = 0)
501        let bosonic = create_statistics(0).unwrap();
502        assert_eq!(bosonic, Statistics::Bosonic);
503
504        // Invalid zeta
505        assert!(create_statistics(2).is_err());
506        assert!(create_statistics(-1).is_err());
507    }
508}