sampling/wang_landau/
wang_landau.rs

1use{
2    crate::{*, traits::*},
3    rand::Rng,
4    num_traits::{Bounded, ops::wrapping::*, identities::*},
5    std::{marker::PhantomData, io::Write, num::*}
6};
7
8#[cfg(feature = "serde_support")]
9use serde::{Serialize, Deserialize};
10
11/// # The 1/t Wang Landau approach comes from this paper
12/// > R. E. Belardinelli and V. D. Pereyra,
13/// > “Fast algorithm to calculate density of states,”
14/// > Phys. Rev. E **75**: 046701 (2007), DOI [10.1103/PhysRevE.75.046701](https://doi.org/10.1103/PhysRevE.75.046701)
15/// 
16/// * The original Wang Landau algorithm comes from this paper
17/// > F. Wang and D. P. Landau,
18/// > “Efficient, multiple-range random walk algorithm to calculate the density of states,” 
19/// > Phys. Rev. Lett. **86**, 2050–2053 (2001), DOI [10.1103/PhysRevLett.86.2050](https://doi.org/10.1103/PhysRevLett.86.2050)
20#[derive(Debug, Clone)]
21#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
22pub struct WangLandau1T<Hist, Rng, Ensemble, S, Res, Energy>{
23    pub(crate) ensemble: Ensemble,
24    pub(crate) rng: Rng,
25    pub(crate) marker_res: PhantomData<Res>,
26    pub(crate) steps: Vec<S>,
27    mode: WangLandauMode,
28    pub(crate) log_density: Vec<f64>,
29    pub(crate) log_f: f64,
30    pub(crate) log_f_threshold: f64,
31    pub(crate) step_size: usize,
32    step_count: usize,
33    accepted_steps_total: usize,
34    recected_steps_total: usize,
35    accepted_steps_current: usize,
36    recected_steps_current: usize,
37    pub(crate) old_bin: usize,
38    pub(crate) hist: Hist,
39    pub(crate) old_energy: Option<Energy>,
40    check_refine_every: usize,
41}
42
43impl<Hist, R, E, S, Res, Energy> GlueAble<Hist> for WangLandau1T<Hist, R, E, S, Res, Energy>
44    where Hist: Clone
45{
46    fn push_glue_entry_ignoring(
47            &self, 
48            job: &mut GlueJob<Hist>,
49            ignore_idx: &[usize]
50        ) {
51        if !ignore_idx.contains(&0)
52        {
53            let sim_progress = SimProgress::LogF(self.log_f);
54            let rejected = self.total_steps_rejected() as u64;
55            let accepted = self.total_steps_accepted() as u64;
56
57            let stats = IntervalSimStats{
58                sim_progress,
59                interval_sim_type: SimulationType::WangLandau1T,
60                rejected_steps: rejected,
61                accepted_steps: accepted,
62                replica_exchanges: None,
63                proposed_replica_exchanges: None,
64                merged_over_walkers: NonZeroUsize::new(1).unwrap()
65            };
66
67            let glue_entry = GlueEntry{
68                hist: self.hist.clone(),
69                prob: self.log_density.clone(),
70                log_base: LogBase::BaseE,
71                interval_stats: stats
72            };
73            job.collection.push(glue_entry);
74        }
75    }
76}
77
78impl<Hist, Rng, Ensemble, S, Res, Energy>WangLandau1T<Hist, Rng, Ensemble, S, Res, Energy>
79{
80    /// Returns internal ensemble, histogram and Rng
81    pub fn into_inner(self) -> (Ensemble, Hist, Rng)
82    {
83        (self.ensemble, self.hist, self.rng)
84    }
85}
86
87
88impl<Hist, R, E, S, Res, Energy> WangLandau 
89    for WangLandau1T<Hist, R, E, S, Res, Energy>
90{
91    #[inline(always)]
92    fn log_f(&self) -> f64
93    {
94        self.log_f
95    }
96
97    #[inline(always)]
98    fn log_f_threshold(&self) -> f64
99    {
100        self.log_f_threshold
101    }
102
103    fn set_log_f_threshold(&mut self, log_f_threshold: f64) -> Result<f64, WangLandauErrors>
104    {
105        if !log_f_threshold.is_finite() || log_f_threshold.is_sign_negative() {
106            return Err(WangLandauErrors::InvalidLogFThreshold);
107        }
108        let old_threshold = self.log_f_threshold;
109        self.log_f_threshold = log_f_threshold;
110        Ok(old_threshold)
111    }
112
113    #[inline(always)]
114    fn log_density(&self) -> &Vec<f64>
115    {
116        &self.log_density
117    }
118
119    fn write_log<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
120        writeln!(writer,
121            "#Acceptance prob_total: {}\n#Acceptance prob current: {}\n#total_steps: {}\n#log_f: {:e}\n#Current_mode {:?}",
122            self.fraction_accepted_total(),
123            self.fraction_accepted_current(),
124            self.step_counter(),
125            self.log_f(),
126            self.mode
127        )?;
128        writeln!(
129            writer,
130            "#total_steps_accepted: {}\n#total_steps_rejected: {}\n#current_accepted_steps: {}\n#current_rejected_steps: {}",
131            self.accepted_steps_total,
132            self.recected_steps_total,
133            self.accepted_steps_current,
134            self.recected_steps_current
135        )
136    }
137
138    #[inline(always)]
139    fn mode(&self) -> WangLandauMode
140    {
141        self.mode
142    }
143
144    #[inline(always)]
145    fn step_counter(&self) -> usize
146    {
147        self.step_count
148    }
149
150    #[inline(always)]
151    fn total_steps_rejected(&self) -> usize {
152        self.recected_steps_total
153    }
154
155    #[inline(always)]
156    fn total_steps_accepted(&self) -> usize {
157        self.accepted_steps_total
158    }
159}
160
161impl<Hist, R, E, S, Res, Energy> 
162    WangLandau1T<Hist, R, E, S, Res, Energy>
163where 
164    Hist: Histogram + HistogramVal<Energy>
165{
166    /// # Check if `self` is initialized
167    /// * if this returns true, you can begin the WangLandau simulation
168    /// * otherwise call one of the `self.init*` methods
169    pub fn is_initialized(&self) -> bool
170    {
171        match &self.old_energy{
172            None => false,
173            Some(e) => {
174                self.hist.is_inside(e)
175            }
176        }
177    }
178}
179
180/// Possible errors when setting initial guess
181#[derive(Clone, Copy, Debug)]
182pub enum SetInitialError{
183    /// # Dimensions do not match! 
184    /// The length of the initial guess and the amount of bins have to be the same 
185    DimensionError,
186    /// All values inside the initial guess have to be finite
187    NonFiniteEncountered,
188    /// log_f has to fullfill 0.0 < log_f < 10.0
189    InvalidLogF
190}
191
192
193impl <Hist, R, E, S, Res, Energy> WangLandauEnsemble<E> 
194    for WangLandau1T<Hist, R, E, S, Res, Energy>
195{
196    #[inline(always)]
197    fn ensemble(&self) -> &E {
198        &self.ensemble
199    }
200
201    #[inline(always)]
202    unsafe fn ensemble_mut(&mut self) -> &mut E {
203        &mut self.ensemble
204    }
205}
206
207impl <Hist, R, E, S, Res, Energy> WangLandauEnergy<Energy> 
208    for WangLandau1T<Hist, R, E, S, Res, Energy>
209{
210    #[inline(always)]
211    fn energy(&self) -> Option<&Energy> {
212        self.old_energy.as_ref()
213    }
214}
215
216impl <Hist, R, E, S, Res, Energy> WangLandauHist<Hist> 
217    for WangLandau1T<Hist, R, E, S, Res, Energy>
218{
219    #[inline(always)]
220    fn hist(&self) -> &Hist {
221        &self.hist   
222    }
223}
224
225impl<Hist, R, E, S, Res, Energy> 
226    WangLandau1T<Hist, R, E, S, Res, Energy>
227{
228    /// # Acceptance rate
229    /// Fraction of performed wang landau steps, that were accepted
230    fn fraction_accepted_total(&self) -> f64
231    {
232        let sum = self.accepted_steps_total + self.recected_steps_total;
233        self.accepted_steps_total as f64 / sum as f64
234    }
235
236    /// # Acceptance rate since last Refinement
237    /// Fraction of performed wang landau steps since 
238    /// the last time, the factor f was refined, that were accepted
239    fn fraction_accepted_current(&self) -> f64
240    {
241        let total = self.accepted_steps_current + self.recected_steps_current;
242        if total == 0 {
243            f64::NAN
244        } else {
245            self.accepted_steps_current as f64 / total as f64
246        }
247    }
248
249    /// # Set the initial guess for the non-normalized probability estimate
250    /// * `new_guess` your new guess for the probability estimate. Its length has to equal the number of bins of the internal histogram
251    ///   which is the same as the length of the old estimate which you can get by calling [log_density](Self::log_density). All contained values have 
252    ///   to be finite
253    /// * `new_log_f`: Which log_f to start at? 0.0 < log_f <= 10.0 has to be true. 
254    ///   If you don't know what's best I recommend starting with log_f=1.0, the better your probability estimate is, the smaller this value can be
255    /// # Note
256    /// This will reset the calculation. Meaning you will have to call one of the initializing functions like `init_greedy_heuristic`again 
257    /// and all internal counters are reset to 0
258    pub fn set_initial_probability_guess(mut self, new_guess: Vec<f64>, new_log_f: f64) -> Result<Self, SetInitialError>
259    where Hist: Histogram
260    {
261        if 0.0 >= new_log_f || new_log_f > 10.0 {
262            Err(SetInitialError::InvalidLogF)
263        }
264        else if new_guess.len() != self.log_density.len()
265        { 
266            Err(SetInitialError::DimensionError)
267        } else if new_guess.iter().any(|val| !val.is_finite())
268        {
269            Err(SetInitialError::NonFiniteEncountered)
270        } else {
271            self.log_density = new_guess;
272            self.log_f = new_log_f;
273            self.step_count = 0;
274            self.accepted_steps_current = 0;
275            self.accepted_steps_total = 0;
276            self.recected_steps_current = 0;
277            self.recected_steps_total = 0;
278            self.mode = WangLandauMode::RefineOriginal;
279            self.hist.reset();
280            self.old_energy = None;
281            self.old_bin = usize::MAX;
282            Ok(self)
283        }
284    }
285}
286
287
288impl<Hist, R, E, S, Res, Energy> 
289    WangLandau1T<Hist, R, E, S, Res, Energy>
290where 
291    R: Rng,
292    E: MarkovChain<S, Res>,
293    Energy: Clone,
294    Hist: Histogram + HistogramVal<Energy>
295{
296    /// # Create a new WangLandau simulation
297    /// **IMPORTANT** You have to call one of the `init*` functions, 
298    /// to create a valid state, before you can start the simulation
299    /// ## Parameter
300    /// * `log_f_threshold`: how small should the ln(f) (see paper) become
301    ///   until the simulation is finished?
302    /// * `ensemble`: The ensemble to explore. 
303    ///   Current state of ensemble will be used as inital condition for the `init*` functions
304    /// * `step_size`: The markov steps will be performed with this step size, e.g., 
305    ///   `ensemble.m_steps(step_size)`
306    /// * `histogram`: Provides the binning. You can either use one of the already implemented
307    ///   histograms, like `HistU32Fast`, `HistU32`, `HistF64` etc. or implement your own by 
308    ///   implementing the traits `Histogram + HistogramVal<Energy>` yourself
309    /// * `check_refine_every`: how often to check, if every bin in the histogram was hit.
310    ///   Needs to be at least 1. Good values depend on the problem at hand, but if you are 
311    ///   unsure, you can start with a value like 1000 
312    pub fn new(
313        log_f_threshold: f64,
314        ensemble: E,
315        rng: R,
316        step_size: usize,
317        histogram: Hist,
318        check_refine_every: usize
319    )-> Result<Self, WangLandauErrors>
320    {
321        if !log_f_threshold.is_finite() || log_f_threshold.is_sign_negative() 
322        {
323            return Err(WangLandauErrors::InvalidLogFThreshold);
324        }
325        else if check_refine_every == 0 {
326            return Err(WangLandauErrors::CheckRefineEvery0)
327        }
328        let log_density = vec![0.0; histogram.bin_count()];
329        let steps = Vec::with_capacity(step_size);
330
331        Ok(
332            Self{
333                ensemble,
334                step_count: 0,
335                step_size,
336                hist: histogram,
337                rng,
338                marker_res: PhantomData::<Res>,
339                log_f: 1.0,
340                log_density,
341                log_f_threshold,
342                mode: WangLandauMode::RefineOriginal,
343                recected_steps_current: 0,
344                recected_steps_total: 0,
345                accepted_steps_current: 0,
346                accepted_steps_total: 0,
347                old_bin: usize::MAX,
348                old_energy: None,
349                check_refine_every,
350                steps,
351            }
352        )
353    }
354
355    fn init<F>(
356        &mut self,
357        energy_fn: F,
358        step_limit: Option<u64>
359    ) -> Result<(), WangLandauErrors>
360        where F: Fn(&mut E) -> Option<Energy>
361    {
362        self.old_energy = energy_fn(&mut self.ensemble);
363        if self.old_energy.is_some(){
364            return Ok(());
365        }
366
367        match step_limit {
368            None => {
369                loop {
370                    self.ensemble.m_steps_quiet(self.step_size);
371                    self.old_energy = energy_fn(&mut self.ensemble);
372        
373                    if self.old_energy.is_some(){
374                        self.count_accepted();
375                        return Ok(());
376                    }
377                    self.count_rejected();
378                }
379            },
380            Some(limit) => {
381                for _ in 0..limit {
382                    self.ensemble.m_steps_quiet(self.step_size);
383                    self.old_energy = energy_fn(&mut self.ensemble);
384        
385                    if self.old_energy.is_some(){
386                        self.count_accepted();
387                        return Ok(());
388                    }
389                    self.count_rejected();
390                }
391                Err(WangLandauErrors::InitFailed)
392            }
393        }
394    }
395
396    fn greedy_helper<F, H, J>(
397        &mut self,
398        old_distance: &mut J,
399        energy_fn: F,
400        distance_fn: H,
401    )   where F: Fn(&mut E) -> Option<Energy> + Copy,
402            H: Fn(&Hist, &Energy) -> J,
403            J: PartialOrd
404    {
405        self.ensemble
406            .m_steps(self.step_size, &mut self.steps);
407
408        
409        if let Some(energy) = energy_fn(&mut self.ensemble) {
410            let distance = distance_fn(&self.hist, &energy);
411            if distance <= *old_distance {
412                self.old_energy = Some(energy);
413                *old_distance = distance;
414                self.count_accepted();
415                
416                return;
417            }
418        }
419
420        
421        self.count_rejected();
422        self.ensemble
423            .undo_steps_quiet(&self.steps);
424        
425    }
426
427    /// # Find a valid starting Point
428    /// * if the ensemble is already at a valid starting point,
429    ///   the ensemble is left unchanged (as long as your energy calculation does not change the ensemble)
430    /// * Uses a greedy heuristic. Performs markov steps. If that brought us closer to the target interval,
431    ///   the step is accepted. Otherwise it is rejected
432    /// # Parameter
433    /// * `step_limit`: Some(val) -> val is max number of steps tried, if no valid state is found, it will return an Error. None -> will loop until either 
434    ///   a valid state is found or forever
435    /// * `energy_fn` function calculating `Some(energy)` of the system
436    ///   or rather the Parameter of which you wish to obtain the probability distribution.
437    ///   Has to be the same function as used for the wang landau simulation later.
438    ///   If there are any states, for which the calculation is invalid, `None` should be returned
439    /// * steps resulting in ensembles for which `energy_fn(&mut ensemble)` is `None`
440    ///   will always be rejected 
441    pub fn init_greedy_heuristic<F>(
442        &mut self,
443        energy_fn: F,
444        step_limit: Option<u64>,
445    ) -> Result<(), WangLandauErrors>
446    where F: Fn(&mut E) -> Option<Energy>,
447    {
448        self.init(&energy_fn, step_limit)?;
449        let mut old_distance = self.hist
450            .distance(self.old_energy_ref());
451        let mut step_count = 0;
452        while old_distance != 0.0 {
453            self.greedy_helper(
454                &mut old_distance,
455                &energy_fn,
456                |hist, energy| {
457                    hist.distance(energy)
458                }
459            );
460            if let Some(limit) = step_limit {
461                if limit == step_count{
462                    return Err(WangLandauErrors::InitFailed);
463                }
464                step_count += 1;
465            }
466        }
467        self.end_init();
468        Ok(())
469    }
470
471    /// # Find a valid starting Point
472    /// * if the ensemble is already at a valid starting point,
473    ///   the ensemble is left unchanged (as long as your energy calculation does not change the ensemble)
474    /// * Uses overlapping intervals. Accepts a step, if the resulting ensemble is in the same interval as before,
475    ///   or it is in an interval closer to the target interval
476    /// * Take a look at the [`HistogramIntervalDistance` trait](`crate::HistogramIntervalDistance`)
477    /// # Parameter
478    /// * `step_limit`: Some(val) -> val is max number of steps tried, if no valid state is found, it will return an Error. None -> will loop until either 
479    ///   a valid state is found or forever
480    /// * `energy_fn` function calculating `Some(energy)` of the system
481    ///   or rather the Parameter of which you wish to obtain the probability distribution.
482    ///   Has to be the same function as used for the wang landau simulation later.
483    ///   If there are any states, for which the calculation is invalid, `None` should be returned
484    /// * steps resulting in ensembles for which `energy_fn(&mut ensemble)` is `None`
485    ///   will always be rejected 
486    pub fn init_interval_heuristik<F>(
487        &mut self,
488        overlap: NonZeroUsize,
489        energy_fn: F,
490        step_limit: Option<u64>,
491    ) -> Result<(), WangLandauErrors>
492    where F: Fn(&mut E) -> Option<Energy>,
493        Hist: HistogramIntervalDistance<Energy>
494    {
495        self.init(&energy_fn, step_limit)?;
496        let mut old_dist = self.hist
497            .interval_distance_overlap(
498                self.old_energy_ref(),
499                overlap
500            );
501        
502        let dist = |h: &Hist, val: &Energy| h.interval_distance_overlap(val, overlap);
503        let mut step_count = 0;
504        while old_dist != 0 {
505            self.greedy_helper(
506                &mut old_dist,
507                &energy_fn,
508                dist
509            );
510            if let Some(limit) = step_limit {
511                if limit == step_count{
512                    return Err(WangLandauErrors::InitFailed);
513                }
514                step_count += 1;
515            }
516        }
517        self.end_init();
518        Ok(())
519    }
520
521    /// # Find a valid starting Point
522    /// * if the ensemble is already at a valid starting point,
523    ///   the ensemble is left unchanged (as long as your energy calculation does not change the ensemble)
524    /// * `overlap` - see [`HistogramIntervalDistance` trait](`crate::HistogramIntervalDistance`)
525    ///   Should be greater than 0 and smaller than the number of bins in your histogram. E.g. `overlap = 3` if you have 200 bins
526    /// * `mid` - should be something like `128u8`, `0i8` or `0i16`. It is very unlikely that using a type with more than 16 bit makes sense for mid
527    /// * `step_limit`: Some(val) -> val is max number of steps tried, if no valid state is found, it will return an Error. None -> will loop until either 
528    ///   a valid state is found or forever
529    /// * alternates between greedy and interval heuristic every time a wrapping counter passes `mid` or `U::min_value()`
530    /// * I recommend using this heuristic, if you do not know which one to use
531    /// # Parameter
532    /// * `energy_fn` function calculating `Some(energy)` of the system
533    ///   or rather the Parameter of which you wish to obtain the probability distribution.
534    ///   Has to be the same function as used for the wang landau simulation later.
535    ///   If there are any states, for which the calculation is invalid, `None` should be returned
536    /// * steps resulting in ensembles for which `energy_fn(&mut ensemble)` is `None`
537    ///   will always be rejected 
538    pub fn init_mixed_heuristik<F, U>
539    (
540        &mut self,
541        overlap: NonZeroUsize,
542        mid: U,
543        energy_fn: F,
544        step_limit: Option<u64>
545    ) -> Result<(), WangLandauErrors>
546    where F: Fn(&mut E) -> Option<Energy>,
547        Hist: HistogramIntervalDistance<Energy>,
548        U: One + Bounded + WrappingAdd + Eq + PartialOrd
549    {
550        self.init(&energy_fn, step_limit)?;
551        if self.hist
552            .is_inside(
553                self.old_energy_ref()
554            )
555        {
556            self.end_init();
557            return Ok(());
558        }    
559        
560        let mut old_dist = f64::INFINITY;
561        let mut old_dist_interval = usize::MAX;
562        let mut counter: U = U::min_value();
563        let min_val = U::min_value();
564        let one = U::one();
565        let dist_interval = |h: &Hist, val: &Energy| h.interval_distance_overlap(val, overlap);
566        let mut step_count = 0;
567        loop {
568            if counter == min_val {
569                let current_energy = self.old_energy_ref();
570                old_dist = self.hist.distance(current_energy);
571            }else if counter == mid {
572                let current_energy = self.old_energy_ref();
573                old_dist_interval = dist_interval(&self.hist, current_energy);
574            }
575            if counter < mid {
576                self.greedy_helper(
577                    &mut old_dist,
578                    &energy_fn,
579                    |hist, val| {
580                        hist.distance(val)
581                    }
582                );
583                if old_dist == 0.0 {
584                    break;
585                }
586            } else {
587                self.greedy_helper(
588                    &mut old_dist_interval,
589                    &energy_fn,
590                    dist_interval,
591                );
592                if old_dist_interval == 0 {
593                    break;
594                }
595            }
596            counter = counter.wrapping_add(&one);
597            if let Some(limit) = step_limit {
598                if limit == step_count{
599                    return Err(WangLandauErrors::InitFailed);
600                }
601                step_count += 1;
602            }
603        }
604        self.end_init();
605        Ok(())
606    }
607
608    fn end_init(&mut self)
609    {
610        self.old_bin = self.hist
611            .get_bin_index( 
612                self.old_energy_ref()
613            ).expect("Error in heuristic - old bin invalid");
614    }
615
616    fn old_energy_clone(&self) -> Energy {
617        self.old_energy_ref()
618            .clone()
619    }
620
621    fn old_energy_ref(&self) -> &Energy {
622        self.old_energy
623            .as_ref()
624            .unwrap()
625    }
626
627
628    fn count_accepted(&mut self){
629        self.ensemble.steps_accepted(&self.steps);
630        self.accepted_steps_current += 1;
631        self.accepted_steps_total += 1;
632    }
633
634    fn count_rejected(&mut self){
635        self.ensemble.steps_rejected(&self.steps);
636        self.recected_steps_current += 1;
637        self.recected_steps_total += 1;
638    }
639
640
641    fn check_refine(&mut self)
642    {
643        match self.mode{
644            WangLandauMode::Refine1T => {
645                self.log_f = self.log_f_1_t();
646            },
647            WangLandauMode::RefineOriginal => {
648                if self.step_count % self.check_refine_every == 0 
649                    && !self.hist.any_bin_zero() 
650                {
651                    self.recected_steps_current = 0;
652                    self.accepted_steps_current = 0;
653                    let ref_1_t = self.log_f_1_t();
654                    self.log_f *= 0.5;
655                    if self.log_f < ref_1_t {
656                        self.log_f = ref_1_t;
657                        self.mode = WangLandauMode::Refine1T;
658                    }
659                    self.hist.reset();
660                }
661            }
662        }
663    }
664
665
666    fn wl_step_helper(&mut self, energy: Option<Energy>)
667    {
668        let current_energy = match energy 
669        {
670            Some(energy) => energy,
671            None => {
672                self.count_rejected();
673                self.hist.increment_index(self.old_bin).unwrap();
674                self.log_density[self.old_bin] += self.log_f;
675                self.ensemble.undo_steps_quiet(&self.steps);
676                return;
677            }
678        };
679        
680        match self.hist.get_bin_index(&current_energy)
681        {
682            Ok(current_bin) => {
683                let accept_prob = self.metropolis_acception_prob( current_bin);
684
685                if self.rng.random::<f64>() > accept_prob {
686                    // reject step
687                    self.count_rejected();
688                    self.ensemble.undo_steps_quiet(&self.steps);
689                } else {
690                    // accept step
691                    self.count_accepted();
692                    
693                    self.old_energy = Some(current_energy);
694                    self.old_bin = current_bin;
695                }
696            },
697            _  => {
698                // invalid step -> reject
699                self.count_rejected();
700                self.ensemble.undo_steps_quiet(&self.steps);
701            }
702        };
703        
704        self.hist.increment_index(self.old_bin).unwrap();
705        self.log_density[self.old_bin] += self.log_f;
706    }
707
708    /// # Wang Landau Step
709    /// * performs a single Wang Landau step
710    /// # Parameter
711    /// * `energy_fn` function calculating `Some(energy)` of the system
712    ///   or rather the Parameter of which you wish to obtain the probability distribution.
713    ///   If there are any states, for which the calculation is invalid, `None` should be returned
714    /// * steps resulting in ensembles for which `energy_fn(&mut ensemble)` is `None`
715    ///   will always be rejected 
716    /// # Important
717    /// * You have to call one of the `self.init*` functions before calling this one - 
718    ///   **will panic otherwise**
719    pub fn wang_landau_step<F>(
720        &mut self,
721        energy_fn: F,
722    )where F: Fn(&E) -> Option<Energy>
723    {
724        unsafe {
725            self.wang_landau_step_unsafe(|e| energy_fn(e))
726        }
727    }
728
729    /// # Wang Landau Step
730    /// * if possible, use `self.wang_landau_step()` instead - it is safer
731    /// * performs a single Wang Landau step
732    /// # Parameter
733    /// * `energy_fn` function calculating `Some(energy)` of the system
734    ///   or rather the Parameter of which you wish to obtain the probability distribution.
735    ///   If there are any states, for which the calculation is invalid, `None` should be returned
736    /// * steps resulting in ensembles for which `energy_fn(&mut ensemble)` is `None`
737    ///   will always be rejected 
738    /// # Safety
739    /// * You have to call one of the `self.init*` functions before calling this one - 
740    ///   **will panic otherwise**
741    /// * unsafe, because you have to make sure, that the `energy_fn` function 
742    ///   does not change the state of the ensemble in such a way, that the result of
743    ///   `energy_fn` changes when called again. Maybe do cleanup at the beginning of the energy function?
744    pub unsafe fn wang_landau_step_unsafe<F>(
745        &mut self,
746        mut energy_fn: F,
747    )where F: FnMut(&mut E) -> Option<Energy>
748    {
749        debug_assert!(
750            self.old_energy.is_some(),
751            "Error - self.old_energy invalid - Did you forget to call one of the `self.init*` members for initialization?"
752        );
753
754        self.step_count += 1;
755
756
757        self.ensemble.m_steps(self.step_size, &mut self.steps);
758        
759        self.check_refine();
760        let current_energy = energy_fn(&mut self.ensemble);
761        
762        self.wl_step_helper(current_energy);
763    }
764
765    /// # Wang Landau Step
766    /// * performs a single Wang Landau step
767    /// # Parameter
768    /// * `energy_fn` function calculating the energy of the system **on the fly**
769    /// * **steps resulting in invalid ensembles are not allowed!**
770    /// # Important
771    /// * You have to call one of the `self.init*` functions before calling this one - 
772    ///   **will panic otherwise**
773    pub fn wang_landau_step_acc<F>(
774        &mut self,
775        energy_fn: F,
776    )
777    where F: FnMut(&E, &S, &mut Energy)
778    {
779        debug_assert!(
780            self.old_energy.is_some(),
781            "Error - self.old_energy invalid - Did you forget to call one of the `self.init*` members for initialization?"
782        );
783
784        self.step_count += 1;
785
786        let mut new_energy = self.old_energy_clone();
787
788        self.ensemble
789            .m_steps_acc(
790                self.step_size,
791                &mut self.steps,
792                &mut new_energy,
793                energy_fn
794            );
795        
796        self.check_refine();
797        
798        self.wl_step_helper(Some(new_energy));
799
800    }
801
802    /// # Wang Landau
803    /// * perform Wang Landau simulation
804    /// * calls `self.wang_landau_step(energy_fn, valid_ensemble)` until `self.is_finished()` 
805    pub fn wang_landau_convergence<F>(
806        &mut self,
807        energy_fn: F,
808    )where F: Fn(&E) -> Option<Energy>,
809    {
810        while !self.is_finished() {
811            self.wang_landau_step(&energy_fn);
812        }
813    }
814
815    /// # Wang Landau - efficient energy calculation
816    /// * perform Wang Landau simulation
817    /// * calls `self.wang_landau_step_acc(energy_fn, valid_ensemble)` until `self.is_finished()` 
818    pub fn wang_landau_convergence_acc<F>(
819        &mut self,
820        mut energy_fn: F,
821    )where F: FnMut(&E, &S, &mut Energy)
822    {
823        while !self.is_finished() {
824            self.wang_landau_step_acc(&mut energy_fn);
825        }
826    }
827
828    /// # Wang Landau
829    /// * if possible, use `self.wang_landau_convergence()` instead - it is safer
830    /// * perform Wang Landau simulation
831    /// * calls `self.wang_landau_step_unsafe(energy_fn, valid_ensemble)` until `self.is_finished()` 
832    /// # Safety
833    /// * You have mutable access to your ensemble, which is why this function is unsafe. 
834    ///   If you do anything, which changes the future outcome of the energy function, the results will be wrong!
835    ///   I use the unsafe keyword here to force the user to acknowledge that.
836    pub unsafe fn wang_landau_convergence_unsafe<F>(
837        &mut self,
838        mut energy_fn: F,
839    )where F: FnMut(&mut E) -> Option<Energy>,
840    {
841        while !self.is_finished() {
842            self.wang_landau_step_unsafe(&mut energy_fn);
843        }
844    }
845
846    /// # Wang Landau
847    /// * perform Wang Landau simulation
848    /// * calls `self.wang_landau_step(energy_fn)` until `self.is_finished()` 
849    ///   or `condition(&self)` is false
850    pub fn wang_landau_while<F, W>(
851        &mut self,
852        energy_fn: F,
853        mut condition: W
854    ) where F: Fn(&E) -> Option<Energy>,
855        W: FnMut(&Self) -> bool,
856    {
857        while !self.is_finished() && condition(self) {
858            self.wang_landau_step(&energy_fn);
859        }
860    }
861
862    /// # Wang Landau
863    /// * perform Wang Landau simulation
864    /// * calls `self.wang_landau_step(energy_fn)` until `self.is_finished()` 
865    ///   or `condition(&self)` is false
866    pub fn wang_landau_while_acc<F, W>(
867        &mut self,
868        mut energy_fn: F,
869        mut condition: W
870    ) where F: FnMut(&E, &S, &mut Energy),
871        W: FnMut(&Self) -> bool,
872    {
873        while !self.is_finished() && condition(self) {
874            self.wang_landau_step_acc(&mut energy_fn);
875        }
876    }
877
878    /// # Wang Landau
879    /// * if possible, use `self.wang_landau_while()` instead - it is safer
880    /// * perform Wang Landau simulation
881    /// * calls `self.wang_landau_step(energy_fn)` until `self.is_finished()` 
882    ///   or `condition(&self)` is false
883    /// # Safety
884    /// * You have mutable access to your ensemble, which is why this function is unsafe. 
885    ///   If you do anything, which changes the future outcome of the energy function, the results will be wrong!
886    ///   I use the unsafe keyword here to force the user to acknowledge that
887    pub unsafe fn wang_landau_while_unsafe<F, W>(
888        &mut self,
889        mut energy_fn: F,
890        mut condition: W
891    ) where F: FnMut(&mut E) -> Option<Energy>,
892        W: FnMut(&Self) -> bool,
893    {
894        while !self.is_finished() && condition(self) {
895            self.wang_landau_step_unsafe(&mut energy_fn);
896        }
897    }
898
899
900    /// **panics** if index is invalid
901    #[inline(always)]
902    fn metropolis_acception_prob(&self, new_bin: usize) -> f64
903    {
904        
905        (self.log_density[self.old_bin] - self.log_density[new_bin])
906            .exp()
907        
908    }
909}
910
911
912#[cfg(test)]
913mod tests {
914    use super::*;
915    use rand_pcg::Pcg64Mcg;
916    use rand::SeedableRng;
917    use crate::examples::coin_flips::*;
918    #[test]
919    #[cfg_attr(miri,ignore)]
920    fn wl_simulations_equal() {
921        let mut rng = Pcg64Mcg::seed_from_u64(2239790);
922        let ensemble = CoinFlipSequence::new(100, Pcg64Mcg::from_rng(&mut rng));
923        let histogram = HistogramFast::new_inclusive(0, 100).unwrap();
924        let mut wl= WangLandau1T::new(
925            0.0075,
926            ensemble,
927            rng,
928            1,
929            histogram,
930            30
931        ).unwrap();
932
933        wl.init_mixed_heuristik(
934            NonZeroUsize::new(3).unwrap(),
935            6400i16,
936            |e|  {
937                Some(e.head_count())
938            },
939            None
940        ).unwrap();
941
942        let mut wl_backup = wl.clone();
943        let start_wl= std::time::Instant::now();
944        wl.wang_landau_convergence(
945            |e| Some(e.head_count())
946        );
947        let dur_1 = start_wl.elapsed();
948        let start_wl_acc = std::time::Instant::now();
949        wl_backup.wang_landau_convergence_acc(
950            CoinFlipSequence::update_head_count
951        );
952        let dur_2 = start_wl_acc.elapsed();
953        println!("WL: {}, WL_ACC: {}, difference: {}", dur_1.as_nanos(), dur_2.as_nanos(), dur_1.as_nanos() - dur_2.as_nanos());
954
955        // assert, that the different methods lead to the same result
956        for (&log_value, &log_value_acc) in wl.log_density().iter().zip(wl_backup.log_density().iter()){
957            assert_eq!(log_value, log_value_acc);
958        }
959    }
960}