lattice_qcd_rs/integrator/
mod.rs

1//! Numerical integrators to carry out simulations.
2//!
3//! See [`SymplecticIntegrator`]. The simulations are done on [`LatticeStateWithEField`]
4//! It also require a notion of [`SimulationStateSynchronous`]
5//! and [`SimulationStateLeapFrog`].
6//!
7//! Even thought it is effortless to implement both [`SimulationStateSynchronous`]
8//! and [`SimulationStateLeapFrog`].
9//! I advice against it and implement only [`SimulationStateSynchronous`] and
10//! use [`super::simulation::SimulationStateLeap`]
11//! for leap frog states as it gives a compile time check that you did not forget
12//! doing a half steps.
13//!
14//! This library gives two implementations of [`SymplecticIntegrator`]:
15//! [`SymplecticEuler`] and [`SymplecticEulerRayon`].
16//! I would advice using [`SymplecticEulerRayon`] if you do not mind the little
17//! more memory it uses.
18//! # Example
19//! let us create a basic random state and let us simulate.
20//! ```
21//! # use std::error::Error;
22//! #
23//! # fn main() -> Result<(), Box<dyn Error>> {
24//! use lattice_qcd_rs::integrator::{SymplecticEuler, SymplecticIntegrator};
25//! use lattice_qcd_rs::simulation::{
26//!     LatticeStateDefault, LatticeStateEFSyncDefault, LatticeStateWithEField,
27//! };
28//! use rand::SeedableRng;
29//!
30//! let mut rng = rand::rngs::StdRng::seed_from_u64(0); // change with your seed
31//! let state1 = LatticeStateEFSyncDefault::new_random_e_state(
32//!     LatticeStateDefault::<3>::new_determinist(100_f64, 1_f64, 4, &mut rng)?,
33//!     &mut rng,
34//! );
35//! let integrator = SymplecticEuler::default();
36//! let state2 = integrator.integrate_sync_sync(&state1, 0.000_1_f64)?;
37//! let state3 = integrator.integrate_sync_sync(&state2, 0.000_1_f64)?;
38//! #     Ok(())
39//! # }
40//! ```
41//! Let us then compute and compare the Hamiltonian.
42//! ```
43//! # use std::error::Error;
44//! #
45//! # fn main() -> Result<(), Box<dyn Error>> {
46//! # use lattice_qcd_rs::integrator::{SymplecticEuler, SymplecticIntegrator};
47//! # use lattice_qcd_rs::simulation::{
48//! #     LatticeStateDefault, LatticeStateWithEField, LatticeStateEFSyncDefault,
49//! # };
50//! # use rand::SeedableRng;
51//! #
52//! # let mut rng = rand::rngs::StdRng::seed_from_u64(0); // change with your seed
53//! # let state1 = LatticeStateEFSyncDefault::new_random_e_state(
54//! #     LatticeStateDefault::<3>::new_determinist(100_f64, 1_f64, 4, &mut rng)?,
55//! #     &mut rng,
56//! # );
57//! # let integrator = SymplecticEuler::default();
58//! # let state2 = integrator.integrate_sync_sync(&state1, 0.000_1_f64)?;
59//! # let state3 = integrator.integrate_sync_sync(&state2, 0.000_1_f64)?;
60//! let h = state1.hamiltonian_total();
61//! let h2 = state3.hamiltonian_total();
62//! println!("The error on the Hamiltonian is {}", h - h2);
63//! #     Ok(())
64//! # }
65//! ```
66//! See [`SimulationStateSynchronous`] for more convenient methods.
67
68use na::SVector;
69
70use super::{
71    field::{EField, LinkMatrix, Su3Adjoint},
72    lattice::{LatticeCyclic, LatticeLink, LatticeLinkCanonical, LatticePoint},
73    simulation::{LatticeStateWithEField, SimulationStateLeapFrog, SimulationStateSynchronous},
74    CMatrix3, Complex, Real,
75};
76
77pub mod symplectic_euler;
78pub mod symplectic_euler_rayon;
79
80pub use symplectic_euler::SymplecticEuler;
81pub use symplectic_euler_rayon::SymplecticEulerRayon;
82
83/// Define an symplectic numerical integrator
84///
85/// The integrator evolve the state in time.
86///
87/// The integrator should be capable of switching between Sync state
88/// (q (or link matrices) at time T , p (or e_field) at time T )
89/// and leap frog (a at time T, p at time T + 1/2)
90///
91/// # Example
92/// For an example see the module level documentation [`super::integrator`].
93pub trait SymplecticIntegrator<StateSync, StateLeap, const D: usize>
94where
95    StateSync: SimulationStateSynchronous<D>,
96    StateLeap: SimulationStateLeapFrog<D>,
97{
98    /// Type of error returned by the Integrator.
99    type Error;
100
101    /// Integrate a sync state to a sync state by advancing the link matrices and the electrical field simultaneously.
102    ///
103    /// # Example
104    /// see the module level documentation [`super::integrator`].
105    ///
106    /// # Errors
107    /// Return an error if the integration encounter a problem
108    fn integrate_sync_sync(&self, l: &StateSync, delta_t: Real) -> Result<StateSync, Self::Error>;
109
110    /// Integrate a leap state to a leap state using leap frog algorithm.
111    ///
112    ///
113    /// # Example
114    /// ```
115    /// # use std::error::Error;
116    /// #
117    /// # fn main() -> Result<(), Box<dyn Error>> {
118    /// use lattice_qcd_rs::integrator::{SymplecticEulerRayon, SymplecticIntegrator};
119    /// use lattice_qcd_rs::simulation::{
120    ///     LatticeStateDefault, LatticeStateEFSyncDefault, LatticeStateWithEField,
121    /// };
122    /// use rand::SeedableRng;
123    ///
124    /// let mut rng = rand::rngs::StdRng::seed_from_u64(0); // change with your seed
125    /// let state = LatticeStateEFSyncDefault::new_random_e_state(
126    ///     LatticeStateDefault::<3>::new_determinist(1_f64, 2_f64, 4, &mut rng)?,
127    ///     &mut rng,
128    /// );
129    /// let h = state.hamiltonian_total();
130    /// let integrator = SymplecticEulerRayon::default();
131    /// let mut leap_frog = integrator.integrate_sync_leap(&state, 0.000_001_f64)?;
132    /// drop(state);
133    /// for _ in 0..2 {
134    ///     // Realistically you would want more steps
135    ///     leap_frog = integrator.integrate_leap_leap(&leap_frog, 0.000_001_f64)?;
136    /// }
137    /// let state = integrator.integrate_leap_sync(&leap_frog, 0.000_001_f64)?;
138    /// let h2 = state.hamiltonian_total();
139    ///
140    /// println!("The error on the Hamiltonian is {}", h - h2);
141    /// #     Ok(())
142    /// # }
143    /// ```
144    ///
145    /// # Errors
146    /// Return an error if the integration encounter a problem
147    fn integrate_leap_leap(&self, l: &StateLeap, delta_t: Real) -> Result<StateLeap, Self::Error>;
148
149    /// Integrate a sync state to a leap state by doing a half step for the conjugate momenta.
150    ///
151    /// # Example
152    /// see [`SymplecticIntegrator::integrate_leap_leap`]
153    ///
154    /// # Errors
155    /// Return an error if the integration encounter a problem
156    fn integrate_sync_leap(&self, l: &StateSync, delta_t: Real) -> Result<StateLeap, Self::Error>;
157
158    /// Integrate a leap state to a sync state by finishing doing a step for the position and finishing
159    /// the half step for the conjugate momenta.
160    ///
161    ///  # Example
162    /// see [`SymplecticIntegrator::integrate_leap_leap`]
163    ///
164    /// # Errors
165    /// Return an error if the integration encounter a problem
166    fn integrate_leap_sync(&self, l: &StateLeap, delta_t: Real) -> Result<StateSync, Self::Error>;
167
168    /// Integrate a Sync state by going to leap and then back to sync.
169    /// This is the symplectic method of integration, which should conserve approximately the hamiltonian
170    ///
171    /// Note that you might want to override this method as it can save you from a clone.
172    ///
173    /// # Example
174    /// ```
175    /// # use std::error::Error;
176    /// #
177    /// # fn main() -> Result<(), Box<dyn Error>> {
178    /// use lattice_qcd_rs::integrator::{SymplecticEulerRayon, SymplecticIntegrator};
179    /// use lattice_qcd_rs::simulation::{
180    ///     LatticeStateDefault, LatticeStateEFSyncDefault, LatticeStateWithEField,
181    /// };
182    /// use rand::SeedableRng;
183    ///
184    /// let mut rng = rand::rngs::StdRng::seed_from_u64(0); // change with your seed
185    /// let mut state = LatticeStateEFSyncDefault::new_random_e_state(
186    ///     LatticeStateDefault::<3>::new_determinist(1_f64, 2_f64, 4, &mut rng)?,
187    ///     &mut rng,
188    /// );
189    /// let h = state.hamiltonian_total();
190    ///
191    /// let integrator = SymplecticEulerRayon::default();
192    /// for _ in 0..1 {
193    ///     // Realistically you would want more steps
194    ///     state = integrator.integrate_symplectic(&state, 0.000_001_f64)?;
195    /// }
196    /// let h2 = state.hamiltonian_total();
197    ///
198    /// println!("The error on the Hamiltonian is {}", h - h2);
199    /// #     Ok(())
200    /// # }
201    /// ```
202    ///
203    /// # Errors
204    /// Return an error if the integration encounter a problem
205    fn integrate_symplectic(&self, l: &StateSync, delta_t: Real) -> Result<StateSync, Self::Error> {
206        self.integrate_leap_sync(&self.integrate_sync_leap(l, delta_t)?, delta_t)
207    }
208}
209
210/// function for link integration.
211/// This must succeed as it is use while doing parallel computation. Returning a Option is undesirable.
212/// As it can panic if a out of bound link is passed it needs to stay private.
213///
214/// # Panic
215/// It panics if a out of bound link is passed.
216fn integrate_link<State, const D: usize>(
217    link: &LatticeLinkCanonical<D>,
218    link_matrix: &LinkMatrix,
219    e_field: &EField<D>,
220    lattice: &LatticeCyclic<D>,
221    delta_t: Real,
222) -> CMatrix3
223where
224    State: LatticeStateWithEField<D>,
225{
226    let canonical_link = LatticeLink::from(*link);
227    let initial_value = link_matrix
228        .matrix(&canonical_link, lattice)
229        .expect("Link matrix not found");
230    initial_value
231        + State::derivative_u(link, link_matrix, e_field, lattice).expect("Derivative not found")
232            * Complex::from(delta_t)
233}
234
235/// function for "Electrical" field integration.
236/// Like [`integrate_link`] this must succeed.
237///
238/// # Panics
239/// It panics if a out of bound link is passed.
240fn integrate_efield<State, const D: usize>(
241    point: &LatticePoint<D>,
242    link_matrix: &LinkMatrix,
243    e_field: &EField<D>,
244    lattice: &LatticeCyclic<D>,
245    delta_t: Real,
246) -> SVector<Su3Adjoint, D>
247where
248    State: LatticeStateWithEField<D>,
249{
250    let initial_value = e_field.e_vec(point, lattice).expect("E Field not found");
251    let deriv =
252        State::derivative_e(point, link_matrix, e_field, lattice).expect("Derivative not found");
253    initial_value + deriv.map(|el| el * delta_t)
254}