lattice_qcd_rs/su3.rs
1//! Module for SU(3) matrices and su(3) (that is the generators of SU(3) )
2//!
3//! The module defines the SU(3) generator we use the same matrices as on
4//! [wikipedia](https://en.wikipedia.org/w/index.php?title=Gell-Mann_matrices&oldid=988659438#Matrices)
5//! **divided by two** such that `Tr(T^a T^b) = \delta^{ab} /2 `.
6
7use std::iter::FusedIterator;
8
9use na::{base::allocator::Allocator, ComplexField, DefaultAllocator, OMatrix};
10use rand::Rng;
11use rand_distr::{Distribution, Uniform};
12
13use super::{
14 field::Su3Adjoint, su2, utils, utils::FactorialNumber, CMatrix2, CMatrix3, Complex, Real, I,
15 ONE, ZERO,
16};
17
18/// SU(3) generator
19/// ```textrust
20/// 0 0.5 0
21/// 0.5 0 0
22/// 0 0 0
23/// ```
24pub const GENERATOR_1: CMatrix3 = CMatrix3::new(
25 ZERO,
26 Complex::new(0.5_f64, 0_f64),
27 ZERO,
28 // ---
29 Complex::new(0.5_f64, 0_f64),
30 ZERO,
31 ZERO,
32 // ---
33 ZERO,
34 ZERO,
35 ZERO,
36);
37
38/// SU(3) generator
39/// ```textrust
40/// 0 -i/2 0
41/// i/2 0 0
42/// 0 0 0
43/// ```
44pub const GENERATOR_2: CMatrix3 = CMatrix3::new(
45 ZERO,
46 Complex::new(0_f64, -0.5_f64),
47 ZERO,
48 // ---
49 Complex::new(0_f64, 0.5_f64),
50 ZERO,
51 ZERO,
52 // ---
53 ZERO,
54 ZERO,
55 ZERO,
56);
57
58/// SU(3) generator
59/// ```textrust
60/// 0.5 0 0
61/// 0 -0.5 0
62/// 0 0 0
63/// ```
64pub const GENERATOR_3: CMatrix3 = CMatrix3::new(
65 Complex::new(0.5_f64, 0_f64),
66 ZERO,
67 ZERO,
68 // ---
69 ZERO,
70 Complex::new(-0.5_f64, 0_f64),
71 ZERO,
72 // ---
73 ZERO,
74 ZERO,
75 ZERO,
76);
77
78/// SU(3) generator
79/// ```textrust
80/// 0 0 0.5
81/// 0 0 0
82/// 0.5 0 0
83/// ```
84pub const GENERATOR_4: CMatrix3 = CMatrix3::new(
85 ZERO,
86 ZERO,
87 Complex::new(0.5_f64, 0_f64),
88 // ---
89 ZERO,
90 ZERO,
91 ZERO,
92 // ---
93 Complex::new(0.5_f64, 0_f64),
94 ZERO,
95 ZERO,
96);
97
98/// SU(3) generator
99/// ```textrust
100/// 0 0 -i/2
101/// 0 0 0
102/// i/2 0 0
103/// ```
104pub const GENERATOR_5: CMatrix3 = CMatrix3::new(
105 ZERO,
106 ZERO,
107 Complex::new(0_f64, -0.5_f64),
108 // ---
109 ZERO,
110 ZERO,
111 ZERO,
112 // ---
113 Complex::new(0_f64, 0.5_f64),
114 ZERO,
115 ZERO,
116);
117
118/// SU(3) generator
119/// ```textrust
120/// 0 0 0
121/// 0 0 0.5
122/// 0 0.5 0
123/// ```
124pub const GENERATOR_6: CMatrix3 = CMatrix3::new(
125 ZERO,
126 ZERO,
127 ZERO,
128 // ---
129 ZERO,
130 ZERO,
131 Complex::new(0.5_f64, 0_f64),
132 // ---
133 ZERO,
134 Complex::new(0.5_f64, 0_f64),
135 ZERO,
136);
137
138/// SU(3) generator
139/// ```textrust
140/// 0 0 0
141/// 0 0 -i/2
142/// 0 i/2 0
143/// ```
144pub const GENERATOR_7: CMatrix3 = CMatrix3::new(
145 ZERO,
146 ZERO,
147 ZERO,
148 // ---
149 ZERO,
150 ZERO,
151 Complex::new(0_f64, -0.5_f64),
152 // ---
153 ZERO,
154 Complex::new(0_f64, 0.5_f64),
155 ZERO,
156);
157
158/// SU(3) generator
159/// ```textrust
160/// 0.5/sqrt(3) 0 0
161/// 0 0.5/sqrt(3) 0
162/// 0 0 -1/sqrt(3)
163/// ```
164pub const GENERATOR_8: CMatrix3 = CMatrix3::new(
165 Complex::new(ONE_OVER_2_SQRT_3, 0_f64),
166 ZERO,
167 ZERO,
168 // ---
169 ZERO,
170 Complex::new(ONE_OVER_2_SQRT_3, 0_f64),
171 ZERO,
172 // ---
173 ZERO,
174 ZERO,
175 Complex::new(MINUS_ONE_OVER_SQRT_3, 0_f64),
176);
177
178/// -1/sqrt(3), used for [`GENERATOR_8`].
179const MINUS_ONE_OVER_SQRT_3: f64 = -0.577_350_269_189_625_8_f64;
180/// 2/sqrt(3), used for [`GENERATOR_8`].
181const ONE_OVER_2_SQRT_3: f64 = 0.288_675_134_594_812_9_f64;
182
183/// list of SU(3) generators
184/// they are normalize such that `Tr(T^a T^b) = \frac{1}{2}\delta^{ab}`
185pub const GENERATORS: [&CMatrix3; 8] = [
186 &GENERATOR_1,
187 &GENERATOR_2,
188 &GENERATOR_3,
189 &GENERATOR_4,
190 &GENERATOR_5,
191 &GENERATOR_6,
192 &GENERATOR_7,
193 &GENERATOR_8,
194];
195
196/// Exponential of matrices.
197///
198/// Note prefer using [`su3_exp_r`] and [`su3_exp_i`] when possible.
199#[deprecated(since = "0.2.0", note = "Please use nalgebra exp instead")]
200pub trait MatrixExp<T = Self> {
201 /// Return the exponential of the matrix
202 fn exp(self) -> T;
203}
204
205/// Basic implementation of matrix exponential for complex matrices.
206/// It does it by first diagonalizing the matrix then exponentiate the diagonal
207/// and retransforms it back to the original basis.
208#[allow(deprecated)]
209impl<T, D> MatrixExp<OMatrix<T, D, D>> for OMatrix<T, D, D>
210where
211 T: ComplexField + Copy,
212 D: na::DimName + na::DimSub<na::U1>,
213 DefaultAllocator: Allocator<T, D, na::DimDiff<D, na::U1>>
214 + Allocator<T, na::DimDiff<D, na::U1>>
215 + Allocator<T, D, D>
216 + Allocator<T, D>,
217 OMatrix<T, D, D>: Clone,
218{
219 fn exp(self) -> OMatrix<T, D, D> {
220 let decomposition = self.schur();
221 // a complex matrix is always diagonalizable
222 let eigens = decomposition.eigenvalues().unwrap();
223 let new_matrix = Self::from_diagonal(&eigens.map(|el| el.exp()));
224 let (q, _) = decomposition.unpack();
225 // q is always invertible
226 q.clone() * new_matrix * q.try_inverse().unwrap()
227 }
228}
229
230/// Create a [`super::CMatrix3`] of the form `[v1, v2, v1* x v2*]` where v1* is the conjugate of v1 and
231/// `x` is the cross product.
232///
233/// # Example
234/// ```
235/// # use nalgebra::{Vector3, Complex, Matrix3};
236/// # use lattice_qcd_rs::{assert_eq_matrix};
237/// # use lattice_qcd_rs::su3::create_matrix_from_2_vector;
238/// let v1 = Vector3::new(
239/// Complex::new(1_f64, 0_f64),
240/// Complex::new(0_f64, 0_f64),
241/// Complex::new(0_f64, 0_f64),
242/// );
243/// let v2 = Vector3::new(
244/// Complex::new(0_f64, 0_f64),
245/// Complex::new(1_f64, 0_f64),
246/// Complex::new(0_f64, 0_f64),
247/// );
248/// assert_eq_matrix!(
249/// Matrix3::identity(),
250/// create_matrix_from_2_vector(v1, v2),
251/// 0.000_000_1_f64
252/// );
253///
254/// let v1 = Vector3::new(
255/// Complex::new(0_f64, 1_f64),
256/// Complex::new(0_f64, 0_f64),
257/// Complex::new(0_f64, 0_f64),
258/// );
259/// let v2 = Vector3::new(
260/// Complex::new(0_f64, 0_f64),
261/// Complex::new(1_f64, 0_f64),
262/// Complex::new(0_f64, 0_f64),
263/// );
264/// let m = Matrix3::new(
265/// Complex::new(0_f64, 1_f64),
266/// Complex::new(0_f64, 0_f64),
267/// Complex::new(0_f64, 0_f64),
268/// // ---
269/// Complex::new(0_f64, 0_f64),
270/// Complex::new(1_f64, 0_f64),
271/// Complex::new(0_f64, 0_f64),
272/// // ---
273/// Complex::new(0_f64, 0_f64),
274/// Complex::new(0_f64, 0_f64),
275/// Complex::new(0_f64, -1_f64),
276/// );
277/// assert_eq_matrix!(m, create_matrix_from_2_vector(v1, v2), 0.000_000_1_f64);
278/// ```
279pub fn create_matrix_from_2_vector(
280 v1: na::Vector3<Complex>,
281 v2: na::Vector3<Complex>,
282) -> na::Matrix3<Complex> {
283 // TODO find a better way
284 let cross_vec: na::Vector3<Complex> = v1.conjugate().cross(&v2.conjugate());
285 let iter = v1.iter().chain(v2.iter()).chain(cross_vec.iter()).copied();
286 na::Matrix3::<Complex>::from_iterator(iter)
287}
288
289/// get an orthonormalize matrix from two vector.
290fn ortho_matrix_from_2_vector(v1: na::Vector3<Complex>, v2: na::Vector3<Complex>) -> CMatrix3 {
291 let v1_new = v1.try_normalize(f64::EPSILON).unwrap_or(v1);
292 let v2_temp = v2 - v1_new * v1_new.conjugate().dot(&v2);
293 let v2_new = v2_temp.try_normalize(f64::EPSILON).unwrap_or(v2_temp);
294 create_matrix_from_2_vector(v1_new, v2_new)
295}
296
297/// Try orthonormalize the given matrix.
298// TODO example
299pub fn orthonormalize_matrix(matrix: &CMatrix3) -> CMatrix3 {
300 let v1 = na::Vector3::from_iterator(matrix.column(0).iter().copied());
301 let v2 = na::Vector3::from_iterator(matrix.column(1).iter().copied());
302 ortho_matrix_from_2_vector(v1, v2)
303}
304
305/// Orthonormalize the given matrix by mutating its content.
306// TODO example
307pub fn orthonormalize_matrix_mut(matrix: &mut CMatrix3) {
308 *matrix = orthonormalize_matrix(matrix);
309}
310
311/// Generate Uniformly distributed SU(3) matrix.
312///
313/// # Example
314/// ```
315/// # use lattice_qcd_rs::{assert_matrix_is_su_3,su3::random_su3};
316/// # use rand::SeedableRng;
317/// # let mut rng = rand::rngs::StdRng::seed_from_u64(0);
318/// for _ in 0..10 {
319/// assert_matrix_is_su_3!(random_su3(&mut rng), 9_f64 * f64::EPSILON);
320/// }
321/// ```
322pub fn random_su3<Rng>(rng: &mut Rng) -> CMatrix3
323where
324 Rng: rand::Rng + ?Sized,
325{
326 rand_su3_with_dis(rng, &rand::distributions::Uniform::new(-1_f64, 1_f64))
327}
328
329/// Get a random SU3 with the given distribution.
330///
331/// The given distribution can be quite opaque on the distribution of the SU(3) matrix.
332/// For a matrix Uniformly distributed among SU(3) use [`get_random_su3`].
333/// For a matrix close to unity use [`get_random_su3_close_to_unity`]
334fn rand_su3_with_dis<Rng>(rng: &mut Rng, d: &impl rand_distr::Distribution<Real>) -> CMatrix3
335where
336 Rng: rand::Rng + ?Sized,
337{
338 let mut v1 = random_vec_3(rng, d);
339 while v1.norm() <= f64::EPSILON {
340 v1 = random_vec_3(rng, d);
341 }
342 let mut v2 = random_vec_3(rng, d);
343 while v1.dot(&v2).modulus() <= f64::EPSILON {
344 v2 = random_vec_3(rng, d);
345 }
346 ortho_matrix_from_2_vector(v1, v2)
347}
348
349/// get a random [`na::Vector3<Complex>`].
350fn random_vec_3<Rng>(rng: &mut Rng, d: &impl rand_distr::Distribution<Real>) -> na::Vector3<Complex>
351where
352 Rng: rand::Rng + ?Sized,
353{
354 na::Vector3::from_fn(|_, _| Complex::new(d.sample(rng), d.sample(rng)))
355}
356
357/// Get a radom SU(3) matrix close to `[get_r] (+/- 1) * [get_s] (+/- 1) * [get_t] (+/- 1)`.
358///
359/// Note that it diverges from SU(3) slightly.
360/// `spread_parameter` should be between between 0 and 1 both excluded to generate valid data.
361/// outside this bound it will not panic but can have unexpected results.
362///
363/// # Example
364/// ```
365/// # use lattice_qcd_rs::{assert_matrix_is_su_3,su3::random_su3_close_to_unity};
366/// # use rand::SeedableRng;
367/// # let mut rng = rand::rngs::StdRng::seed_from_u64(0);
368/// for _ in 0..10 {
369/// assert_matrix_is_su_3!(
370/// random_su3_close_to_unity(0.000_000_001_f64, &mut rng),
371/// 0.000_000_1_f64
372/// );
373/// }
374/// ```
375/// but it will be not close to SU(3) up to [`f64::EPSILON`].
376/// ```should_panic
377/// # use lattice_qcd_rs::{assert_matrix_is_su_3,su3::random_su3_close_to_unity};
378/// # use rand::SeedableRng;
379/// # let mut rng = rand::rngs::StdRng::seed_from_u64(0);
380/// assert_matrix_is_su_3!(
381/// random_su3_close_to_unity(0.000_000_001_f64, &mut rng),
382/// f64::EPSILON * 180_f64
383/// );
384/// ```
385pub fn random_su3_close_to_unity<R>(spread_parameter: Real, rng: &mut R) -> CMatrix3
386where
387 R: rand::Rng + ?Sized,
388{
389 let r = get_r(su2::random_su2_close_to_unity(spread_parameter, rng));
390 let s = get_s(su2::random_su2_close_to_unity(spread_parameter, rng));
391 let t = get_t(su2::random_su2_close_to_unity(spread_parameter, rng));
392 let distribution = rand::distributions::Bernoulli::new(0.5_f64).unwrap();
393 let mut x = r * s * t;
394 if distribution.sample(rng) {
395 x = x.adjoint();
396 }
397 x
398}
399
400/// Embed a Matrix2 inside Matrix3 leaving the last row and column be the same as identity.
401///
402/// # Example
403/// ```
404/// # use lattice_qcd_rs::{CMatrix3, CMatrix2, su3::get_r, Complex, assert_eq_matrix};
405/// let m1 = CMatrix2::new(
406/// Complex::from(1_f64),
407/// Complex::from(2_f64),
408/// // ---
409/// Complex::from(3_f64),
410/// Complex::from(4_f64),
411/// );
412///
413/// let m2 = CMatrix3::new(
414/// Complex::from(1_f64),
415/// Complex::from(2_f64),
416/// Complex::from(0_f64),
417/// // ---
418/// Complex::from(3_f64),
419/// Complex::from(4_f64),
420/// Complex::from(0_f64),
421/// // ---
422/// Complex::from(0_f64),
423/// Complex::from(0_f64),
424/// Complex::from(1_f64),
425/// );
426/// assert_eq_matrix!(get_r(m1), m2, f64::EPSILON);
427/// ```
428pub fn get_r(m: CMatrix2) -> CMatrix3 {
429 CMatrix3::new(
430 m[(0, 0)],
431 m[(0, 1)],
432 ZERO,
433 // ---
434 m[(1, 0)],
435 m[(1, 1)],
436 ZERO,
437 // ---
438 ZERO,
439 ZERO,
440 ONE,
441 )
442}
443
444/// Embed a Matrix2 inside Matrix3 leaving the second row and column be the same as identity.
445///
446/// # Example
447/// ```
448/// # use lattice_qcd_rs::{CMatrix3, CMatrix2, su3::get_s, Complex, assert_eq_matrix};
449/// let m1 = CMatrix2::new(
450/// Complex::from(1_f64),
451/// Complex::from(2_f64),
452/// // ---
453/// Complex::from(3_f64),
454/// Complex::from(4_f64),
455/// );
456///
457/// let m2 = CMatrix3::new(
458/// Complex::from(1_f64),
459/// Complex::from(0_f64),
460/// Complex::from(2_f64),
461/// // ---
462/// Complex::from(0_f64),
463/// Complex::from(1_f64),
464/// Complex::from(0_f64),
465/// // ---
466/// Complex::from(3_f64),
467/// Complex::from(0_f64),
468/// Complex::from(4_f64),
469/// );
470/// assert_eq_matrix!(get_s(m1), m2, f64::EPSILON);
471/// ```
472pub fn get_s(m: CMatrix2) -> CMatrix3 {
473 CMatrix3::new(
474 m[(0, 0)],
475 ZERO,
476 m[(0, 1)],
477 // ---
478 ZERO,
479 ONE,
480 ZERO,
481 // ---
482 m[(1, 0)],
483 ZERO,
484 m[(1, 1)],
485 )
486}
487
488/// Embed a Matrix2 inside Matrix3 leaving the first row and column be the same as identity.
489///
490/// # Example
491/// ```
492/// # use lattice_qcd_rs::{CMatrix3, CMatrix2, su3::get_t, Complex, assert_eq_matrix};
493/// let m1 = CMatrix2::new(
494/// Complex::from(1_f64),
495/// Complex::from(2_f64),
496/// // ---
497/// Complex::from(3_f64),
498/// Complex::from(4_f64),
499/// );
500///
501/// let m2 = CMatrix3::new(
502/// Complex::from(1_f64),
503/// Complex::from(0_f64),
504/// Complex::from(0_f64),
505/// // ---
506/// Complex::from(0_f64),
507/// Complex::from(1_f64),
508/// Complex::from(2_f64),
509/// // ---
510/// Complex::from(0_f64),
511/// Complex::from(3_f64),
512/// Complex::from(4_f64),
513/// );
514/// assert_eq_matrix!(get_t(m1), m2, f64::EPSILON);
515/// ```
516pub fn get_t(m: CMatrix2) -> CMatrix3 {
517 CMatrix3::new(
518 ONE,
519 ZERO,
520 ZERO,
521 // ---
522 ZERO,
523 m[(0, 0)],
524 m[(0, 1)],
525 // ---
526 ZERO,
527 m[(1, 0)],
528 m[(1, 1)],
529 )
530}
531
532/// get the [`CMatrix2`] sub block corresponding to [`get_r`]
533///
534/// # Example
535/// ```
536/// # use lattice_qcd_rs::{CMatrix3, CMatrix2, su3::get_sub_block_r, Complex, assert_eq_matrix};
537/// let m1 = CMatrix3::new(
538/// Complex::from(1_f64),
539/// Complex::from(2_f64),
540/// Complex::from(3_f64),
541/// // ---
542/// Complex::from(4_f64),
543/// Complex::from(5_f64),
544/// Complex::from(6_f64),
545/// // ---
546/// Complex::from(7_f64),
547/// Complex::from(8_f64),
548/// Complex::from(9_f64),
549/// );
550/// let m2 = CMatrix2::new(
551/// Complex::from(1_f64),
552/// Complex::from(2_f64),
553/// // ---
554/// Complex::from(4_f64),
555/// Complex::from(5_f64),
556/// );
557/// assert_eq_matrix!(get_sub_block_r(m1), m2, f64::EPSILON);
558/// ```
559pub fn get_sub_block_r(m: CMatrix3) -> CMatrix2 {
560 CMatrix2::new(
561 m[(0, 0)],
562 m[(0, 1)],
563 // ---
564 m[(1, 0)],
565 m[(1, 1)],
566 )
567}
568
569/// get the [`CMatrix2`] sub block corresponding to [`get_s`]
570///
571/// # Example
572/// ```
573/// # use lattice_qcd_rs::{CMatrix3, CMatrix2, su3::get_sub_block_s, Complex, assert_eq_matrix};
574/// let m1 = CMatrix3::new(
575/// Complex::from(1_f64),
576/// Complex::from(2_f64),
577/// Complex::from(3_f64),
578/// // ---
579/// Complex::from(4_f64),
580/// Complex::from(5_f64),
581/// Complex::from(6_f64),
582/// // ---
583/// Complex::from(7_f64),
584/// Complex::from(8_f64),
585/// Complex::from(9_f64),
586/// );
587/// let m2 = CMatrix2::new(
588/// Complex::from(1_f64),
589/// Complex::from(3_f64),
590/// // ---
591/// Complex::from(7_f64),
592/// Complex::from(9_f64),
593/// );
594/// assert_eq_matrix!(get_sub_block_s(m1), m2, f64::EPSILON);
595/// ```
596pub fn get_sub_block_s(m: CMatrix3) -> CMatrix2 {
597 CMatrix2::new(
598 m[(0, 0)],
599 m[(0, 2)],
600 // ---
601 m[(2, 0)],
602 m[(2, 2)],
603 )
604}
605
606/// Get the [`CMatrix2`] sub block corresponding to [`get_t`]
607///
608/// # Example
609/// ```
610/// # use lattice_qcd_rs::{CMatrix3, CMatrix2, su3::get_sub_block_t, Complex, assert_eq_matrix};
611/// let m1 = CMatrix3::new(
612/// Complex::from(1_f64),
613/// Complex::from(2_f64),
614/// Complex::from(3_f64),
615/// // ---
616/// Complex::from(4_f64),
617/// Complex::from(5_f64),
618/// Complex::from(6_f64),
619/// // ---
620/// Complex::from(7_f64),
621/// Complex::from(8_f64),
622/// Complex::from(9_f64),
623/// );
624/// let m2 = CMatrix2::new(
625/// Complex::from(5_f64),
626/// Complex::from(6_f64),
627/// // ---
628/// Complex::from(8_f64),
629/// Complex::from(9_f64),
630/// );
631/// assert_eq_matrix!(get_sub_block_t(m1), m2, f64::EPSILON);
632/// ```
633pub fn get_sub_block_t(m: CMatrix3) -> CMatrix2 {
634 CMatrix2::new(
635 m[(1, 1)],
636 m[(1, 2)],
637 // ---
638 m[(2, 1)],
639 m[(2, 2)],
640 )
641}
642
643/// Get the unormalize SU(2) sub matrix of an SU(3) matrix corresponding to the "r" sub block see
644/// [`get_sub_block_r`] and [`su2::project_to_su2_unorm`].
645pub fn get_su2_r_unorm(input: CMatrix3) -> CMatrix2 {
646 su2::project_to_su2_unorm(get_sub_block_r(input))
647}
648
649/// Get the unormalize SU(2) sub matrix of an SU(3) matrix corresponding to the "s" sub block see
650/// [`get_sub_block_s`] and [`su2::project_to_su2_unorm`].
651pub fn get_su2_s_unorm(input: CMatrix3) -> CMatrix2 {
652 su2::project_to_su2_unorm(get_sub_block_s(input))
653}
654
655/// Get the unormalize SU(2) sub matrix of an SU(3) matrix corresponding to the "t" sub block see
656/// [`get_sub_block_t`] and [`su2::project_to_su2_unorm`].
657pub fn get_su2_t_unorm(input: CMatrix3) -> CMatrix2 {
658 su2::project_to_su2_unorm(get_sub_block_t(input))
659}
660
661/// Get the three unormalize sub SU(2) matrix of the given SU(3) matrix, ordered `r, s, t`
662/// see [`get_sub_block_r`], [`get_sub_block_s`] and [`get_sub_block_t`]
663pub fn extract_su2_unorm(m: CMatrix3) -> [CMatrix2; 3] {
664 [get_su2_r_unorm(m), get_su2_s_unorm(m), get_su2_t_unorm(m)]
665}
666
667/// Return the matrix
668/// ```textrust
669/// U'_{ij} =| -U_{ij} if i not equal to j
670/// | U_{ii} if i = j
671/// ```
672/// # Example
673/// ```
674/// # use lattice_qcd_rs::{CMatrix3, su3::reverse, Complex};
675/// assert_eq!(CMatrix3::identity(), reverse(CMatrix3::identity()));
676/// let m1 = CMatrix3::new(
677/// Complex::from(1_f64),
678/// Complex::from(2_f64),
679/// Complex::from(3_f64),
680/// // ---
681/// Complex::from(4_f64),
682/// Complex::from(5_f64),
683/// Complex::from(6_f64),
684/// // ---
685/// Complex::from(7_f64),
686/// Complex::from(8_f64),
687/// Complex::from(9_f64),
688/// );
689/// let m2 = CMatrix3::new(
690/// Complex::from(1_f64),
691/// -Complex::from(2_f64),
692/// -Complex::from(3_f64),
693/// // ---
694/// -Complex::from(4_f64),
695/// Complex::from(5_f64),
696/// -Complex::from(6_f64),
697/// // ---
698/// -Complex::from(7_f64),
699/// -Complex::from(8_f64),
700/// Complex::from(9_f64),
701/// );
702/// assert_eq!(m2, reverse(m1));
703/// assert_eq!(m1, reverse(m2));
704/// ```
705pub fn reverse(input: CMatrix3) -> CMatrix3 {
706 input.map_with_location(|i, j, el| {
707 if i == j {
708 el
709 }
710 else {
711 -el
712 }
713 })
714}
715
716/// Return N such that `1/(N-7)!` < [`f64::EPSILON`].
717///
718/// This number is needed for the computation of exponential matrix
719#[allow(clippy::as_conversions)] // no try into for f64
720pub fn factorial_size_for_exp() -> usize {
721 let mut n: usize = 7;
722 let mut factorial_value = 1;
723 while 1_f64 / (factorial_value as f64) >= Real::EPSILON {
724 n += 1;
725 factorial_value *= n - 7;
726 }
727 n
728}
729
730/// Size of the array for FactorialStorageStatic
731const FACTORIAL_STORAGE_STAT_SIZE: usize = utils::MAX_NUMBER_FACTORIAL + 1;
732
733/// Static store for factorial number.
734#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
735struct FactorialStorageStatic {
736 data: [FactorialNumber; FACTORIAL_STORAGE_STAT_SIZE],
737}
738
739macro_rules! set_factorial_storage {
740 ($data:ident, 0) => {
741 $data[0] = 1;
742 };
743 ($data:ident, $e:expr) => {
744 $data[$e] = $data[$e - 1] * $e as FactorialNumber;
745 };
746}
747
748impl FactorialStorageStatic {
749 /// compile time evaluation of all 34 factorial numbers
750 #[allow(clippy::as_conversions)] // constant function cant use try into
751 pub const fn new() -> Self {
752 let mut data: [FactorialNumber; FACTORIAL_STORAGE_STAT_SIZE] =
753 [1; FACTORIAL_STORAGE_STAT_SIZE];
754 let mut i = 1;
755 while i < FACTORIAL_STORAGE_STAT_SIZE {
756 // still not for loop in const fn
757 set_factorial_storage!(data, i);
758 i += 1;
759 }
760 Self { data }
761 }
762
763 /// access in O(1). Return None if `value` is bigger than 34.
764 pub fn try_get_factorial(&self, value: usize) -> Option<&FactorialNumber> {
765 self.data.get(value)
766 }
767
768 /// Get an iterator over the factorial number form `0!` up to `34!`.
769 pub fn iter(
770 &self,
771 ) -> impl Iterator<Item = &FactorialNumber> + ExactSizeIterator + FusedIterator {
772 self.data.iter()
773 }
774
775 /// Get the slice of factorial number.
776 pub const fn as_slice(&self) -> &[FactorialNumber; FACTORIAL_STORAGE_STAT_SIZE] {
777 &self.data
778 }
779}
780
781impl Default for FactorialStorageStatic {
782 fn default() -> Self {
783 Self::new()
784 }
785}
786
787impl std::fmt::Display for FactorialStorageStatic {
788 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
789 write!(f, "factorial Storage : ")?;
790 for (i, n) in self.iter().enumerate() {
791 write!(f, "{}! = {}", i, n)?;
792 if i < self.data.len() - 1 {
793 write!(f, ", ")?;
794 }
795 }
796 Ok(())
797 }
798}
799
800impl<'a> IntoIterator for &'a FactorialStorageStatic {
801 type IntoIter = <&'a [u128; FACTORIAL_STORAGE_STAT_SIZE] as IntoIterator>::IntoIter;
802 type Item = &'a u128;
803
804 fn into_iter(self) -> Self::IntoIter {
805 self.data.iter()
806 }
807}
808
809impl AsRef<[FactorialNumber; FACTORIAL_STORAGE_STAT_SIZE]> for FactorialStorageStatic {
810 fn as_ref(&self) -> &[FactorialNumber; FACTORIAL_STORAGE_STAT_SIZE] {
811 self.as_slice()
812 }
813}
814
815/// factorial number storage in order to find the exponential in O(1) for a set storage
816/// the set if for all number `N` such that `\frac{1}{(N-7)!} >= \mathrm{f64::EPSILON}`
817const FACTORIAL_STORAGE_STAT: FactorialStorageStatic = FactorialStorageStatic::new();
818
819/// number of step for the computation of matrix exponential using the Cayley–Hamilton theorem.
820const N: usize = 26;
821
822/// give the SU3 matrix from the adjoint rep, i.e compute `exp(i v^a T^a )`
823///
824/// The algorithm use is much more efficient the diagonalization method.
825/// It use the Cayley–Hamilton theorem. If you wish to find more about it you can read the
826/// [OpenQCD](https://luscher.web.cern.ch/luscher/openQCD/) documentation that can be found
827/// [here](https://github.com/sa2c/OpenQCD-AVX512/blob/master/doc/su3_fcts.pdf) or by downloading a release.
828/// Note that the documentation above explain the algorithm for exp(X) here it is a modified version for
829/// exp(i X).
830#[inline]
831#[allow(clippy::as_conversions)] // no try into for f64
832pub fn su3_exp_i(su3_adj: Su3Adjoint) -> CMatrix3 {
833 // todo optimize even more using f64 to reduce the number of operation using complex that might be useless
834 const N_LOOP: usize = N - 1;
835 let mut q0: Complex =
836 Complex::from(1_f64 / *FACTORIAL_STORAGE_STAT.try_get_factorial(N_LOOP).unwrap() as f64);
837 let mut q1: Complex = Complex::from(0_f64);
838 let mut q2: Complex = Complex::from(0_f64);
839 let d: Complex = su3_adj.d();
840 let t: Complex = su3_adj.t().into();
841 for i in (0..N_LOOP).rev() {
842 let q0_n =
843 Complex::from(1_f64 / *FACTORIAL_STORAGE_STAT.try_get_factorial(i).unwrap() as f64)
844 + d * q2;
845 let q1_n = I * (q0 - t * q2);
846 let q2_n = I * q1;
847
848 q0 = q0_n;
849 q1 = q1_n;
850 q2 = q2_n;
851 }
852
853 let m = su3_adj.to_matrix();
854 CMatrix3::from_diagonal_element(q0) + m * q1 + m * m * q2
855}
856
857/// the input must be a su(3) matrix (generator of SU(3)), gives the SU3 matrix from the adjoint rep,
858/// i.e compute `exp(i v^a T^a )`
859///
860/// The algorithm use is much more efficient the diagonalization method.
861/// It use the Cayley–Hamilton theorem. If you wish to find more about it you can read the
862/// [OpenQCD](https://luscher.web.cern.ch/luscher/openQCD/) documentation that can be found
863/// [here](https://github.com/sa2c/OpenQCD-AVX512/blob/master/doc/su3_fcts.pdf) or by downloading a release.
864/// Note that the documentation above explain the algorithm for exp(X) here it is a modified version for
865/// exp(i X).
866///
867/// # Panic
868/// The input matrix must be an su(3) (Lie algebra of SU(3)) matrix or approximately su(3),
869/// otherwise the function will panic in debug mod, in release the output gives unexpected values.
870/// ```should_panic
871/// use lattice_qcd_rs::{
872/// assert_eq_matrix,
873/// su3::{matrix_su3_exp_i, MatrixExp},
874/// };
875/// use nalgebra::{Complex, Matrix3};
876/// let i = Complex::new(0_f64, 1_f64);
877/// let matrix = Matrix3::identity(); // this is NOT an su(3) matrix
878/// let output = matrix_su3_exp_i(matrix);
879/// // We panic in debug. In release the following asset will fail.
880/// // assert_eq_matrix!(output, (matrix* i).exp(), f64::EPSILON * 100_000_f64);
881/// ```
882#[inline]
883#[allow(clippy::as_conversions)] // no try into for f64
884pub fn matrix_su3_exp_i(matrix: CMatrix3) -> CMatrix3 {
885 debug_assert!(is_matrix_su3_lie(&matrix, f64::EPSILON * 100_f64));
886 const N_LOOP: usize = N - 1;
887 let mut q0: Complex =
888 Complex::from(1_f64 / *FACTORIAL_STORAGE_STAT.try_get_factorial(N_LOOP).unwrap() as f64);
889 let mut q1: Complex = Complex::from(0_f64);
890 let mut q2: Complex = Complex::from(0_f64);
891 let d: Complex = matrix.determinant() * I;
892 let t: Complex = -Complex::from(0.5_f64) * (matrix * matrix).trace();
893 for i in (0..N_LOOP).rev() {
894 let q0_n =
895 Complex::from(1_f64 / *FACTORIAL_STORAGE_STAT.try_get_factorial(i).unwrap() as f64)
896 + d * q2;
897 let q1_n = I * (q0 - t * q2);
898 let q2_n = I * q1;
899
900 q0 = q0_n;
901 q1 = q1_n;
902 q2 = q2_n;
903 }
904
905 CMatrix3::from_diagonal_element(q0) + matrix * q1 + matrix * matrix * q2
906}
907
908/// gives the value `exp(v^a T^a )`
909///
910/// The algorithm use is much more efficient the diagonalization method.
911/// It use the Cayley–Hamilton theorem. If you wish to find more about it you can read the
912/// [OpenQCD](https://luscher.web.cern.ch/luscher/openQCD/) documentation that can be found
913/// [here](https://github.com/sa2c/OpenQCD-AVX512/blob/master/doc/su3_fcts.pdf) or by downloading a release.
914#[inline]
915#[allow(clippy::as_conversions)] // no try into for f64
916pub fn su3_exp_r(su3_adj: Su3Adjoint) -> CMatrix3 {
917 const N_LOOP: usize = N - 1;
918 let mut q0: Complex =
919 Complex::from(1_f64 / *FACTORIAL_STORAGE_STAT.try_get_factorial(N_LOOP).unwrap() as f64);
920 let mut q1: Complex = Complex::from(0_f64);
921 let mut q2: Complex = Complex::from(0_f64);
922 let d: Complex = su3_adj.d();
923 let t: Complex = su3_adj.t().into();
924 for i in (0..N_LOOP).rev() {
925 let q0_n =
926 Complex::from(1_f64 / *FACTORIAL_STORAGE_STAT.try_get_factorial(i).unwrap() as f64)
927 - I * d * q2;
928 let q1_n = q0 - t * q2;
929 let q2_n = q1;
930
931 q0 = q0_n;
932 q1 = q1_n;
933 q2 = q2_n;
934 }
935
936 let m = su3_adj.to_matrix();
937 CMatrix3::from_diagonal_element(q0) + m * q1 + m * m * q2
938}
939
940/// the input must be a su(3) matrix (generator of SU(3)), gives the value `exp(v^a T^a )`
941///
942/// The algorithm use is much more efficient the diagonalization method.
943/// It use the Cayley–Hamilton theorem. If you wish to find more about it you can read the
944/// [OpenQCD](https://luscher.web.cern.ch/luscher/openQCD/) documentation that can be found
945/// [here](https://github.com/sa2c/OpenQCD-AVX512/blob/master/doc/su3_fcts.pdf) or by downloading a release.
946///
947/// # Panic
948/// The input matrix must be an su(3) (Lie algebra of SU(3)) matrix or approximately su(3),
949/// otherwise the function will panic in debug mod, in release the output gives unexpected values.
950/// ```should_panic
951/// use lattice_qcd_rs::{
952/// assert_eq_matrix,
953/// su3::{matrix_su3_exp_r, MatrixExp},
954/// };
955/// use nalgebra::{Complex, Matrix3};
956/// let i = Complex::new(0_f64, 1_f64);
957/// let matrix = Matrix3::identity(); // this is NOT an su(3)
958/// let output = matrix_su3_exp_r(matrix);
959/// // We panic in debug. In release the following asset will fail.
960/// // assert_eq_matrix!(output, matrix.exp(), f64::EPSILON * 100_000_f64);
961/// ```
962#[inline]
963#[allow(clippy::as_conversions)] // no try into for f64
964pub fn matrix_su3_exp_r(matrix: CMatrix3) -> CMatrix3 {
965 debug_assert!(is_matrix_su3_lie(&matrix, f64::EPSILON * 100_f64));
966 const N_LOOP: usize = N - 1;
967 let mut q0: Complex =
968 Complex::from(1_f64 / *FACTORIAL_STORAGE_STAT.try_get_factorial(N_LOOP).unwrap() as f64);
969 let mut q1: Complex = Complex::from(0_f64);
970 let mut q2: Complex = Complex::from(0_f64);
971 let d: Complex = matrix.determinant() * I;
972 let t: Complex = -Complex::from(0.5_f64) * (matrix * matrix).trace();
973 for i in (0..N_LOOP).rev() {
974 let q0_n =
975 Complex::from(1_f64 / *FACTORIAL_STORAGE_STAT.try_get_factorial(i).unwrap() as f64)
976 - I * d * q2;
977 let q1_n = q0 - t * q2;
978 let q2_n = q1;
979
980 q0 = q0_n;
981 q1 = q1_n;
982 q2 = q2_n;
983 }
984
985 CMatrix3::from_diagonal_element(q0) + matrix * q1 + matrix * matrix * q2
986}
987
988/// Return wether the input matrix is SU(3) up to epsilon.
989// TODO example
990pub fn is_matrix_su3(m: &CMatrix3, epsilon: f64) -> bool {
991 ((m.determinant() - Complex::from(1_f64)).modulus_squared() < epsilon)
992 && ((m * m.adjoint() - CMatrix3::identity()).norm() < epsilon)
993}
994
995/// Returns wether the given matrix is in the lie algebra su(3) that generates SU(3) up to epsilon.
996pub fn is_matrix_su3_lie(matrix: &CMatrix3, epsilon: Real) -> bool {
997 matrix.trace().modulus() < epsilon && (matrix - matrix.adjoint()).norm() < epsilon
998}
999
1000#[doc(hidden)]
1001/// Crate a random 3x3 Matrix
1002pub fn random_matrix_3<R: Rng + ?Sized>(rng: &mut R) -> CMatrix3 {
1003 let d = Uniform::from(-10_f64..10_f64);
1004 CMatrix3::from_fn(|_, _| Complex::new(d.sample(rng), d.sample(rng)))
1005}
1006
1007#[cfg(test)]
1008mod test {
1009 use rand::SeedableRng;
1010
1011 use super::*;
1012 use crate::{assert_eq_matrix, I};
1013
1014 const EPSILON: f64 = 0.000_000_001_f64;
1015 const SEED_RNG: u64 = 0x45_78_93_f4_4a_b0_67_f0;
1016
1017 #[test]
1018 /// test that [`N`] is indeed what we need
1019 fn test_constant() {
1020 assert_eq!(N, factorial_size_for_exp() + 1);
1021 }
1022
1023 #[test]
1024 fn test_factorial() {
1025 for i in 0..FACTORIAL_STORAGE_STAT_SIZE {
1026 assert_eq!(
1027 *FACTORIAL_STORAGE_STAT.try_get_factorial(i).unwrap(),
1028 utils::factorial(i)
1029 );
1030 }
1031 }
1032
1033 #[test]
1034 fn sub_block() {
1035 let m = CMatrix3::new(
1036 Complex::from(1_f64),
1037 Complex::from(2_f64),
1038 Complex::from(3_f64),
1039 // ---
1040 Complex::from(4_f64),
1041 Complex::from(5_f64),
1042 Complex::from(6_f64),
1043 // ---
1044 Complex::from(7_f64),
1045 Complex::from(8_f64),
1046 Complex::from(9_f64),
1047 );
1048 let r = CMatrix2::new(
1049 Complex::from(1_f64),
1050 Complex::from(2_f64),
1051 // ---
1052 Complex::from(4_f64),
1053 Complex::from(5_f64),
1054 );
1055 assert_eq_matrix!(get_sub_block_r(m), r, EPSILON);
1056
1057 let m_r = CMatrix3::new(
1058 Complex::from(1_f64),
1059 Complex::from(2_f64),
1060 Complex::from(0_f64),
1061 // ---
1062 Complex::from(4_f64),
1063 Complex::from(5_f64),
1064 Complex::from(0_f64),
1065 // ---
1066 Complex::from(0_f64),
1067 Complex::from(0_f64),
1068 Complex::from(1_f64),
1069 );
1070 assert_eq_matrix!(get_r(r), m_r, EPSILON);
1071
1072 let s = CMatrix2::new(
1073 Complex::from(1_f64),
1074 Complex::from(3_f64),
1075 // ---
1076 Complex::from(7_f64),
1077 Complex::from(9_f64),
1078 );
1079 assert_eq_matrix!(get_sub_block_s(m), s, EPSILON);
1080
1081 let m_s = CMatrix3::new(
1082 Complex::from(1_f64),
1083 Complex::from(0_f64),
1084 Complex::from(3_f64),
1085 // ---
1086 Complex::from(0_f64),
1087 Complex::from(1_f64),
1088 Complex::from(0_f64),
1089 // ---
1090 Complex::from(7_f64),
1091 Complex::from(0_f64),
1092 Complex::from(9_f64),
1093 );
1094 assert_eq_matrix!(get_s(s), m_s, EPSILON);
1095
1096 let t = CMatrix2::new(
1097 Complex::from(5_f64),
1098 Complex::from(6_f64),
1099 // ---
1100 Complex::from(8_f64),
1101 Complex::from(9_f64),
1102 );
1103 assert_eq_matrix!(get_sub_block_t(m), t, EPSILON);
1104
1105 let m_t = CMatrix3::new(
1106 Complex::from(1_f64),
1107 Complex::from(0_f64),
1108 Complex::from(0_f64),
1109 // ---
1110 Complex::from(0_f64),
1111 Complex::from(5_f64),
1112 Complex::from(6_f64),
1113 // ---
1114 Complex::from(0_f64),
1115 Complex::from(8_f64),
1116 Complex::from(9_f64),
1117 );
1118 assert_eq_matrix!(get_t(t), m_t, EPSILON);
1119
1120 for p in &extract_su2_unorm(m) {
1121 assert_matrix_is_su_2!(p / p.determinant().sqrt(), EPSILON);
1122 }
1123 }
1124
1125 #[test]
1126 #[allow(deprecated)]
1127 fn exp_matrix() {
1128 let mut rng = rand::rngs::StdRng::seed_from_u64(SEED_RNG);
1129 let d = rand::distributions::Uniform::from(-1_f64..1_f64);
1130 for m in &GENERATORS {
1131 let output_r = matrix_su3_exp_r(**m);
1132 assert_eq_matrix!(output_r, m.exp(), EPSILON);
1133 let output_i = matrix_su3_exp_i(**m);
1134 assert_eq_matrix!(output_i, (*m * I).exp(), EPSILON);
1135 }
1136 for _ in 0_u32..100_u32 {
1137 let v = Su3Adjoint::random(&mut rng, &d);
1138 let m = v.to_matrix();
1139 let output_r = matrix_su3_exp_r(m);
1140 assert_eq_matrix!(output_r, m.exp(), EPSILON);
1141 let output_i = matrix_su3_exp_i(m);
1142 assert_eq_matrix!(output_i, (m * I).exp(), EPSILON);
1143 }
1144 }
1145 #[test]
1146 fn test_is_matrix_su3_lie() {
1147 let mut rng = rand::rngs::StdRng::seed_from_u64(SEED_RNG);
1148 let d = rand::distributions::Uniform::from(-1_f64..1_f64);
1149 for m in &GENERATORS {
1150 assert!(is_matrix_su3_lie(*m, f64::EPSILON * 100_f64));
1151 }
1152 for _ in 0_u32..100_u32 {
1153 let v = Su3Adjoint::random(&mut rng, &d);
1154 let m = v.to_matrix();
1155 assert!(is_matrix_su3_lie(&m, f64::EPSILON * 100_f64));
1156 }
1157 }
1158
1159 #[test]
1160 fn test_gen() {
1161 assert_eq!(
1162 CMatrix3::new(
1163 Complex::new(1_f64, 0_f64),
1164 ZERO,
1165 ZERO,
1166 // ---
1167 ZERO,
1168 Complex::new(1_f64, 0_f64),
1169 ZERO,
1170 // ---
1171 ZERO,
1172 ZERO,
1173 Complex::new(-2_f64, 0_f64),
1174 ) * Complex::from(0.5_f64 / 3_f64.sqrt()),
1175 GENERATOR_8
1176 );
1177 }
1178
1179 #[test]
1180 fn test_is_matrix_su3() {
1181 let mut rng = rand::rngs::StdRng::seed_from_u64(SEED_RNG);
1182 let m = CMatrix3::new(
1183 Complex::from(0_f64),
1184 Complex::from(0_f64),
1185 Complex::from(0_f64),
1186 Complex::from(0_f64),
1187 Complex::from(0_f64),
1188 Complex::from(0_f64),
1189 Complex::from(0_f64),
1190 Complex::from(0_f64),
1191 Complex::from(0_f64),
1192 );
1193 assert!(!is_matrix_su3(&m, f64::EPSILON * 100_f64));
1194 let m = CMatrix3::new(
1195 Complex::from(1_f64),
1196 Complex::from(0_f64),
1197 Complex::from(0_f64),
1198 Complex::from(0_f64),
1199 Complex::from(0_f64),
1200 Complex::from(0_f64),
1201 Complex::from(0_f64),
1202 Complex::from(0_f64),
1203 Complex::from(0_f64),
1204 );
1205 assert!(!is_matrix_su3(&m, f64::EPSILON * 100_f64),);
1206 let m = CMatrix3::new(
1207 Complex::from(1_f64),
1208 Complex::from(0_f64),
1209 Complex::from(1_f64),
1210 Complex::from(0_f64),
1211 Complex::from(1_f64),
1212 Complex::from(0_f64),
1213 Complex::from(1_f64),
1214 Complex::from(0_f64),
1215 Complex::from(1_f64),
1216 );
1217 assert!(!is_matrix_su3(&m, f64::EPSILON * 100_f64));
1218 for _ in 0_u32..10_u32 {
1219 let m = random_su3(&mut rng);
1220 assert!(is_matrix_su3(&m, f64::EPSILON * 100_f64));
1221 }
1222 for _ in 0_u32..10_u32 {
1223 let m = random_su3(&mut rng) * Complex::from(1.1_f64);
1224 assert!(!is_matrix_su3(&m, f64::EPSILON * 100_f64));
1225 }
1226 }
1227
1228 #[test]
1229 fn factorial_storage() {
1230 let storage = FactorialStorageStatic::new();
1231 for (i, f) in storage.data.iter().enumerate() {
1232 assert_eq!(utils::factorial(i), *f);
1233 }
1234 assert!(storage.try_get_factorial(35).is_none());
1235 assert_eq!(storage.try_get_factorial(34), Some(&utils::factorial(34)));
1236 }
1237}