simple_optimization/
util.rs

1use print_duration::print_duration;
2
3use std::{
4    convert::TryInto,
5    io::{stdout, Write},
6    sync::{
7        atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering},
8        Arc, Mutex,
9    },
10    thread,
11    time::{Duration, Instant},
12};
13
14pub fn update_execution_position<const N: usize>(
15    i: usize,
16    execution_position_timer: Instant,
17    thread_execution_position: &Arc<AtomicU8>,
18    thread_execution_times: &Arc<[Mutex<(Duration, u64)>; N]>,
19) -> Instant {
20    {
21        let mut data = thread_execution_times[i - 1].lock().unwrap();
22        data.0 += execution_position_timer.elapsed();
23        data.1 += 1;
24    }
25    thread_execution_position.store(i as u8, Ordering::SeqCst);
26    Instant::now()
27}
28
29/// The struct defining polling data.
30pub struct Polling {
31    /// How often to poll the process.
32    pub poll_rate: Duration,
33    /// If to print progress.
34    pub printing: bool,
35    /// If to exit early if the evaluation function produces a value less than or equal to the given value.
36    pub early_exit_minimum: Option<f64>,
37    /// If to print thread execution information.
38    pub thread_execution_reporting: bool,
39}
40impl Polling {
41    const DEFAULT_POLL_RATE: Duration = Duration::from_millis(10);
42    /// Default but giving `printing` and `early_exit_minimum`.
43    pub fn new(printing: bool, early_exit_minimum: Option<f64>) -> Self {
44        Self {
45            poll_rate: Polling::DEFAULT_POLL_RATE,
46            printing,
47            early_exit_minimum,
48            thread_execution_reporting: false,
49        }
50    }
51}
52impl Default for Polling {
53    /// The typical parameters you might want.
54    /// ```ignore
55    /// Self {
56    ///     poll_rate: Polling::DEFAULT_POLL_RATE, // `Duration::from_millis(10);`
57    ///     printing: true,
58    ///     early_exit_minimum: None,
59    ///     thread_execution_reporting: false,
60    /// }
61    /// ```
62    fn default() -> Self {
63        Self {
64            poll_rate: Polling::DEFAULT_POLL_RATE,
65            printing: true,
66            early_exit_minimum: None,
67            thread_execution_reporting: false,
68        }
69    }
70}
71
72pub fn poll<const N: usize>(
73    data: Polling,
74    // Current count of each thread.
75    counters: Vec<Arc<AtomicU64>>,
76    offset: u64,
77    // Final total iterations.
78    iterations: u64,
79    // Best values of each thread.
80    thread_bests: Vec<Arc<Mutex<f64>>>,
81    // Early exit switch.
82    thread_exit: Arc<AtomicBool>,
83    // Current positions of execution of each thread.
84    thread_execution_positions: Vec<Arc<AtomicU8>>,
85    // Current average times between execution positions for each thread
86    thread_execution_times: Vec<Arc<[Mutex<(Duration, u64)>; N]>>,
87) {
88    let start = Instant::now();
89    let mut stdout = stdout();
90    let mut count = offset
91        + counters
92            .iter()
93            .map(|c| c.load(Ordering::SeqCst))
94            .sum::<u64>();
95
96    if data.printing {
97        println!("{:20}", iterations);
98    }
99
100    let mut poll_time = Instant::now();
101    let mut held_best: f64 = f64::MAX;
102
103    let mut held_average_execution_times: [(Duration, u64); N] =
104        vec![(Duration::new(0, 0), 0); N].try_into().unwrap();
105    let mut held_recent_execution_times: [Duration; N] =
106        vec![Duration::new(0, 0); N].try_into().unwrap();
107    while count < iterations {
108        if data.printing {
109            // loop {
110            let percent = count as f32 / iterations as f32;
111
112            // If count == 0, give 00... for remaining time as placeholder
113            let remaining_time_estimate = if count == 0 {
114                Duration::new(0, 0)
115            } else {
116                start.elapsed().div_f32(percent)
117            };
118            print!(
119                "\r{:20} ({:.2}%) {} / {} [{}] {}\t",
120                count,
121                100. * percent,
122                print_duration(start.elapsed(), 0..3),
123                print_duration(remaining_time_estimate, 0..3),
124                if held_best == f64::MAX {
125                    String::from("?")
126                } else {
127                    format!("{}", held_best)
128                },
129                if data.thread_execution_reporting {
130                    let (average_execution_times, recent_execution_times): (
131                        Vec<String>,
132                        Vec<String>,
133                    ) = (0..thread_execution_times[0].len())
134                        .map(|i| {
135                            let (mut sum, mut num) = (Duration::new(0, 0), 0);
136                            for n in 0..thread_execution_times.len() {
137                                {
138                                    let mut data = thread_execution_times[n][i].lock().unwrap();
139                                    sum += data.0;
140                                    held_average_execution_times[i].0 += data.0;
141                                    num += data.1;
142                                    held_average_execution_times[i].1 += data.1;
143                                    *data = (Duration::new(0, 0), 0);
144                                }
145                            }
146                            if num > 0 {
147                                held_recent_execution_times[i] = sum.div_f64(num as f64);
148                            }
149                            (
150                                if held_average_execution_times[i].1 > 0 {
151                                    format!(
152                                        "{:.1?}",
153                                        held_average_execution_times[i]
154                                            .0
155                                            .div_f64(held_average_execution_times[i].1 as f64)
156                                    )
157                                } else {
158                                    String::from("?")
159                                },
160                                if held_recent_execution_times[i] > Duration::new(0, 0) {
161                                    format!("{:.1?}", held_recent_execution_times[i])
162                                } else {
163                                    String::from("?")
164                                },
165                            )
166                        })
167                        .unzip();
168
169                    let execution_positions: Vec<u8> = thread_execution_positions
170                        .iter()
171                        .map(|pos| pos.load(Ordering::SeqCst))
172                        .collect();
173                    format!(
174                        "{{ [{}] [{}] {:.?} }}",
175                        recent_execution_times.join(", "),
176                        average_execution_times.join(", "),
177                        execution_positions
178                    )
179                } else {
180                    String::from("")
181                }
182            );
183            stdout.flush().unwrap();
184        }
185
186        // Updates best and does early exiting
187        match (data.early_exit_minimum, data.printing) {
188            (Some(early_exit), true) => {
189                for thread_best in thread_bests.iter() {
190                    let thread_best_temp = *thread_best.lock().unwrap();
191                    if thread_best_temp < held_best {
192                        held_best = thread_best_temp;
193                        if thread_best_temp <= early_exit {
194                            thread_exit.store(true, Ordering::SeqCst);
195                            println!();
196                            return;
197                        }
198                    }
199                }
200            }
201            (None, true) => {
202                for thread_best in thread_bests.iter() {
203                    let thread_best_temp = *thread_best.lock().unwrap();
204                    if thread_best_temp < held_best {
205                        held_best = thread_best_temp;
206                    }
207                }
208            }
209            (Some(early_exit), false) => {
210                for thread_best in thread_bests.iter() {
211                    if *thread_best.lock().unwrap() <= early_exit {
212                        thread_exit.store(true, Ordering::SeqCst);
213                        return;
214                    }
215                }
216            }
217            (None, false) => {}
218        }
219
220        thread::sleep(saturating_sub(data.poll_rate, poll_time.elapsed()));
221        poll_time = Instant::now();
222
223        count = offset
224            + counters
225                .iter()
226                .map(|c| c.load(Ordering::SeqCst))
227                .sum::<u64>();
228    }
229
230    if data.printing {
231        println!(
232            "\r{:20} (100.00%) {} / {} [{}] {}\t",
233            count,
234            print_duration(start.elapsed(), 0..3),
235            print_duration(start.elapsed(), 0..3),
236            held_best,
237            if data.thread_execution_reporting {
238                let (average_execution_times, recent_execution_times): (Vec<String>, Vec<String>) =
239                    (0..thread_execution_times[0].len())
240                        .map(|i| {
241                            let (mut sum, mut num) = (Duration::new(0, 0), 0);
242                            for n in 0..thread_execution_times.len() {
243                                {
244                                    let mut data = thread_execution_times[n][i].lock().unwrap();
245                                    sum += data.0;
246                                    held_average_execution_times[i].0 += data.0;
247                                    num += data.1;
248                                    held_average_execution_times[i].1 += data.1;
249                                    *data = (Duration::new(0, 0), 0);
250                                }
251                            }
252                            if num > 0 {
253                                held_recent_execution_times[i] = sum.div_f64(num as f64);
254                            }
255                            (
256                                if held_average_execution_times[i].1 > 0 {
257                                    format!(
258                                        "{:.1?}",
259                                        held_average_execution_times[i]
260                                            .0
261                                            .div_f64(held_average_execution_times[i].1 as f64)
262                                    )
263                                } else {
264                                    String::from("?")
265                                },
266                                if held_recent_execution_times[i] > Duration::new(0, 0) {
267                                    format!("{:.1?}", held_recent_execution_times[i])
268                                } else {
269                                    String::from("?")
270                                },
271                            )
272                        })
273                        .unzip();
274
275                let execution_positions: Vec<u8> = thread_execution_positions
276                    .iter()
277                    .map(|pos| pos.load(Ordering::SeqCst))
278                    .collect();
279                format!(
280                    "{{ [{}] [{}] {:.?} }}",
281                    recent_execution_times.join(", "),
282                    average_execution_times.join(", "),
283                    execution_positions
284                )
285            } else {
286                String::from("")
287            }
288        );
289        stdout.flush().unwrap();
290    }
291}
292/// Since `Duration::saturating_sub` is unstable this is an alternative.
293fn saturating_sub(a: Duration, b: Duration) -> Duration {
294    if let Some(dur) = a.checked_sub(b) {
295        dur
296    } else {
297        Duration::new(0, 0)
298    }
299}