diff --git a/README.md b/README.md index 7949823..cde16e6 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ let study: Study = Study::with_sampler(Direction::Minimize, sampler); study .optimize_with_sampler(20, |trial| { let x = trial.suggest_float("x", -10.0, 10.0)?; - Ok::<_, optimizer::TpeError>(x * x) + Ok::<_, optimizer::Error>(x * x) }) .unwrap(); diff --git a/src/error.rs b/src/error.rs index d6cff2f..fe34b47 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,5 @@ -//! Error types for the optimizer library. - -use thiserror::Error; - -/// The error type for TPE operations. -#[derive(Debug, Error)] -pub enum TpeError { +#[derive(Debug, thiserror::Error)] +pub enum Error { /// Returned when the lower bound is greater than the upper bound. #[error("invalid bounds: low ({low}) must be less than or equal to high ({high})")] InvalidBounds { @@ -61,5 +56,4 @@ pub enum TpeError { TaskError(String), } -/// A specialized Result type for TPE operations. -pub type Result = core::result::Result; +pub type Result = core::result::Result; diff --git a/src/kde.rs b/src/kde.rs index 686d9e6..0235955 100644 --- a/src/kde.rs +++ b/src/kde.rs @@ -5,7 +5,7 @@ use rand::Rng; -use crate::error::{Result, TpeError}; +use crate::error::{Error, Result}; /// A Gaussian kernel density estimator for continuous distributions. /// @@ -45,10 +45,10 @@ impl KernelDensityEstimator { /// /// # Errors /// - /// Returns `TpeError::EmptySamples` if `samples` is empty. + /// Returns `Error::EmptySamples` if `samples` is empty. pub(crate) fn new(samples: Vec) -> Result { if samples.is_empty() { - return Err(TpeError::EmptySamples); + return Err(Error::EmptySamples); } let bandwidth = Self::scotts_rule(&samples); @@ -61,14 +61,14 @@ impl KernelDensityEstimator { /// /// # Errors /// - /// Returns `TpeError::EmptySamples` if `samples` is empty. - /// Returns `TpeError::InvalidBandwidth` if `bandwidth` is not positive. + /// Returns `Error::EmptySamples` if `samples` is empty. + /// Returns `Error::InvalidBandwidth` if `bandwidth` is not positive. pub(crate) fn with_bandwidth(samples: Vec, bandwidth: f64) -> Result { if samples.is_empty() { - return Err(TpeError::EmptySamples); + return Err(Error::EmptySamples); } if bandwidth <= 0.0 { - return Err(TpeError::InvalidBandwidth(bandwidth)); + return Err(Error::InvalidBandwidth(bandwidth)); } Ok(Self { samples, bandwidth }) @@ -261,20 +261,20 @@ mod tests { fn test_kde_empty_samples() { let samples: Vec = vec![]; let result = KernelDensityEstimator::new(samples); - assert!(matches!(result, Err(TpeError::EmptySamples))); + assert!(matches!(result, Err(Error::EmptySamples))); } #[test] fn test_kde_zero_bandwidth() { let samples = vec![1.0, 2.0, 3.0]; let result = KernelDensityEstimator::with_bandwidth(samples, 0.0); - assert!(matches!(result, Err(TpeError::InvalidBandwidth(_)))); + assert!(matches!(result, Err(Error::InvalidBandwidth(_)))); } #[test] fn test_kde_negative_bandwidth() { let samples = vec![1.0, 2.0, 3.0]; let result = KernelDensityEstimator::with_bandwidth(samples, -1.0); - assert!(matches!(result, Err(TpeError::InvalidBandwidth(_)))); + assert!(matches!(result, Err(Error::InvalidBandwidth(_)))); } } diff --git a/src/lib.rs b/src/lib.rs index 8b4c1eb..a0660df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ //! study //! .optimize_with_sampler(20, |trial| { //! let x = trial.suggest_float("x", -10.0, 10.0)?; -//! Ok::<_, optimizer::TpeError>(x * x) +//! Ok::<_, optimizer::Error>(x * x) //! }) //! .unwrap(); //! @@ -86,7 +86,7 @@ //! let optimizer = trial.suggest_categorical("optimizer", &["sgd", "adam", "rmsprop"])?; //! //! // Return objective value -//! Ok::<_, optimizer::TpeError>(x * n as f64) +//! Ok::<_, optimizer::Error>(x * n as f64) //! }) //! .unwrap(); //! ``` @@ -140,7 +140,7 @@ mod study; mod trial; mod types; -pub use error::{Result, TpeError}; +pub use error::{Error, Result}; pub use param::ParamValue; pub use study::Study; pub use trial::Trial; diff --git a/src/sampler/tpe.rs b/src/sampler/tpe.rs index 7ef57f1..b4f5a6e 100644 --- a/src/sampler/tpe.rs +++ b/src/sampler/tpe.rs @@ -9,7 +9,7 @@ use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; use crate::distribution::Distribution; -use crate::error::{Result, TpeError}; +use crate::error::{Error, Result}; use crate::kde::KernelDensityEstimator; use crate::param::ParamValue; use crate::sampler::{CompletedTrial, Sampler}; @@ -106,8 +106,8 @@ impl TpeSampler { /// /// # Errors /// - /// Returns `TpeError::InvalidGamma` if gamma is not in (0.0, 1.0). - /// Returns `TpeError::InvalidBandwidth` if `kde_bandwidth` is Some but not positive. + /// Returns `Error::InvalidGamma` if gamma is not in (0.0, 1.0). + /// Returns `Error::InvalidBandwidth` if `kde_bandwidth` is Some but not positive. pub fn with_config( gamma: f64, n_startup_trials: usize, @@ -116,12 +116,12 @@ impl TpeSampler { seed: Option, ) -> Result { if gamma <= 0.0 || gamma >= 1.0 { - return Err(TpeError::InvalidGamma(gamma)); + return Err(Error::InvalidGamma(gamma)); } if let Some(bw) = kde_bandwidth && bw <= 0.0 { - return Err(TpeError::InvalidBandwidth(bw)); + return Err(Error::InvalidBandwidth(bw)); } let rng = match seed { @@ -484,7 +484,7 @@ impl TpeSamplerBuilder { /// # Note /// /// Validation happens at `build()` time. If gamma is not in (0.0, 1.0), - /// `build()` will return `Err(TpeError::InvalidGamma)`. + /// `build()` will return `Err(Error::InvalidGamma)`. #[must_use] pub fn gamma(mut self, gamma: f64) -> Self { self.gamma = gamma; @@ -569,7 +569,7 @@ impl TpeSamplerBuilder { /// # Note /// /// Validation happens at `build()` time. If bandwidth is not positive, - /// `build()` will return `Err(TpeError::InvalidBandwidth)`. + /// `build()` will return `Err(Error::InvalidBandwidth)`. #[must_use] pub fn kde_bandwidth(mut self, bandwidth: f64) -> Self { self.kde_bandwidth = Some(bandwidth); @@ -602,8 +602,8 @@ impl TpeSamplerBuilder { /// /// # Errors /// - /// Returns `TpeError::InvalidGamma` if gamma is not in (0.0, 1.0). - /// Returns `TpeError::InvalidBandwidth` if `kde_bandwidth` is Some but not positive. + /// Returns `Error::InvalidGamma` if gamma is not in (0.0, 1.0). + /// Returns `Error::InvalidBandwidth` if `kde_bandwidth` is Some but not positive. /// /// # Examples /// @@ -817,13 +817,13 @@ mod tests { #[test] fn test_tpe_sampler_invalid_gamma_zero() { let result = TpeSampler::with_config(0.0, 10, 24, None, None); - assert!(matches!(result, Err(TpeError::InvalidGamma(_)))); + assert!(matches!(result, Err(Error::InvalidGamma(_)))); } #[test] fn test_tpe_sampler_invalid_gamma_one() { let result = TpeSampler::with_config(1.0, 10, 24, None, None); - assert!(matches!(result, Err(TpeError::InvalidGamma(_)))); + assert!(matches!(result, Err(Error::InvalidGamma(_)))); } #[test] @@ -1077,7 +1077,7 @@ mod tests { #[test] fn test_tpe_sampler_builder_invalid_gamma() { let result = TpeSamplerBuilder::new().gamma(1.5).build(); - assert!(matches!(result, Err(TpeError::InvalidGamma(_)))); + assert!(matches!(result, Err(Error::InvalidGamma(_)))); } #[test] diff --git a/src/study.rs b/src/study.rs index 4526066..e9fa0e0 100644 --- a/src/study.rs +++ b/src/study.rs @@ -275,7 +275,7 @@ where /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if no trials have been completed. + /// Returns `Error::NoCompletedTrials` if no trials have been completed. /// /// # Examples /// @@ -305,7 +305,7 @@ where let trials = self.completed_trials.read(); if trials.is_empty() { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } let best = trials @@ -325,7 +325,7 @@ where } } }) - .ok_or(crate::TpeError::NoCompletedTrials)?; + .ok_or(crate::Error::NoCompletedTrials)?; Ok(best.clone()) } @@ -338,7 +338,7 @@ where /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if no trials have been completed. + /// Returns `Error::NoCompletedTrials` if no trials have been completed. /// /// # Examples /// @@ -387,7 +387,7 @@ where /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if all trials failed (no successful trials). + /// Returns `Error::NoCompletedTrials` if all trials failed (no successful trials). /// /// # Examples /// @@ -402,7 +402,7 @@ where /// study /// .optimize(10, |trial| { /// let x = trial.suggest_float("x", -10.0, 10.0)?; - /// Ok::<_, optimizer::TpeError>(x * x) + /// Ok::<_, optimizer::Error>(x * x) /// }) /// .unwrap(); /// @@ -431,7 +431,7 @@ where // Return error if no trials succeeded if self.n_trials() == 0 { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } Ok(()) @@ -455,7 +455,7 @@ where /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if all trials failed (no successful trials). + /// Returns `Error::NoCompletedTrials` if all trials failed (no successful trials). /// /// # Examples /// @@ -474,7 +474,7 @@ where /// let x = trial.suggest_float("x", -10.0, 10.0)?; /// // Simulate async work (e.g., network request) /// let value = x * x; - /// Ok::<_, optimizer::TpeError>((trial, value)) + /// Ok::<_, optimizer::Error>((trial, value)) /// }) /// .await?; /// @@ -511,7 +511,7 @@ where // Return error if no trials succeeded if self.n_trials() == 0 { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } Ok(()) @@ -536,8 +536,8 @@ where /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if all trials failed (no successful trials). - /// Returns `TpeError::TaskError` if the semaphore is closed or a spawned task panics. + /// Returns `Error::NoCompletedTrials` if all trials failed (no successful trials). + /// Returns `Error::TaskError` if the semaphore is closed or a spawned task panics. /// /// # Examples /// @@ -556,7 +556,7 @@ where /// let x = trial.suggest_float("x", -10.0, 10.0)?; /// // Async objective function (e.g., network request) /// let value = x * x; - /// Ok::<_, optimizer::TpeError>((trial, value)) + /// Ok::<_, optimizer::Error>((trial, value)) /// }) /// .await?; /// @@ -590,7 +590,7 @@ where .clone() .acquire_owned() .await - .map_err(|e| crate::TpeError::TaskError(e.to_string()))?; + .map_err(|e| crate::Error::TaskError(e.to_string()))?; let trial = self.create_trial(); let objective = Arc::clone(&objective); @@ -607,7 +607,7 @@ where for handle in handles { match handle .await - .map_err(|e| crate::TpeError::TaskError(e.to_string()))? + .map_err(|e| crate::Error::TaskError(e.to_string()))? { Ok((trial, value)) => { self.complete_trial(trial, value); @@ -620,7 +620,7 @@ where // Return error if no trials succeeded if self.n_trials() == 0 { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } Ok(()) @@ -643,9 +643,9 @@ where /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if no trials completed successfully + /// Returns `Error::NoCompletedTrials` if no trials completed successfully /// before optimization stopped (either by completing all trials or early stopping). - /// Returns `TpeError::Internal` if a completed trial is not found after adding (internal invariant violation). + /// Returns `Error::Internal` if a completed trial is not found after adding (internal invariant violation). /// /// # Examples /// @@ -664,7 +664,7 @@ where /// 100, /// |trial| { /// let x = trial.suggest_float("x", -10.0, 10.0)?; - /// Ok::<_, optimizer::TpeError>(x * x) + /// Ok::<_, optimizer::Error>(x * x) /// }, /// |_study, completed_trial| { /// // Stop early if we find a value less than 1.0 @@ -702,7 +702,7 @@ where // Get the just-completed trial for the callback let trials = self.completed_trials.read(); let Some(completed) = trials.last() else { - return Err(crate::TpeError::Internal( + return Err(crate::Error::Internal( "completed trial not found after adding", )); }; @@ -725,7 +725,7 @@ where // Return error if no trials succeeded if self.n_trials() == 0 { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } Ok(()) @@ -783,7 +783,7 @@ impl Study { /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if all trials failed (no successful trials). + /// Returns `Error::NoCompletedTrials` if all trials failed (no successful trials). /// /// # Examples /// @@ -798,7 +798,7 @@ impl Study { /// study /// .optimize_with_sampler(10, |trial| { /// let x = trial.suggest_float("x", -10.0, 10.0)?; - /// Ok::<_, optimizer::TpeError>(x * x) + /// Ok::<_, optimizer::Error>(x * x) /// }) /// .unwrap(); /// @@ -829,7 +829,7 @@ impl Study { // Return error if no trials succeeded if self.n_trials() == 0 { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } Ok(()) @@ -851,8 +851,8 @@ impl Study { /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if no trials completed successfully. - /// Returns `TpeError::Internal` if a completed trial is not found after adding (internal invariant violation). + /// Returns `Error::NoCompletedTrials` if no trials completed successfully. + /// Returns `Error::Internal` if a completed trial is not found after adding (internal invariant violation). /// /// # Examples /// @@ -871,7 +871,7 @@ impl Study { /// 100, /// |trial| { /// let x = trial.suggest_float("x", -10.0, 10.0)?; - /// Ok::<_, optimizer::TpeError>(x * x) + /// Ok::<_, optimizer::Error>(x * x) /// }, /// |study, _completed_trial| { /// // Stop after finding 5 good trials @@ -907,7 +907,7 @@ impl Study { // Get the just-completed trial for the callback let trials = self.completed_trials.read(); let Some(completed) = trials.last() else { - return Err(crate::TpeError::Internal( + return Err(crate::Error::Internal( "completed trial not found after adding", )); }; @@ -930,7 +930,7 @@ impl Study { // Return error if no trials succeeded if self.n_trials() == 0 { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } Ok(()) @@ -953,7 +953,7 @@ impl Study { /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if all trials failed (no successful trials). + /// Returns `Error::NoCompletedTrials` if all trials failed (no successful trials). /// /// # Examples /// @@ -972,7 +972,7 @@ impl Study { /// let x = trial.suggest_float("x", -10.0, 10.0)?; /// // Simulate async work (e.g., network request) /// let value = x * x; - /// Ok::<_, optimizer::TpeError>((trial, value)) + /// Ok::<_, optimizer::Error>((trial, value)) /// }) /// .await?; /// @@ -1009,7 +1009,7 @@ impl Study { // Return error if no trials succeeded if self.n_trials() == 0 { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } Ok(()) @@ -1034,8 +1034,8 @@ impl Study { /// /// # Errors /// - /// Returns `TpeError::NoCompletedTrials` if all trials failed (no successful trials). - /// Returns `TpeError::TaskError` if the semaphore is closed or a spawned task panics. + /// Returns `Error::NoCompletedTrials` if all trials failed (no successful trials). + /// Returns `Error::TaskError` if the semaphore is closed or a spawned task panics. /// /// # Examples /// @@ -1054,7 +1054,7 @@ impl Study { /// let x = trial.suggest_float("x", -10.0, 10.0)?; /// // Async objective function (e.g., network request) /// let value = x * x; - /// Ok::<_, optimizer::TpeError>((trial, value)) + /// Ok::<_, optimizer::Error>((trial, value)) /// }) /// .await?; /// @@ -1087,7 +1087,7 @@ impl Study { .clone() .acquire_owned() .await - .map_err(|e| crate::TpeError::TaskError(e.to_string()))?; + .map_err(|e| crate::Error::TaskError(e.to_string()))?; let trial = self.create_trial_with_sampler(); let objective = Arc::clone(&objective); @@ -1104,7 +1104,7 @@ impl Study { for handle in handles { match handle .await - .map_err(|e| crate::TpeError::TaskError(e.to_string()))? + .map_err(|e| crate::Error::TaskError(e.to_string()))? { Ok((trial, value)) => { self.complete_trial(trial, value); @@ -1117,7 +1117,7 @@ impl Study { // Return error if no trials succeeded if self.n_trials() == 0 { - return Err(crate::TpeError::NoCompletedTrials); + return Err(crate::Error::NoCompletedTrials); } Ok(()) diff --git a/src/trial.rs b/src/trial.rs index b53b2d5..83c7a77 100644 --- a/src/trial.rs +++ b/src/trial.rs @@ -8,7 +8,7 @@ use parking_lot::RwLock; use crate::distribution::{ CategoricalDistribution, Distribution, FloatDistribution, IntDistribution, }; -use crate::error::{Result, TpeError}; +use crate::error::{Error, Result}; use crate::param::ParamValue; use crate::sampler::{CompletedTrial, Sampler}; use crate::types::TrialState; @@ -190,7 +190,7 @@ impl Trial { /// ``` pub fn suggest_float(&mut self, name: impl Into, low: f64, high: f64) -> Result { if low > high { - return Err(TpeError::InvalidBounds { low, high }); + return Err(Error::InvalidBounds { low, high }); } let name = name.into(); @@ -216,7 +216,7 @@ impl Trial { } } // Distribution exists but doesn't match - return Err(TpeError::ParameterConflict { + return Err(Error::ParameterConflict { name, reason: "parameter was previously sampled with different bounds or type" .to_string(), @@ -226,7 +226,7 @@ impl Trial { // Sample using the sampler let dist = Distribution::Float(distribution); let ParamValue::Float(value) = self.sample_value(&dist) else { - return Err(TpeError::Internal( + return Err(Error::Internal( "Float distribution should return Float value", )); }; @@ -283,11 +283,11 @@ impl Trial { high: f64, ) -> Result { if low <= 0.0 { - return Err(TpeError::InvalidLogBounds); + return Err(Error::InvalidLogBounds); } if low > high { - return Err(TpeError::InvalidBounds { low, high }); + return Err(Error::InvalidBounds { low, high }); } let name = name.into(); @@ -313,7 +313,7 @@ impl Trial { } } // Distribution exists but doesn't match - return Err(TpeError::ParameterConflict { + return Err(Error::ParameterConflict { name, reason: "parameter was previously sampled with different bounds or type" .to_string(), @@ -323,7 +323,7 @@ impl Trial { // Sample using the sampler (sampler handles log-scale transformation) let dist = Distribution::Float(distribution); let ParamValue::Float(value) = self.sample_value(&dist) else { - return Err(TpeError::Internal( + return Err(Error::Internal( "Float distribution should return Float value", )); }; @@ -380,11 +380,11 @@ impl Trial { step: f64, ) -> Result { if step <= 0.0 { - return Err(TpeError::InvalidStep); + return Err(Error::InvalidStep); } if low > high { - return Err(TpeError::InvalidBounds { low, high }); + return Err(Error::InvalidBounds { low, high }); } let name = name.into(); @@ -410,7 +410,7 @@ impl Trial { } } // Distribution exists but doesn't match - return Err(TpeError::ParameterConflict { + return Err(Error::ParameterConflict { name, reason: "parameter was previously sampled with different bounds or type" .to_string(), @@ -420,7 +420,7 @@ impl Trial { // Sample using the sampler (sampler handles step-grid) let dist = Distribution::Float(distribution); let ParamValue::Float(value) = self.sample_value(&dist) else { - return Err(TpeError::Internal( + return Err(Error::Internal( "Float distribution should return Float value", )); }; @@ -466,7 +466,7 @@ impl Trial { #[allow(clippy::cast_precision_loss)] pub fn suggest_int(&mut self, name: impl Into, low: i64, high: i64) -> Result { if low > high { - return Err(TpeError::InvalidBounds { + return Err(Error::InvalidBounds { low: low as f64, high: high as f64, }); @@ -495,7 +495,7 @@ impl Trial { } } // Distribution exists but doesn't match - return Err(TpeError::ParameterConflict { + return Err(Error::ParameterConflict { name, reason: "parameter was previously sampled with different bounds or type" .to_string(), @@ -505,9 +505,7 @@ impl Trial { // Sample using the sampler let dist = Distribution::Int(distribution); let ParamValue::Int(value) = self.sample_value(&dist) else { - return Err(TpeError::Internal( - "Int distribution should return Int value", - )); + return Err(Error::Internal("Int distribution should return Int value")); }; // Store distribution and value @@ -554,11 +552,11 @@ impl Trial { #[allow(clippy::cast_precision_loss)] pub fn suggest_int_log(&mut self, name: impl Into, low: i64, high: i64) -> Result { if low < 1 { - return Err(TpeError::InvalidLogBounds); + return Err(Error::InvalidLogBounds); } if low > high { - return Err(TpeError::InvalidBounds { + return Err(Error::InvalidBounds { low: low as f64, high: high as f64, }); @@ -587,7 +585,7 @@ impl Trial { } } // Distribution exists but doesn't match - return Err(TpeError::ParameterConflict { + return Err(Error::ParameterConflict { name, reason: "parameter was previously sampled with different bounds or type" .to_string(), @@ -597,9 +595,7 @@ impl Trial { // Sample using the sampler (sampler handles log-scale transformation) let dist = Distribution::Int(distribution); let ParamValue::Int(value) = self.sample_value(&dist) else { - return Err(TpeError::Internal( - "Int distribution should return Int value", - )); + return Err(Error::Internal("Int distribution should return Int value")); }; // Store distribution and value @@ -659,11 +655,11 @@ impl Trial { step: i64, ) -> Result { if step <= 0 { - return Err(TpeError::InvalidStep); + return Err(Error::InvalidStep); } if low > high { - return Err(TpeError::InvalidBounds { + return Err(Error::InvalidBounds { low: low as f64, high: high as f64, }); @@ -692,7 +688,7 @@ impl Trial { } } // Distribution exists but doesn't match - return Err(TpeError::ParameterConflict { + return Err(Error::ParameterConflict { name, reason: "parameter was previously sampled with different bounds or type" .to_string(), @@ -702,9 +698,7 @@ impl Trial { // Sample using the sampler (sampler handles step-grid) let dist = Distribution::Int(distribution); let ParamValue::Int(value) = self.sample_value(&dist) else { - return Err(TpeError::Internal( - "Int distribution should return Int value", - )); + return Err(Error::Internal("Int distribution should return Int value")); }; // Store distribution and value @@ -760,7 +754,7 @@ impl Trial { choices: &[T], ) -> Result { if choices.is_empty() { - return Err(TpeError::EmptyChoices); + return Err(Error::EmptyChoices); } let name = name.into(); @@ -779,7 +773,7 @@ impl Trial { } } // Distribution exists but doesn't match - return Err(TpeError::ParameterConflict { + return Err(Error::ParameterConflict { name, reason: "parameter was previously sampled with different number of choices or type" .to_string(), @@ -789,7 +783,7 @@ impl Trial { // Sample using the sampler let dist = Distribution::Categorical(distribution); let ParamValue::Categorical(index) = self.sample_value(&dist) else { - return Err(TpeError::Internal( + return Err(Error::Internal( "Categorical distribution should return Categorical value", )); }; diff --git a/tests/async_tests.rs b/tests/async_tests.rs index d046baf..dae3862 100644 --- a/tests/async_tests.rs +++ b/tests/async_tests.rs @@ -6,7 +6,7 @@ use optimizer::sampler::random::RandomSampler; use optimizer::sampler::tpe::TpeSampler; -use optimizer::{Direction, Study, TpeError}; +use optimizer::{Direction, Error, Study}; #[tokio::test] async fn test_optimize_async_basic() { @@ -16,7 +16,7 @@ async fn test_optimize_async_basic() { study .optimize_async(10, |mut trial| async move { let x = trial.suggest_float("x", -10.0, 10.0)?; - Ok::<_, TpeError>((trial, x * x)) + Ok::<_, Error>((trial, x * x)) }) .await .expect("async optimization should succeed"); @@ -39,7 +39,7 @@ async fn test_optimize_async_with_sampler() { study .optimize_async_with_sampler(15, |mut trial| async move { let x = trial.suggest_float("x", -5.0, 5.0)?; - Ok::<_, TpeError>((trial, x * x)) + Ok::<_, Error>((trial, x * x)) }) .await .expect("async optimization with sampler should succeed"); @@ -57,7 +57,7 @@ async fn test_optimize_parallel() { study .optimize_parallel(20, 4, |mut trial| async move { let x = trial.suggest_float("x", -10.0, 10.0)?; - Ok::<_, TpeError>((trial, x * x)) + Ok::<_, Error>((trial, x * x)) }) .await .expect("parallel optimization should succeed"); @@ -79,7 +79,7 @@ async fn test_optimize_parallel_with_sampler() { .optimize_parallel_with_sampler(15, 3, |mut trial| async move { let x = trial.suggest_float("x", -5.0, 5.0)?; let y = trial.suggest_float("y", -5.0, 5.0)?; - Ok::<_, TpeError>((trial, x * x + y * y)) + Ok::<_, Error>((trial, x * x + y * y)) }) .await .expect("parallel optimization with sampler should succeed"); @@ -99,7 +99,7 @@ async fn test_optimize_async_all_failures() { .await; assert!( - matches!(result, Err(TpeError::NoCompletedTrials)), + matches!(result, Err(Error::NoCompletedTrials)), "should return NoCompletedTrials when all trials fail" ); } @@ -116,7 +116,7 @@ async fn test_optimize_async_with_sampler_all_failures() { .await; assert!( - matches!(result, Err(TpeError::NoCompletedTrials)), + matches!(result, Err(Error::NoCompletedTrials)), "should return NoCompletedTrials when all trials fail" ); } @@ -133,7 +133,7 @@ async fn test_optimize_parallel_all_failures() { .await; assert!( - matches!(result, Err(TpeError::NoCompletedTrials)), + matches!(result, Err(Error::NoCompletedTrials)), "should return NoCompletedTrials when all trials fail" ); } @@ -150,7 +150,7 @@ async fn test_optimize_parallel_with_sampler_all_failures() { .await; assert!( - matches!(result, Err(TpeError::NoCompletedTrials)), + matches!(result, Err(Error::NoCompletedTrials)), "should return NoCompletedTrials when all trials fail" ); } @@ -168,9 +168,9 @@ async fn test_optimize_async_partial_failures() { async move { if count.is_multiple_of(2) { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>((trial, x)) + Ok::<_, Error>((trial, x)) } else { - Err(TpeError::NoCompletedTrials) // Use as error type + Err(Error::NoCompletedTrials) // Use as error type } } }) @@ -190,7 +190,7 @@ async fn test_optimize_parallel_high_concurrency() { study .optimize_parallel(5, 10, |mut trial| async move { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>((trial, x)) + Ok::<_, Error>((trial, x)) }) .await .expect("should handle high concurrency"); @@ -207,7 +207,7 @@ async fn test_optimize_parallel_single_concurrency() { study .optimize_parallel(10, 1, |mut trial| async move { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>((trial, x)) + Ok::<_, Error>((trial, x)) }) .await .expect("should work with single concurrency"); diff --git a/tests/integration.rs b/tests/integration.rs index c576166..dd37f42 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -8,7 +8,7 @@ use optimizer::sampler::random::RandomSampler; use optimizer::sampler::tpe::TpeSampler; -use optimizer::{Direction, Study, TpeError, Trial}; +use optimizer::{Direction, Error, Study, Trial}; // ============================================================================= // Test: optimize simple quadratic function with TPE, finds near-optimal @@ -30,7 +30,7 @@ fn test_tpe_optimizes_quadratic_function() { study .optimize_with_sampler(50, |trial| { let x = trial.suggest_float("x", -10.0, 10.0)?; - Ok::<_, TpeError>((x - 3.0).powi(2)) + Ok::<_, Error>((x - 3.0).powi(2)) }) .expect("optimization should succeed"); @@ -61,7 +61,7 @@ fn test_tpe_optimizes_multivariate_function() { .optimize_with_sampler(100, |trial| { let x = trial.suggest_float("x", -5.0, 5.0)?; let y = trial.suggest_float("y", -5.0, 5.0)?; - Ok::<_, TpeError>(x * x + y * y) + Ok::<_, Error>(x * x + y * y) }) .expect("optimization should succeed"); @@ -90,7 +90,7 @@ fn test_tpe_maximization() { study .optimize_with_sampler(50, |trial| { let x = trial.suggest_float("x", -10.0, 10.0)?; - Ok::<_, TpeError>(-(x - 2.0).powi(2) + 10.0) + Ok::<_, Error>(-(x - 2.0).powi(2) + 10.0) }) .expect("optimization should succeed"); @@ -123,7 +123,7 @@ fn test_random_sampler_uniform_float_distribution() { .optimize(n_samples, |trial| { let x = trial.suggest_float("x", 0.0, 1.0)?; samples.push(x); - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap(); @@ -160,7 +160,7 @@ fn test_random_sampler_uniform_int_distribution() { let n = trial.suggest_int("n", 1, 10)?; assert!((1..=10).contains(&n), "sample {n} out of range [1, 10]"); counts[(n - 1) as usize] += 1; - Ok::<_, TpeError>(n as f64) + Ok::<_, Error>(n as f64) }) .unwrap(); @@ -192,7 +192,7 @@ fn test_random_sampler_uniform_categorical_distribution() { let choice = trial.suggest_categorical("cat", &choices)?; let idx = choices.iter().position(|&c| c == choice).unwrap(); counts[idx] += 1; - Ok::<_, TpeError>(idx as f64) + Ok::<_, Error>(idx as f64) }) .unwrap(); @@ -227,7 +227,7 @@ fn test_random_sampler_reproducibility() { .optimize_with_sampler(100, |trial| { let x = trial.suggest_float("x", 0.0, 100.0)?; values1.push(x); - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap(); @@ -235,7 +235,7 @@ fn test_random_sampler_reproducibility() { .optimize_with_sampler(100, |trial| { let x = trial.suggest_float("x", 0.0, 100.0)?; values2.push(x); - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap(); @@ -367,7 +367,7 @@ fn test_parameter_conflict_float_different_bounds() { trial.suggest_float("x", 0.0, 1.0).unwrap(); let result = trial.suggest_float("x", 0.0, 2.0); // Different upper bound - assert!(matches!(result, Err(TpeError::ParameterConflict { .. }))); + assert!(matches!(result, Err(Error::ParameterConflict { .. }))); } #[test] @@ -377,7 +377,7 @@ fn test_parameter_conflict_float_vs_log() { trial.suggest_float("x", 0.1, 1.0).unwrap(); let result = trial.suggest_float_log("x", 0.1, 1.0); // Same bounds but log scale - assert!(matches!(result, Err(TpeError::ParameterConflict { .. }))); + assert!(matches!(result, Err(Error::ParameterConflict { .. }))); } #[test] @@ -387,7 +387,7 @@ fn test_parameter_conflict_float_vs_step() { trial.suggest_float("x", 0.0, 1.0).unwrap(); let result = trial.suggest_float_step("x", 0.0, 1.0, 0.1); // Same bounds but with step - assert!(matches!(result, Err(TpeError::ParameterConflict { .. }))); + assert!(matches!(result, Err(Error::ParameterConflict { .. }))); } #[test] @@ -397,7 +397,7 @@ fn test_parameter_conflict_int_different_bounds() { trial.suggest_int("n", 1, 10).unwrap(); let result = trial.suggest_int("n", 1, 20); // Different upper bound - assert!(matches!(result, Err(TpeError::ParameterConflict { .. }))); + assert!(matches!(result, Err(Error::ParameterConflict { .. }))); } #[test] @@ -407,7 +407,7 @@ fn test_parameter_conflict_int_vs_log() { trial.suggest_int("n", 1, 100).unwrap(); let result = trial.suggest_int_log("n", 1, 100); // Same bounds but log scale - assert!(matches!(result, Err(TpeError::ParameterConflict { .. }))); + assert!(matches!(result, Err(Error::ParameterConflict { .. }))); } #[test] @@ -417,7 +417,7 @@ fn test_parameter_conflict_categorical_different_n_choices() { trial.suggest_categorical("opt", &["a", "b", "c"]).unwrap(); let result = trial.suggest_categorical("opt", &["x", "y"]); // Different number of choices - assert!(matches!(result, Err(TpeError::ParameterConflict { .. }))); + assert!(matches!(result, Err(Error::ParameterConflict { .. }))); } #[test] @@ -427,7 +427,7 @@ fn test_parameter_conflict_float_vs_int() { trial.suggest_float("x", 0.0, 10.0).unwrap(); let result = trial.suggest_int("x", 0, 10); // Different type - assert!(matches!(result, Err(TpeError::ParameterConflict { .. }))); + assert!(matches!(result, Err(Error::ParameterConflict { .. }))); } #[test] @@ -438,7 +438,7 @@ fn test_parameter_conflict_returns_name() { let result = trial.suggest_float("my_param", 0.0, 2.0); match result { - Err(TpeError::ParameterConflict { name, .. }) => { + Err(Error::ParameterConflict { name, .. }) => { assert_eq!(name, "my_param"); } _ => panic!("expected ParameterConflict error"), @@ -456,7 +456,7 @@ fn test_empty_categorical_returns_error() { let result = trial.suggest_categorical("opt", empty); - assert!(matches!(result, Err(TpeError::EmptyChoices))); + assert!(matches!(result, Err(Error::EmptyChoices))); } #[test] @@ -466,7 +466,7 @@ fn test_empty_categorical_vec_returns_error() { let result = trial.suggest_categorical("numbers", &empty); - assert!(matches!(result, Err(TpeError::EmptyChoices))); + assert!(matches!(result, Err(Error::EmptyChoices))); } // ============================================================================= @@ -480,7 +480,7 @@ fn test_study_basic_workflow() { study .optimize(10, |trial| { let x = trial.suggest_float("x", -5.0, 5.0)?; - Ok::<_, TpeError>(x * x) + Ok::<_, Error>(x * x) }) .expect("optimization should succeed"); @@ -517,7 +517,7 @@ fn test_no_completed_trials_error() { let study: Study = Study::new(Direction::Minimize); let result = study.best_trial(); - assert!(matches!(result, Err(TpeError::NoCompletedTrials))); + assert!(matches!(result, Err(Error::NoCompletedTrials))); } #[test] @@ -526,11 +526,11 @@ fn test_invalid_bounds_errors() { // low > high for float let result = trial.suggest_float("x", 10.0, 5.0); - assert!(matches!(result, Err(TpeError::InvalidBounds { .. }))); + assert!(matches!(result, Err(Error::InvalidBounds { .. }))); // low > high for int let result = trial.suggest_int("n", 100, 50); - assert!(matches!(result, Err(TpeError::InvalidBounds { .. }))); + assert!(matches!(result, Err(Error::InvalidBounds { .. }))); } #[test] @@ -539,14 +539,14 @@ fn test_invalid_log_bounds_errors() { // low <= 0 for log float let result = trial.suggest_float_log("x", 0.0, 1.0); - assert!(matches!(result, Err(TpeError::InvalidLogBounds))); + assert!(matches!(result, Err(Error::InvalidLogBounds))); let result = trial.suggest_float_log("y", -1.0, 1.0); - assert!(matches!(result, Err(TpeError::InvalidLogBounds))); + assert!(matches!(result, Err(Error::InvalidLogBounds))); // low < 1 for log int let result = trial.suggest_int_log("n", 0, 100); - assert!(matches!(result, Err(TpeError::InvalidLogBounds))); + assert!(matches!(result, Err(Error::InvalidLogBounds))); } #[test] @@ -555,14 +555,14 @@ fn test_invalid_step_errors() { // step <= 0 for float let result = trial.suggest_float_step("x", 0.0, 1.0, 0.0); - assert!(matches!(result, Err(TpeError::InvalidStep))); + assert!(matches!(result, Err(Error::InvalidStep))); let result = trial.suggest_float_step("y", 0.0, 1.0, -0.1); - assert!(matches!(result, Err(TpeError::InvalidStep))); + assert!(matches!(result, Err(Error::InvalidStep))); // step <= 0 for int let result = trial.suggest_int_step("n", 0, 100, 0); - assert!(matches!(result, Err(TpeError::InvalidStep))); + assert!(matches!(result, Err(Error::InvalidStep))); } #[test] @@ -588,7 +588,7 @@ fn test_tpe_with_categorical_parameter() { "cubic" => -((x - 1.0).powi(2)) + 10.0, // peak at x=1, max value 10 _ => unreachable!(), }; - Ok::<_, TpeError>(value) + Ok::<_, Error>(value) }) .expect("optimization should succeed"); @@ -615,7 +615,7 @@ fn test_tpe_with_integer_parameters() { study .optimize_with_sampler(30, |trial| { let n = trial.suggest_int("n", 1, 10)?; - Ok::<_, TpeError>(((n - 7) as f64).powi(2)) + Ok::<_, Error>(((n - 7) as f64).powi(2)) }) .expect("optimization should succeed"); @@ -643,7 +643,7 @@ fn test_callback_early_stopping() { |trial| { trials_run.set(trials_run.get() + 1); let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }, |_study, _trial| { // Stop after 5 trials @@ -666,7 +666,7 @@ fn test_study_trials_iteration() { study .optimize(5, |trial| { let x = trial.suggest_float("x", 0.0, 1.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap(); @@ -758,7 +758,7 @@ fn test_best_value() { study .optimize(10, |trial| { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap(); @@ -792,7 +792,7 @@ fn test_study_set_sampler() { study .optimize_with_sampler(10, |trial| { let x = trial.suggest_float("x", -5.0, 5.0)?; - Ok::<_, TpeError>(x * x) + Ok::<_, Error>(x * x) }) .expect("optimization should succeed with new sampler"); @@ -807,7 +807,7 @@ fn test_study_with_i32_value_type() { study .optimize(10, |trial| { let x = trial.suggest_int("x", -10, 10)?; - Ok::<_, TpeError>(x.abs() as i32) + Ok::<_, Error>(x.abs() as i32) }) .expect("optimization should succeed"); @@ -824,7 +824,7 @@ fn test_optimize_all_trials_fail() { let result = study.optimize(5, |_trial| Err::("always fails")); assert!( - matches!(result, Err(TpeError::NoCompletedTrials)), + matches!(result, Err(Error::NoCompletedTrials)), "should return NoCompletedTrials when all trials fail" ); } @@ -842,7 +842,7 @@ fn test_optimize_with_callback_all_trials_fail() { ); assert!( - matches!(result, Err(TpeError::NoCompletedTrials)), + matches!(result, Err(Error::NoCompletedTrials)), "should return NoCompletedTrials when all trials fail" ); } @@ -854,7 +854,7 @@ fn test_optimize_with_sampler_all_trials_fail() { let result = study.optimize_with_sampler(5, |_trial| Err::("always fails")); assert!( - matches!(result, Err(TpeError::NoCompletedTrials)), + matches!(result, Err(Error::NoCompletedTrials)), "should return NoCompletedTrials when all trials fail" ); } @@ -872,7 +872,7 @@ fn test_optimize_with_callback_sampler_all_trials_fail() { ); assert!( - matches!(result, Err(TpeError::NoCompletedTrials)), + matches!(result, Err(Error::NoCompletedTrials)), "should return NoCompletedTrials when all trials fail" ); } @@ -902,7 +902,7 @@ fn test_tpe_sampler_builder_default_trait() { study .optimize_with_sampler(5, |trial| { let x = trial.suggest_float("x", 0.0, 1.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap(); @@ -917,7 +917,7 @@ fn test_tpe_sampler_default_trait() { study .optimize_with_sampler(5, |trial| { let x = trial.suggest_float("x", 0.0, 1.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap(); @@ -938,7 +938,7 @@ fn test_tpe_with_fixed_kde_bandwidth() { study .optimize_with_sampler(20, |trial| { let x = trial.suggest_float("x", -5.0, 5.0)?; - Ok::<_, TpeError>(x * x) + Ok::<_, Error>(x * x) }) .expect("optimization should succeed"); @@ -949,7 +949,7 @@ fn test_tpe_with_fixed_kde_bandwidth() { #[test] fn test_tpe_sampler_invalid_kde_bandwidth() { let result = TpeSampler::with_config(0.25, 10, 24, Some(-1.0), None); - assert!(matches!(result, Err(TpeError::InvalidBandwidth(_)))); + assert!(matches!(result, Err(Error::InvalidBandwidth(_)))); } #[test] @@ -966,7 +966,7 @@ fn test_tpe_split_trials_with_two_trials() { study .optimize_with_sampler(5, |trial| { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .expect("optimization should succeed with small history"); @@ -987,7 +987,7 @@ fn test_tpe_with_log_scale_int() { .optimize_with_sampler(20, |trial| { let batch_size = trial.suggest_int_log("batch_size", 1, 1024)?; // Optimal around batch_size = 32 - Ok::<_, TpeError>(((batch_size as f64).log2() - 5.0).powi(2)) + Ok::<_, Error>(((batch_size as f64).log2() - 5.0).powi(2)) }) .expect("optimization should succeed"); @@ -1009,7 +1009,7 @@ fn test_tpe_with_step_distributions() { .optimize_with_sampler(20, |trial| { let x = trial.suggest_float_step("x", 0.0, 10.0, 0.5)?; let n = trial.suggest_int_step("n", 0, 100, 10)?; - Ok::<_, TpeError>((x - 5.0).powi(2) + ((n - 50) as f64).powi(2)) + Ok::<_, Error>((x - 5.0).powi(2) + ((n - 50) as f64).powi(2)) }) .expect("optimization should succeed"); @@ -1088,7 +1088,7 @@ fn test_tpe_empty_good_or_bad_values_fallback() { study .optimize_with_sampler(10, |trial| { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap(); @@ -1096,7 +1096,7 @@ fn test_tpe_empty_good_or_bad_values_fallback() { study .optimize_with_sampler(5, |trial| { let y = trial.suggest_float("y", 0.0, 10.0)?; - Ok::<_, TpeError>(y) + Ok::<_, Error>(y) }) .unwrap(); @@ -1114,7 +1114,7 @@ fn test_callback_early_stopping_on_first_trial() { 100, |trial| { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }, |_study, _trial| { // Stop immediately after first trial @@ -1138,7 +1138,7 @@ fn test_callback_sampler_early_stopping() { 100, |trial| { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }, |study, _trial| { if study.n_trials() >= 3 { @@ -1174,7 +1174,7 @@ fn test_best_trial_with_nan_values() { study .optimize(5, |trial| { let x = trial.suggest_float("x", 0.0, 10.0)?; - Ok::<_, TpeError>(x) + Ok::<_, Error>(x) }) .unwrap();