lattice_qcd_rs/simulation/monte_carlo/
hybrid_monte_carlo.rs

1//! Hybrid Monte Carlo method
2//!
3//! # Example
4//! ```
5//! # use std::error::Error;
6//! #
7//! # fn main() -> Result<(), Box<dyn Error>> {
8//! use lattice_qcd_rs::integrator::SymplecticEulerRayon;
9//! use lattice_qcd_rs::simulation::{
10//!     HybridMonteCarloDiagnostic, LatticeState, LatticeStateDefault,
11//! };
12//! use rand::SeedableRng;
13//!
14//! let rng = rand::rngs::StdRng::seed_from_u64(0); // change with your seed
15//! let mut hmc =
16//!     HybridMonteCarloDiagnostic::new(0.000_000_1_f64, 10, SymplecticEulerRayon::new(), rng);
17//! // Realistically you want more steps than 10
18//!
19//! let mut state = LatticeStateDefault::<3>::new_cold(1_f64, 6_f64, 4)?;
20//! for _ in 0..1 {
21//!     state = state.monte_carlo_step(&mut hmc)?;
22//!     println!(
23//!         "probability of accept last step {}, has replaced {}",
24//!         hmc.prob_replace_last(),
25//!         hmc.has_replace_last()
26//!     );
27//!     // operation to track the progress or the evolution
28//! }
29//! // operation at the end of the simulation
30//! #     Ok(())
31//! # }
32//! ```
33
34use std::marker::PhantomData;
35
36use rand_distr::Distribution;
37#[cfg(feature = "serde-serialize")]
38use serde::{Deserialize, Serialize};
39
40use super::{
41    super::{
42        super::{error::MultiIntegrationError, integrator::SymplecticIntegrator, Real},
43        state::{
44            LatticeState, LatticeStateEFSyncDefault, SimulationStateLeap,
45            SimulationStateSynchronous,
46        },
47    },
48    MonteCarlo, MonteCarloDefault,
49};
50
51/// Hybrid Monte Carlo algorithm (HCM for short).
52///
53/// The idea of HCM is to generate a random set on conjugate momenta to the link matrices.
54/// This conjugated momenta is also refed as the "Electric" field
55/// or `e_field` with distribution N(0, 1 / beta). And to solve the equation of motion.
56/// The new state is accepted with probability Exp( -H_old + H_new) where the Hamiltonian has an extra term Tr(E_i ^ 2).
57/// The advantage is that the simulation can be done in a symplectic way i.e. it conserved the Hamiltonian.
58/// Which means that the method has a high acceptance rate.
59///
60/// # Example
61/// See the the level module documentation.
62#[derive(Clone, Debug, PartialEq)]
63#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
64pub struct HybridMonteCarlo<State, Rng, I, const D: usize>
65where
66    State: LatticeState<D> + Clone + ?Sized,
67    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
68    I: SymplecticIntegrator<
69        LatticeStateEFSyncDefault<State, D>,
70        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
71        D,
72    >,
73    Rng: rand::Rng,
74{
75    internal: HybridMonteCarloInternal<LatticeStateEFSyncDefault<State, D>, I, D>,
76    rng: Rng,
77}
78
79impl<State, Rng, I, const D: usize> HybridMonteCarlo<State, Rng, I, D>
80where
81    State: LatticeState<D> + Clone + ?Sized,
82    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
83    I: SymplecticIntegrator<
84        LatticeStateEFSyncDefault<State, D>,
85        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
86        D,
87    >,
88    Rng: rand::Rng,
89{
90    getter!(
91        /// Get a ref to the rng.
92        pub const rng() -> Rng
93    );
94
95    project!(
96        /// Get the integrator.
97        pub const internal.integrator() -> &I
98    );
99
100    project_mut!(
101        /// Get a mut ref to the integrator.
102        pub internal.integrator_mut() -> &mut I
103    );
104
105    project!(
106        /// Get `delta_t`.
107        pub const internal.delta_t() -> Real
108    );
109
110    project!(
111        /// Get the number of steps.
112        pub const internal.number_of_steps() -> usize
113    );
114
115    /// gives the following parameter for the HCM :
116    /// - delta_t is the step size per integration of the equation of motion
117    /// - number_of_steps is the number of time
118    /// - integrator is the methods to solve the equation of motion
119    /// - rng, a random number generator
120    pub const fn new(delta_t: Real, number_of_steps: usize, integrator: I, rng: Rng) -> Self {
121        Self {
122            internal: HybridMonteCarloInternal::<LatticeStateEFSyncDefault<State, D>, I, D>::new(
123                delta_t,
124                number_of_steps,
125                integrator,
126            ),
127            rng,
128        }
129    }
130
131    /// Get a mutable reference to the rng.
132    pub fn rng_mut(&mut self) -> &mut Rng {
133        &mut self.rng
134    }
135
136    /// Get the last probably of acceptance of the random change.
137    #[allow(clippy::missing_const_for_fn)] // false positive
138    pub fn rng_owned(self) -> Rng {
139        self.rng
140    }
141}
142
143impl<State, Rng, I, const D: usize> AsRef<Rng> for HybridMonteCarlo<State, Rng, I, D>
144where
145    State: LatticeState<D> + Clone + ?Sized,
146    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
147    I: SymplecticIntegrator<
148        LatticeStateEFSyncDefault<State, D>,
149        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
150        D,
151    >,
152    Rng: rand::Rng,
153{
154    fn as_ref(&self) -> &Rng {
155        self.rng()
156    }
157}
158
159impl<State, Rng, I, const D: usize> AsMut<Rng> for HybridMonteCarlo<State, Rng, I, D>
160where
161    State: LatticeState<D> + Clone + ?Sized,
162    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
163    I: SymplecticIntegrator<
164        LatticeStateEFSyncDefault<State, D>,
165        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
166        D,
167    >,
168    Rng: rand::Rng,
169{
170    fn as_mut(&mut self) -> &mut Rng {
171        self.rng_mut()
172    }
173}
174
175impl<State, Rng, I, const D: usize> MonteCarlo<State, D> for HybridMonteCarlo<State, Rng, I, D>
176where
177    State: LatticeState<D> + Clone + ?Sized,
178    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
179    I: SymplecticIntegrator<
180        LatticeStateEFSyncDefault<State, D>,
181        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
182        D,
183    >,
184    Rng: rand::Rng,
185{
186    type Error = MultiIntegrationError<I::Error>;
187
188    fn next_element(&mut self, state: State) -> Result<State, Self::Error> {
189        let state_internal =
190            LatticeStateEFSyncDefault::<State, D>::new_random_e_state(state, self.rng_mut());
191        self.internal
192            .next_element_default(state_internal, &mut self.rng)
193            .map(|el| el.state_owned())
194    }
195}
196
197/// internal structure for HybridMonteCarlo using [`LatticeStateWithEField`]
198#[derive(Clone, Debug, PartialEq)]
199#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
200struct HybridMonteCarloInternal<State, I, const D: usize>
201where
202    State: SimulationStateSynchronous<D> + ?Sized,
203    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
204{
205    /// integration step.
206    delta_t: Real,
207    /// number of steps to do.
208    number_of_steps: usize,
209    //// integrator used by the internal method
210    integrator: I,
211    /// The phantom data such that State can be a generic parameter without being stored.
212    #[cfg_attr(feature = "serde-serialize", serde(skip))]
213    _phantom: PhantomData<State>,
214}
215
216impl<State, I, const D: usize> HybridMonteCarloInternal<State, I, D>
217where
218    State: SimulationStateSynchronous<D> + ?Sized,
219    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
220{
221    getter!(
222        /// Get the integrator.
223        pub const,
224        integrator,
225        I
226    );
227
228    getter_copy!(
229        /// Get `delta_t`.
230        pub const,
231        delta_t,
232        Real
233    );
234
235    getter_copy!(
236        /// Get the number of steps.
237        pub const,
238        number_of_steps,
239        usize
240    );
241
242    /// see [HybridMonteCarlo::new]
243    pub const fn new(delta_t: Real, number_of_steps: usize, integrator: I) -> Self {
244        Self {
245            delta_t,
246            number_of_steps,
247            integrator,
248            _phantom: PhantomData,
249        }
250    }
251
252    pub fn integrator_mut(&mut self) -> &mut I {
253        &mut self.integrator
254    }
255}
256
257impl<State, I, const D: usize> AsRef<I> for HybridMonteCarloInternal<State, I, D>
258where
259    State: SimulationStateSynchronous<D> + ?Sized,
260    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
261{
262    fn as_ref(&self) -> &I {
263        self.integrator()
264    }
265}
266
267impl<State, I, const D: usize> AsMut<I> for HybridMonteCarloInternal<State, I, D>
268where
269    State: SimulationStateSynchronous<D> + ?Sized,
270    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
271{
272    fn as_mut(&mut self) -> &mut I {
273        self.integrator_mut()
274    }
275}
276
277impl<State, I, const D: usize> MonteCarloDefault<State, D> for HybridMonteCarloInternal<State, I, D>
278where
279    State: SimulationStateSynchronous<D> + ?Sized,
280    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
281{
282    type Error = MultiIntegrationError<I::Error>;
283
284    fn potential_next_element<Rng>(
285        &mut self,
286        state: &State,
287        _rng: &mut Rng,
288    ) -> Result<State, Self::Error>
289    where
290        Rng: rand::Rng + ?Sized,
291    {
292        state.simulate_symplectic_n_auto(&self.integrator, self.delta_t, self.number_of_steps)
293    }
294
295    fn probability_of_replacement(old_state: &State, new_state: &State) -> Real {
296        (old_state.hamiltonian_total() - new_state.hamiltonian_total())
297            .exp()
298            .min(1_f64)
299            .max(0_f64)
300    }
301}
302
303/// Hybrid Monte Carlo algorithm ( HCM for short) with diagnostics.
304///
305/// The idea of HCM is to generate a random set on conjugate momenta to the link matrices.
306/// This conjugated momenta is also refed as the "Electric" field
307/// or `e_field` with distribution N(0, 1 / beta). And to solve the equation of motion.
308/// The new state is accepted with probability Exp( -H_old + H_new) where the Hamiltonian has an extra term Tr(E_i ^ 2).
309/// The advantage is that the simulation can be done in a symplectic way i.e. it conserved the Hamiltonian.
310/// Which means that the method has a high acceptance rate.
311///
312/// # Example
313/// See the the level module documentation.
314#[derive(Clone, Debug, PartialEq)]
315#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
316pub struct HybridMonteCarloDiagnostic<State, Rng, I, const D: usize>
317where
318    State: LatticeState<D> + Clone + ?Sized,
319    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
320    I: SymplecticIntegrator<
321        LatticeStateEFSyncDefault<State, D>,
322        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
323        D,
324    >,
325    Rng: rand::Rng,
326{
327    internal: HybridMonteCarloInternalDiagnostics<LatticeStateEFSyncDefault<State, D>, I, D>,
328    rng: Rng,
329}
330
331impl<State, Rng, I, const D: usize> HybridMonteCarloDiagnostic<State, Rng, I, D>
332where
333    State: LatticeState<D> + Clone + ?Sized,
334    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
335    I: SymplecticIntegrator<
336        LatticeStateEFSyncDefault<State, D>,
337        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
338        D,
339    >,
340    Rng: rand::Rng,
341{
342    getter!(
343        /// Get a ref to the rng.
344        pub const,
345        rng,
346        Rng
347    );
348
349    project!(
350        /// Get the integrator.
351        pub const,
352        integrator,
353        internal,
354        &I
355    );
356
357    project_mut!(
358        /// Get a mut ref to the integrator.
359        pub,
360        integrator_mut,
361        internal,
362        &mut I
363    );
364
365    project!(
366        /// Get `delta_t`.
367        pub const,
368        delta_t,
369        internal,
370        Real
371    );
372
373    project!(
374        /// Get the number of steps.
375        pub const,
376        number_of_steps,
377        internal,
378        usize
379    );
380
381    /// gives the following parameter for the HCM :
382    /// - delta_t is the step size per integration of the equation of motion
383    /// - number_of_steps is the number of time
384    /// - integrator is the method to solve the equation of motion
385    /// - rng, a random number generator
386    pub const fn new(delta_t: Real, number_of_steps: usize, integrator: I, rng: Rng) -> Self {
387        Self {
388            internal: HybridMonteCarloInternalDiagnostics::<
389                LatticeStateEFSyncDefault<State, D>,
390                I,
391                D,
392            >::new(delta_t, number_of_steps, integrator),
393            rng,
394        }
395    }
396
397    /// Get a mutable reference to the rng.
398    pub fn rng_mut(&mut self) -> &mut Rng {
399        &mut self.rng
400    }
401
402    /// Get the last probably of acceptance of the random change.
403    pub const fn prob_replace_last(&self) -> Real {
404        self.internal.prob_replace_last()
405    }
406
407    /// Get if last step has accepted the replacement.
408    pub const fn has_replace_last(&self) -> bool {
409        self.internal.has_replace_last()
410    }
411
412    /// Get the last probably of acceptance of the random change.
413    #[allow(clippy::missing_const_for_fn)] // false positive
414    pub fn rng_owned(self) -> Rng {
415        self.rng
416    }
417}
418
419impl<State, Rng, I, const D: usize> AsRef<Rng> for HybridMonteCarloDiagnostic<State, Rng, I, D>
420where
421    State: LatticeState<D> + Clone + ?Sized,
422    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
423    I: SymplecticIntegrator<
424        LatticeStateEFSyncDefault<State, D>,
425        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
426        D,
427    >,
428    Rng: rand::Rng,
429{
430    fn as_ref(&self) -> &Rng {
431        self.rng()
432    }
433}
434
435impl<State, Rng, I, const D: usize> AsMut<Rng> for HybridMonteCarloDiagnostic<State, Rng, I, D>
436where
437    State: LatticeState<D> + Clone + ?Sized,
438    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
439    I: SymplecticIntegrator<
440        LatticeStateEFSyncDefault<State, D>,
441        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
442        D,
443    >,
444    Rng: rand::Rng,
445{
446    fn as_mut(&mut self) -> &mut Rng {
447        self.rng_mut()
448    }
449}
450
451impl<State, Rng, I, const D: usize> MonteCarlo<State, D>
452    for HybridMonteCarloDiagnostic<State, Rng, I, D>
453where
454    State: LatticeState<D> + Clone + ?Sized,
455    LatticeStateEFSyncDefault<State, D>: SimulationStateSynchronous<D>,
456    I: SymplecticIntegrator<
457        LatticeStateEFSyncDefault<State, D>,
458        SimulationStateLeap<LatticeStateEFSyncDefault<State, D>, D>,
459        D,
460    >,
461    Rng: rand::Rng,
462{
463    type Error = MultiIntegrationError<I::Error>;
464
465    fn next_element(&mut self, state: State) -> Result<State, Self::Error> {
466        let state_internal =
467            LatticeStateEFSyncDefault::<State, D>::new_random_e_state(state, self.rng_mut());
468        self.internal
469            .next_element_default(state_internal, &mut self.rng)
470            .map(|el| el.state_owned())
471    }
472}
473
474/// internal structure for HybridMonteCarlo using [`LatticeStateWithEField`]
475#[derive(Clone, Debug, PartialEq)]
476#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
477struct HybridMonteCarloInternalDiagnostics<State, I, const D: usize>
478where
479    State: SimulationStateSynchronous<D> + ?Sized,
480    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
481{
482    delta_t: Real,
483    number_of_steps: usize,
484    integrator: I,
485    has_replace_last: bool,
486    prob_replace_last: Real,
487    #[cfg_attr(feature = "serde-serialize", serde(skip))]
488    _phantom: PhantomData<State>,
489}
490
491impl<State, I, const D: usize> HybridMonteCarloInternalDiagnostics<State, I, D>
492where
493    State: SimulationStateSynchronous<D> + ?Sized,
494    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
495{
496    getter!(
497        /// Get the integrator.
498        pub const,
499        integrator,
500        I
501    );
502
503    getter_copy!(
504        /// Get `delta_t`.
505        pub const,
506        delta_t,
507        Real
508    );
509
510    getter_copy!(
511        /// Get the number of steps.
512        pub const,
513        number_of_steps,
514        usize
515    );
516
517    /// see [HybridMonteCarlo::new]
518    pub const fn new(delta_t: Real, number_of_steps: usize, integrator: I) -> Self {
519        Self {
520            delta_t,
521            number_of_steps,
522            integrator,
523            has_replace_last: false,
524            prob_replace_last: 0_f64,
525            _phantom: PhantomData,
526        }
527    }
528
529    /// Get the last probably of acceptance of the random change.
530    pub const fn prob_replace_last(&self) -> Real {
531        self.prob_replace_last
532    }
533
534    /// Get if last step has accepted the replacement.
535    pub const fn has_replace_last(&self) -> bool {
536        self.has_replace_last
537    }
538
539    /// get the integrator as a mutable reference.
540    pub fn integrator_mut(&mut self) -> &mut I {
541        &mut self.integrator
542    }
543}
544
545impl<State, I, const D: usize> AsRef<I> for HybridMonteCarloInternalDiagnostics<State, I, D>
546where
547    State: SimulationStateSynchronous<D> + ?Sized,
548    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
549{
550    fn as_ref(&self) -> &I {
551        self.integrator()
552    }
553}
554
555impl<State, I, const D: usize> AsMut<I> for HybridMonteCarloInternalDiagnostics<State, I, D>
556where
557    State: SimulationStateSynchronous<D> + ?Sized,
558    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
559{
560    fn as_mut(&mut self) -> &mut I {
561        self.integrator_mut()
562    }
563}
564
565impl<State, I, const D: usize> MonteCarloDefault<State, D>
566    for HybridMonteCarloInternalDiagnostics<State, I, D>
567where
568    State: SimulationStateSynchronous<D> + ?Sized,
569    I: SymplecticIntegrator<State, SimulationStateLeap<State, D>, D>,
570{
571    type Error = MultiIntegrationError<I::Error>;
572
573    fn potential_next_element<Rng>(
574        &mut self,
575        state: &State,
576        _rng: &mut Rng,
577    ) -> Result<State, Self::Error>
578    where
579        Rng: rand::Rng + ?Sized,
580    {
581        state.simulate_symplectic_n_auto(&self.integrator, self.delta_t, self.number_of_steps)
582    }
583
584    fn probability_of_replacement(old_state: &State, new_state: &State) -> Real {
585        (old_state.hamiltonian_total() - new_state.hamiltonian_total())
586            .exp()
587            .min(1_f64)
588            .max(0_f64)
589    }
590
591    fn next_element_default<Rng>(
592        &mut self,
593        state: State,
594        rng: &mut Rng,
595    ) -> Result<State, Self::Error>
596    where
597        Rng: rand::Rng + ?Sized,
598    {
599        let potential_next = self.potential_next_element(&state, rng)?;
600        let proba = Self::probability_of_replacement(&state, &potential_next)
601            .min(1_f64)
602            .max(0_f64);
603        self.prob_replace_last = proba;
604        let d = rand::distributions::Bernoulli::new(proba).unwrap();
605        if d.sample(rng) {
606            self.has_replace_last = true;
607            Ok(potential_next)
608        }
609        else {
610            self.has_replace_last = false;
611            Ok(state)
612        }
613    }
614}