1use crate::{
2 Direction, HyperoptError, ObjectiveError, ObjectiveResult, Pruner, Sampler, Storage,
3 StudyMetadata, StudyState, Trial, TrialContext, TrialState,
4};
5use std::panic::{catch_unwind, AssertUnwindSafe};
6use std::sync::Mutex;
7
8pub struct Study {
15 name: String,
16 direction: Direction,
17 sampler: Mutex<Box<dyn Sampler>>,
18 pruner: Box<dyn Pruner>,
19 storage: Box<dyn Storage>,
20}
21
22impl Study {
23 pub fn new(
28 name: impl Into<String>,
29 direction: Direction,
30 sampler: Box<dyn Sampler>,
31 pruner: Box<dyn Pruner>,
32 storage: Box<dyn Storage>,
33 ) -> Result<Self, HyperoptError> {
34 let name = name.into();
35 let direction = match storage.load_study_metadata(&name)? {
36 Some(meta) => meta.direction,
37 None => {
38 storage.save_study_metadata(&StudyMetadata {
39 study_name: name.clone(),
40 direction,
41 })?;
42 direction
43 }
44 };
45 Ok(Study {
46 name,
47 direction,
48 sampler: Mutex::new(sampler),
49 pruner,
50 storage,
51 })
52 }
53
54 pub fn name(&self) -> &str {
56 &self.name
57 }
58
59 pub fn direction(&self) -> Direction {
61 self.direction
62 }
63
64 pub fn trials(&self) -> Result<Vec<Trial>, HyperoptError> {
66 Ok(self.storage.load_trials(&self.name)?)
67 }
68
69 pub fn best_trial(&self) -> Result<Option<Trial>, HyperoptError> {
71 let trials = self.storage.load_trials(&self.name)?;
72 let state = StudyState::new(self.direction, trials);
73 Ok(state.best_trial().cloned())
74 }
75
76 pub fn best_value(&self) -> Result<Option<f64>, HyperoptError> {
78 Ok(self.best_trial()?.and_then(|t| t.value))
79 }
80
81 pub fn optimize<F>(&self, mut objective: F, n_trials: usize) -> Result<(), HyperoptError>
85 where
86 F: FnMut(&mut TrialContext) -> ObjectiveResult,
87 {
88 for _ in 0..n_trials {
89 let existing = self.storage.load_trials(&self.name)?;
90 let number = existing.len();
91 let state = StudyState::new(self.direction, existing);
92 let mut ctx =
93 TrialContext::new(number, &self.sampler, &state, self.pruner.as_ref());
94
95 let result = catch_unwind(AssertUnwindSafe(|| objective(&mut ctx)));
96 let trial = finish_trial(ctx.into_trial(), result);
97 self.storage.save_trial(&self.name, &trial)?;
98 }
99 Ok(())
100 }
101
102 #[cfg(feature = "parallel")]
113 pub fn optimize_parallel<F>(
114 &self,
115 objective: F,
116 n_trials: usize,
117 n_workers: usize,
118 ) -> Result<(), HyperoptError>
119 where
120 F: Fn(&mut TrialContext) -> ObjectiveResult + Sync,
121 {
122 use rayon::prelude::*;
123 use std::sync::atomic::{AtomicUsize, Ordering};
124
125 let base = self.storage.load_trials(&self.name)?.len();
126 let counter = AtomicUsize::new(base);
127
128 let pool = rayon::ThreadPoolBuilder::new()
129 .num_threads(n_workers)
130 .build()
131 .map_err(|e| HyperoptError::Storage(crate::StorageError::Backend(e.to_string())))?;
132
133 pool.install(|| {
134 (0..n_trials).into_par_iter().try_for_each(|_| {
135 let number = counter.fetch_add(1, Ordering::SeqCst);
136 let existing = self.storage.load_trials(&self.name)?;
138 let state = StudyState::new(self.direction, existing);
139 let mut ctx =
140 TrialContext::new(number, &self.sampler, &state, self.pruner.as_ref());
141 let result = catch_unwind(AssertUnwindSafe(|| objective(&mut ctx)));
142 let trial = finish_trial(ctx.into_trial(), result);
143 self.storage.save_trial(&self.name, &trial)?;
144 Ok::<(), HyperoptError>(())
145 })
146 })
147 }
148}
149
150fn finish_trial(
153 mut trial: Trial,
154 result: std::thread::Result<ObjectiveResult>,
155) -> Trial {
156 match result {
157 Ok(Ok(value)) => {
158 trial.value = Some(value);
159 trial.state = TrialState::Complete;
160 }
161 Ok(Err(ObjectiveError::Pruned)) => {
162 trial.state = TrialState::Pruned;
163 }
164 Ok(Err(ObjectiveError::Failed(_))) => {
165 trial.state = TrialState::Failed;
166 }
167 Err(_panic) => {
168 trial.state = TrialState::Failed;
170 }
171 }
172 trial
173}