1use crate::error::{OptimError, Result};
8use crate::privacy::moment_accountant::MomentsAccountant;
9use crate::privacy::{DifferentialPrivacyConfig, PrivacyBudget};
10use scirs2_core::ndarray::{Array1, Array2};
11use scirs2_core::numeric::Float;
12use scirs2_core::random::rngs::StdRng;
13use scirs2_core::random::Rng;
14use std::collections::HashMap;
15use std::fmt::Debug;
16
17type ObjectiveFn<T> = Box<dyn Fn(&ParameterConfiguration<T>) -> Result<f64> + Send + Sync>;
19type RuleFn<T> = Box<dyn Fn(&HPOResult<T>) -> bool + Send + Sync>;
20type TestFn<T> = Box<dyn Fn(&[HPOResult<T>]) -> StatisticalTestResult + Send + Sync>;
21
22pub struct PrivateHyperparameterOptimizer<T: Float + Debug + Send + Sync + 'static> {
24 config: PrivateHPOConfig<T>,
26
27 budget_manager: HPOBudgetManager,
29
30 noisy_optimizers: HashMap<String, Box<dyn NoisyOptimizer<T>>>,
32
33 parameterspace: ParameterSpace<T>,
35
36 private_objective: PrivateObjective<T>,
38
39 search_strategy: SearchStrategy<T>,
41
42 results_aggregator: PrivateResultsAggregator<T>,
44
45 privacy_accountant: MomentsAccountant,
47}
48
49#[derive(Debug, Clone)]
51pub struct PrivateHPOConfig<T: Float + Debug + Send + Sync + 'static> {
52 pub base_privacyconfig: DifferentialPrivacyConfig,
54
55 pub budget_allocation: BudgetAllocationStrategy,
57
58 pub search_algorithm: SearchAlgorithm,
60
61 pub num_evaluations: usize,
63
64 pub cv_folds: usize,
66
67 pub early_stopping: EarlyStoppingConfig,
69
70 pub noise_mechanism: HyperparameterNoiseMechanism,
72
73 pub sensitivity_bounds: SensitivityBounds<T>,
75
76 pub private_model_selection: bool,
78
79 pub validation_strategy: ValidationStrategy,
81}
82
83#[derive(Debug, Clone, Copy)]
85pub enum BudgetAllocationStrategy {
86 Equal,
88
89 Adaptive,
91
92 Bandit,
94
95 Hierarchical,
97
98 Uncertainty,
100}
101
102#[derive(Debug, Clone, Copy)]
104pub enum SearchAlgorithm {
105 RandomSearch,
107
108 GridSearch,
110
111 BayesianOptimization,
113
114 GeneticAlgorithm,
116
117 ParticleSwarm,
119
120 SimulatedAnnealing,
122
123 TPE,
125}
126
127#[derive(Debug, Clone)]
129pub struct EarlyStoppingConfig {
130 pub enabled: bool,
132
133 pub patience: usize,
135
136 pub min_improvement: f64,
138
139 pub max_evaluations: usize,
141}
142
143#[derive(Debug, Clone, Copy)]
145pub enum HyperparameterNoiseMechanism {
146 Exponential,
148
149 Gaussian,
151
152 Laplace,
154
155 NoisyMax,
157
158 SparseVector,
160}
161
162#[derive(Debug, Clone)]
164pub struct SensitivityBounds<T: Float + Debug + Send + Sync + 'static> {
165 pub global_sensitivity: HashMap<String, T>,
167
168 pub local_sensitivity: HashMap<String, (T, T)>,
170
171 pub smooth_sensitivity: HashMap<String, SmoothSensitivityParams<T>>,
173}
174
175#[derive(Debug, Clone)]
177pub struct SmoothSensitivityParams<T: Float + Debug + Send + Sync + 'static> {
178 pub beta: T,
180
181 pub max_local_sensitivity: T,
183
184 pub smoothness: T,
186}
187
188#[derive(Debug, Clone, Copy)]
190pub enum ValidationStrategy {
191 HoldOut,
193
194 KFoldCV,
196
197 LeaveOneOut,
199
200 Bootstrap,
202
203 TimeSeriesSplit,
205}
206
207pub struct HPOBudgetManager {
209 total_budget: PrivacyBudget,
211
212 evaluation_budgets: Vec<PrivacyBudget>,
214
215 allocation_strategy: BudgetAllocationStrategy,
217
218 consumed_budget: PrivacyBudget,
220
221 adaptive_controller: AdaptiveBudgetController,
223}
224
225pub struct AdaptiveBudgetController {
227 performance_history: Vec<f64>,
229
230 budget_efficiency: Vec<f64>,
232
233 allocation_weights: Vec<f64>,
235
236 adaptation_rate: f64,
238}
239
240pub trait NoisyOptimizer<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
242 fn suggest_next(
244 &mut self,
245 parameterspace: &ParameterSpace<T>,
246 evaluation_history: &[HPOEvaluation<T>],
247 _privacy_budget: &PrivacyBudget,
248 ) -> Result<ParameterConfiguration<T>>;
249
250 fn update(
252 &mut self,
253 config: &ParameterConfiguration<T>,
254 result: &HPOResult<T>,
255 _privacy_budget: &PrivacyBudget,
256 ) -> Result<()>;
257
258 fn name(&self) -> &str;
260}
261
262#[derive(Debug, Clone)]
264pub struct ParameterSpace<T: Float + Debug + Send + Sync + 'static> {
265 pub parameters: HashMap<String, ParameterDefinition<T>>,
267
268 pub constraints: Vec<ParameterConstraint<T>>,
270
271 pub defaultconfig: Option<ParameterConfiguration<T>>,
273}
274
275#[derive(Debug, Clone)]
277pub struct ParameterDefinition<T: Float + Debug + Send + Sync + 'static> {
278 pub name: String,
280
281 pub param_type: ParameterType<T>,
283
284 pub bounds: ParameterBounds<T>,
286
287 pub prior: Option<ParameterPrior<T>>,
289
290 pub transformation: Option<ParameterTransformation>,
292}
293
294#[derive(Debug, Clone)]
296pub enum ParameterType<T: Float + Debug + Send + Sync + 'static> {
297 Continuous,
299
300 Integer,
302
303 Categorical(Vec<String>),
305
306 Boolean,
308
309 Ordinal(Vec<T>),
311}
312
313#[derive(Debug, Clone)]
315pub struct ParameterBounds<T: Float + Debug + Send + Sync + 'static> {
316 pub min: Option<T>,
318
319 pub max: Option<T>,
321
322 pub step: Option<T>,
324
325 pub valid_values: Option<Vec<String>>,
327}
328
329#[derive(Debug, Clone)]
331pub enum ParameterPrior<T: Float + Debug + Send + Sync + 'static> {
332 Uniform(T, T),
334
335 Normal(T, T),
337
338 LogNormal(T, T),
340
341 Beta(T, T),
343
344 Gamma(T, T),
346}
347
348#[derive(Debug, Clone, Copy)]
350pub enum ParameterTransformation {
351 Identity,
353
354 Log,
356
357 Exp,
359
360 Sqrt,
362
363 Square,
365}
366
367#[derive(Debug, Clone)]
369pub struct ParameterConstraint<T: Float + Debug + Send + Sync + 'static> {
370 pub name: String,
372
373 pub constraint_type: ConstraintType<T>,
375
376 pub penalty: T,
378}
379
380#[derive(Debug, Clone)]
382pub enum ConstraintType<T: Float + Debug + Send + Sync + 'static> {
383 Linear(Vec<T>, T),
385
386 Quadratic(Array2<T>, Array1<T>, T),
388
389 Custom(String),
391}
392
393#[derive(Debug, Clone)]
395pub struct ParameterConfiguration<T: Float + Debug + Send + Sync + 'static> {
396 pub values: HashMap<String, ParameterValue<T>>,
398
399 pub id: String,
401
402 pub metadata: HashMap<String, String>,
404}
405
406#[derive(Debug, Clone)]
408pub enum ParameterValue<T: Float + Debug + Send + Sync + 'static> {
409 Continuous(T),
411
412 Integer(i64),
414
415 Categorical(String),
417
418 Boolean(bool),
420
421 Ordinal(usize),
423}
424
425pub struct PrivateObjective<T: Float + Debug + Send + Sync + 'static> {
427 objective_fn: ObjectiveFn<T>,
429
430 noise_mechanism: ObjectiveNoiseMechanism<T>,
432
433 sensitivity_analyzer: ObjectiveSensitivityAnalyzer<T>,
435
436 cv_evaluator: PrivateCrossValidation<T>,
438}
439
440pub struct ObjectiveNoiseMechanism<T: Float + Debug + Send + Sync + 'static> {
442 mechanism_type: HyperparameterNoiseMechanism,
444
445 noise_params: NoiseParameters<T>,
447
448 rng: scirs2_core::random::Random,
450}
451
452#[derive(Debug, Clone)]
454pub struct NoiseParameters<T: Float + Debug + Send + Sync + 'static> {
455 pub scale: T,
457
458 pub sensitivity: T,
460
461 pub epsilon: f64,
463
464 pub delta: Option<f64>,
466}
467
468pub struct ObjectiveSensitivityAnalyzer<T: Float + Debug + Send + Sync + 'static> {
470 estimation_method: SensitivityEstimationMethod,
472
473 sensitivity_cache: HashMap<String, T>,
475
476 sample_estimator: SampleBasedSensitivityEstimator<T>,
478}
479
480#[derive(Debug, Clone, Copy)]
482pub enum SensitivityEstimationMethod {
483 Global,
485
486 Local,
488
489 Smooth,
491
492 SampleBased,
494
495 Theoretical,
497}
498
499pub struct SampleBasedSensitivityEstimator<T: Float + Debug + Send + Sync + 'static> {
501 num_samples: usize,
503
504 sampling_strategy: SamplingStrategy,
506
507 confidence_level: f64,
509
510 bootstrap_estimator: BootstrapEstimator<T>,
512
513 _phantom: std::marker::PhantomData<T>,
515}
516
517#[derive(Debug, Clone, Copy)]
519pub enum SamplingStrategy {
520 Uniform,
522
523 LatinHypercube,
525
526 Sobol,
528
529 Halton,
531
532 ImportanceSampling,
534}
535
536pub struct BootstrapEstimator<T: Float + Debug + Send + Sync + 'static> {
538 num_bootstrap: usize,
540
541 confidence_interval: (f64, f64),
543
544 bias_correction: bool,
546
547 _phantom: std::marker::PhantomData<T>,
549}
550
551pub struct PrivateCrossValidation<T: Float + Debug + Send + Sync + 'static> {
553 num_folds: usize,
555
556 fold_budgets: Vec<PrivacyBudget>,
558
559 fold_strategy: FoldStrategy,
561
562 private_aggregation: PrivateFoldAggregation<T>,
564}
565
566#[derive(Debug, Clone, Copy)]
568pub enum FoldStrategy {
569 Random,
571
572 Stratified,
574
575 TimeBased,
577
578 GroupBased,
580}
581
582pub struct PrivateFoldAggregation<T: Float + Debug + Send + Sync + 'static> {
584 aggregation_method: AggregationMethod,
586
587 noise_params: NoiseParameters<T>,
589
590 confidence_estimation: ConfidenceEstimation<T>,
592}
593
594#[derive(Debug, Clone, Copy)]
596pub enum AggregationMethod {
597 NoisyMean,
599
600 Median,
602
603 TrimmedMean,
605
606 WeightedAverage,
608
609 Robust,
611}
612
613pub struct ConfidenceEstimation<T: Float + Debug + Send + Sync + 'static> {
615 confidence_level: f64,
617
618 estimation_method: ConfidenceEstimationMethod,
620
621 bootstrap_params: Option<BootstrapParams>,
623
624 _phantom: std::marker::PhantomData<T>,
626}
627
628#[derive(Debug, Clone, Copy)]
630pub enum ConfidenceEstimationMethod {
631 Normal,
633
634 Bootstrap,
636
637 Jackknife,
639
640 Bayesian,
642}
643
644#[derive(Debug, Clone)]
646pub struct BootstrapParams {
647 pub num_samples: usize,
649
650 pub bootstrap_type: BootstrapType,
652
653 pub bias_correction: bool,
655}
656
657#[derive(Debug, Clone, Copy)]
659pub enum BootstrapType {
660 Standard,
662
663 BCa,
665
666 Parametric,
668
669 Block,
671}
672
673pub struct SearchStrategy<T: Float + Debug + Send + Sync + 'static> {
675 algorithm: SearchAlgorithm,
677
678 algorithm_params: HashMap<String, f64>,
680
681 exploration_factor: f64,
683
684 convergence_criteria: ConvergenceCriteria<T>,
686}
687
688#[derive(Debug, Clone)]
690pub struct ConvergenceCriteria<T: Float + Debug + Send + Sync + 'static> {
691 pub max_iterations: usize,
693
694 pub tolerance: T,
696
697 pub patience: usize,
699
700 pub min_change: T,
702}
703
704pub struct PrivateResultsAggregator<T: Float + Debug + Send + Sync + 'static> {
706 aggregation_strategy: ResultAggregationStrategy,
708
709 selection_budget: PrivacyBudget,
711
712 selection_mechanism: SelectionMechanism<T>,
714
715 result_validator: ResultValidator<T>,
717}
718
719#[derive(Debug, Clone, Copy)]
721pub enum ResultAggregationStrategy {
722 SelectBest,
724
725 Ensemble,
727
728 WeightedCombination,
730
731 Consensus,
733
734 MultiObjective,
736}
737
738pub struct SelectionMechanism<T: Float + Debug + Send + Sync + 'static> {
740 mechanism_type: HyperparameterNoiseMechanism,
742
743 selection_params: SelectionParameters<T>,
745
746 utility_function: UtilityFunction<T>,
748}
749
750#[derive(Debug, Clone)]
752pub struct SelectionParameters<T: Float + Debug + Send + Sync + 'static> {
753 pub temperature: T,
755
756 pub utility_sensitivity: T,
758
759 pub epsilon: f64,
761
762 pub threshold: Option<T>,
764}
765
766pub struct UtilityFunction<T: Float + Debug + Send + Sync + 'static> {
768 function_type: UtilityFunctionType,
770
771 parameters: Vec<T>,
773
774 multi_objective_weights: Option<Vec<T>>,
776}
777
778#[derive(Debug, Clone, Copy)]
780pub enum UtilityFunctionType {
781 Linear,
783
784 Exponential,
786
787 Logarithmic,
789
790 Quadratic,
792
793 Custom,
795}
796
797pub struct ResultValidator<T: Float + Debug + Send + Sync + 'static> {
799 validation_rules: Vec<ValidationRule<T>>,
801
802 statistical_tests: Vec<StatisticalTest<T>>,
804
805 anomaly_detector: AnomalyDetector<T>,
807}
808
809pub struct ValidationRule<T: Float + Debug + Send + Sync + 'static> {
811 pub name: String,
813
814 pub rule_fn: RuleFn<T>,
816
817 pub weight: f64,
819}
820
821pub struct StatisticalTest<T: Float + Debug + Send + Sync + 'static> {
823 pub name: String,
825
826 pub test_fn: TestFn<T>,
828
829 pub alpha: f64,
831}
832
833#[derive(Debug, Clone)]
835pub struct StatisticalTestResult {
836 pub statistic: f64,
838
839 pub p_value: f64,
841
842 pub conclusion: TestConclusion,
844
845 pub confidence_interval: Option<(f64, f64)>,
847}
848
849#[derive(Debug, Clone, Copy)]
851pub enum TestConclusion {
852 Reject,
854
855 FailToReject,
857
858 InsufficientEvidence,
860}
861
862pub struct AnomalyDetector<T: Float + Debug + Send + Sync + 'static> {
864 threshold: T,
866
867 detection_method: AnomalyDetectionMethod,
869
870 baseline: Option<T>,
872}
873
874#[derive(Debug, Clone, Copy)]
876pub enum AnomalyDetectionMethod {
877 ZScore,
879
880 IQR,
882
883 IsolationForest,
885
886 LocalOutlierFactor,
888}
889
890#[derive(Debug, Clone)]
892pub struct HPOEvaluation<T: Float + Debug + Send + Sync + 'static> {
893 pub id: String,
895
896 pub configuration: ParameterConfiguration<T>,
898
899 pub result: HPOResult<T>,
901
902 pub privacy_cost: PrivacyBudget,
904
905 pub timestamp: u64,
907
908 pub metadata: HashMap<String, String>,
910}
911
912#[derive(Debug, Clone)]
914pub struct HPOResult<T: Float + Debug + Send + Sync + 'static> {
915 pub objective_value: T,
917
918 pub standard_error: Option<T>,
920
921 pub cv_scores: Option<Vec<T>>,
923
924 pub training_time: Option<f64>,
926
927 pub complexity_metrics: HashMap<String, T>,
929
930 pub additional_metrics: HashMap<String, T>,
932
933 pub status: EvaluationStatus,
935}
936
937#[derive(Debug, Clone, Copy)]
939pub enum EvaluationStatus {
940 Success,
942
943 Failed,
945
946 Timeout,
948
949 Cancelled,
951
952 InProgress,
954}
955
956impl<T: Float + Debug + Send + Sync + 'static> PrivateHyperparameterOptimizer<T> {
957 pub fn new(config: PrivateHPOConfig<T>, parameterspace: ParameterSpace<T>) -> Result<Self> {
959 let budget_manager = HPOBudgetManager::new(
960 config.base_privacyconfig.clone(),
961 config.budget_allocation,
962 config.num_evaluations,
963 )?;
964
965 let privacy_accountant = MomentsAccountant::new(
966 config.base_privacyconfig.noise_multiplier,
967 config.base_privacyconfig.target_delta,
968 config.base_privacyconfig.batch_size,
969 config.base_privacyconfig.dataset_size,
970 );
971
972 let mut noisy_optimizers: HashMap<String, Box<dyn NoisyOptimizer<T>>> = HashMap::new();
973
974 match config.search_algorithm {
976 SearchAlgorithm::RandomSearch => {
977 noisy_optimizers.insert(
978 "random_search".to_string(),
979 Box::new(PrivateRandomSearch::new(config.clone())?),
980 );
981 }
982 SearchAlgorithm::BayesianOptimization => {
983 noisy_optimizers.insert(
984 "bayesian_opt".to_string(),
985 Box::new(PrivateBayesianOptimization::new(config.clone())?),
986 );
987 }
988 _ => {
989 noisy_optimizers.insert(
991 "random_search".to_string(),
992 Box::new(PrivateRandomSearch::new(config.clone())?),
993 );
994 }
995 }
996
997 Ok(Self {
998 config,
999 budget_manager,
1000 noisy_optimizers,
1001 parameterspace,
1002 private_objective: PrivateObjective::new()?,
1003 search_strategy: SearchStrategy::new(),
1004 results_aggregator: PrivateResultsAggregator::new()?,
1005 privacy_accountant,
1006 })
1007 }
1008
1009 pub fn optimize(&mut self, objective_fn: ObjectiveFn<T>) -> Result<PrivateHPOResults<T>> {
1011 self.private_objective.set_objective(objective_fn)?;
1013
1014 let mut evaluations = Vec::new();
1015 let mut bestconfig: Option<ParameterConfiguration<T>> = None;
1016 let mut best_score = T::neg_infinity();
1017
1018 let optimizer_name = match self.config.search_algorithm {
1020 SearchAlgorithm::RandomSearch => "random_search",
1021 SearchAlgorithm::BayesianOptimization => "bayesian_opt",
1022 _ => "random_search",
1023 };
1024
1025 for iteration in 0..self.config.num_evaluations {
1026 if !self.budget_manager.has_budget_remaining()? {
1028 break;
1029 }
1030
1031 let evaluation_budget = self.budget_manager.get_evaluation_budget(iteration)?;
1033
1034 let config = if let Some(optimizer) = self.noisy_optimizers.get_mut(optimizer_name) {
1036 optimizer.suggest_next(&self.parameterspace, &evaluations, &evaluation_budget)?
1037 } else {
1038 return Err(OptimError::InvalidConfig(
1039 "No optimizer available".to_string(),
1040 ));
1041 };
1042
1043 let result = self
1045 .private_objective
1046 .evaluate(&config, &evaluation_budget)?;
1047
1048 let evaluation = HPOEvaluation {
1050 id: format!("eval_{}", iteration),
1051 configuration: config.clone(),
1052 result: result.clone(),
1053 privacy_cost: evaluation_budget.clone(),
1054 timestamp: std::time::SystemTime::now()
1055 .duration_since(std::time::UNIX_EPOCH)
1056 .unwrap()
1057 .as_secs(),
1058 metadata: HashMap::new(),
1059 };
1060
1061 if result.objective_value > best_score {
1063 best_score = result.objective_value;
1064 bestconfig = Some(config.clone());
1065 }
1066
1067 if let Some(optimizer) = self.noisy_optimizers.get_mut(optimizer_name) {
1069 optimizer.update(&config, &result, &evaluation_budget)?;
1070 }
1071
1072 evaluations.push(evaluation);
1074
1075 self.budget_manager.record_evaluation(
1077 &evaluation_budget,
1078 result.objective_value.to_f64().unwrap_or(0.0),
1079 )?;
1080
1081 if self.should_stop_early(&evaluations)? {
1083 break;
1084 }
1085 }
1086
1087 let final_results = self.results_aggregator.aggregate_results(&evaluations)?;
1089
1090 Ok(PrivateHPOResults {
1091 bestconfiguration: bestconfig,
1092 best_score,
1093 all_evaluations: evaluations,
1094 final_results,
1095 total_privacy_cost: self.budget_manager.get_total_consumed_budget(),
1096 optimization_stats: self.compute_optimization_stats()?,
1097 })
1098 }
1099
1100 fn should_stop_early(&self, evaluations: &[HPOEvaluation<T>]) -> Result<bool> {
1102 if !self.config.early_stopping.enabled {
1103 return Ok(false);
1104 }
1105
1106 if evaluations.len() < self.config.early_stopping.patience {
1107 return Ok(false);
1108 }
1109
1110 let recent_scores: Vec<T> = evaluations
1112 .iter()
1113 .rev()
1114 .take(self.config.early_stopping.patience)
1115 .map(|eval| eval.result.objective_value)
1116 .collect();
1117
1118 let best_recent =
1119 recent_scores
1120 .iter()
1121 .fold(T::neg_infinity(), |acc, &x| if x > acc { x } else { acc });
1122
1123 let best_overall = evaluations
1124 .iter()
1125 .map(|eval| eval.result.objective_value)
1126 .fold(T::neg_infinity(), |acc, x| if x > acc { x } else { acc });
1127
1128 let improvement = best_recent - best_overall;
1129
1130 Ok(improvement
1131 < T::from(self.config.early_stopping.min_improvement).unwrap_or_else(|| T::zero()))
1132 }
1133
1134 fn compute_optimization_stats(&self) -> Result<OptimizationStats<T>> {
1136 Ok(OptimizationStats {
1137 total_evaluations: 0,
1138 successful_evaluations: 0,
1139 failed_evaluations: 0,
1140 average_evaluation_time: 0.0,
1141 total_optimization_time: 0.0,
1142 convergence_iteration: None,
1143 budget_efficiency: 0.0,
1144 _phantom: std::marker::PhantomData,
1145 })
1146 }
1147}
1148
1149#[derive(Debug, Clone)]
1151pub struct PrivateHPOResults<T: Float + Debug + Send + Sync + 'static> {
1152 pub bestconfiguration: Option<ParameterConfiguration<T>>,
1154
1155 pub best_score: T,
1157
1158 pub all_evaluations: Vec<HPOEvaluation<T>>,
1160
1161 pub final_results: AggregatedResults<T>,
1163
1164 pub total_privacy_cost: PrivacyBudget,
1166
1167 pub optimization_stats: OptimizationStats<T>,
1169}
1170
1171#[derive(Debug, Clone)]
1173pub struct AggregatedResults<T: Float + Debug + Send + Sync + 'static> {
1174 pub topconfigurations: Vec<(ParameterConfiguration<T>, T)>,
1176
1177 pub confidence_intervals: Option<(T, T)>,
1179
1180 pub summary_stats: SummaryStatistics<T>,
1182
1183 pub model_selection: Option<ModelSelectionResults<T>>,
1185}
1186
1187#[derive(Debug, Clone)]
1189pub struct SummaryStatistics<T: Float + Debug + Send + Sync + 'static> {
1190 pub noisy_mean: T,
1192
1193 pub noisy_std: T,
1195
1196 pub noisy_median: T,
1198
1199 pub noisy_quantiles: Vec<(f64, T)>,
1201}
1202
1203#[derive(Debug, Clone)]
1205pub struct ModelSelectionResults<T: Float + Debug + Send + Sync + 'static> {
1206 pub selectedconfig: ParameterConfiguration<T>,
1208
1209 pub selection_confidence: f64,
1211
1212 pub alternatives: Vec<ParameterConfiguration<T>>,
1214}
1215
1216#[derive(Debug, Clone)]
1218pub struct OptimizationStats<T: Float + Debug + Send + Sync + 'static> {
1219 pub total_evaluations: usize,
1221
1222 pub successful_evaluations: usize,
1224
1225 pub failed_evaluations: usize,
1227
1228 pub average_evaluation_time: f64,
1230
1231 pub total_optimization_time: f64,
1233
1234 pub convergence_iteration: Option<usize>,
1236
1237 pub budget_efficiency: f64,
1239
1240 _phantom: std::marker::PhantomData<T>,
1242}
1243
1244pub struct PrivateRandomSearch<T: Float + Debug + Send + Sync + 'static> {
1246 config: PrivateHPOConfig<T>,
1248
1249 rng: scirs2_core::random::Random<StdRng>,
1251
1252 history: Vec<HPOEvaluation<T>>,
1254}
1255
1256impl<T: Float + Debug + Send + Sync + 'static> PrivateRandomSearch<T> {
1257 pub fn new(config: PrivateHPOConfig<T>) -> Result<Self> {
1258 Ok(Self {
1259 config,
1260 rng: scirs2_core::random::Random::seed(42),
1261 history: Vec::new(),
1262 })
1263 }
1264}
1265
1266impl<T: Float + Debug + Send + Sync + 'static> NoisyOptimizer<T> for PrivateRandomSearch<T> {
1267 fn suggest_next(
1268 &mut self,
1269 parameterspace: &ParameterSpace<T>,
1270 _evaluation_history: &[HPOEvaluation<T>],
1271 _privacy_budget: &PrivacyBudget,
1272 ) -> Result<ParameterConfiguration<T>> {
1273 let mut values = HashMap::new();
1274
1275 for (param_name, param_def) in ¶meterspace.parameters {
1276 let value = match ¶m_def.param_type {
1277 ParameterType::Continuous => {
1278 let min = param_def.bounds.min.unwrap_or(T::zero());
1279 let max = param_def.bounds.max.unwrap_or(T::one());
1280 let random_val = T::from(self.rng.gen_range(0.0..1.0)).unwrap();
1281 ParameterValue::Continuous(min + random_val * (max - min))
1282 }
1283 ParameterType::Integer => {
1284 let min = param_def
1285 .bounds
1286 .min
1287 .unwrap_or(T::zero())
1288 .to_i64()
1289 .unwrap_or(0);
1290 let max = param_def
1291 .bounds
1292 .max
1293 .unwrap_or(T::from(100).unwrap_or_else(|| T::zero()))
1294 .to_i64()
1295 .unwrap_or(100);
1296 ParameterValue::Integer(self.rng.gen_range(min..max + 1))
1297 }
1298 ParameterType::Boolean => ParameterValue::Boolean(self.rng.gen_range(0..2) == 1),
1299 ParameterType::Categorical(categories) => {
1300 let idx = self.rng.gen_range(0..categories.len());
1301 ParameterValue::Categorical(categories[idx].clone())
1302 }
1303 ParameterType::Ordinal(values) => {
1304 let idx = self.rng.gen_range(0..values.len());
1305 ParameterValue::Ordinal(idx)
1306 }
1307 };
1308
1309 values.insert(param_name.clone(), value);
1310 }
1311
1312 Ok(ParameterConfiguration {
1313 values,
1314 id: format!("config_{}", self.history.len()),
1315 metadata: HashMap::new(),
1316 })
1317 }
1318
1319 fn update(
1320 &mut self,
1321 config: &ParameterConfiguration<T>,
1322 _result: &HPOResult<T>,
1323 _privacy_budget: &PrivacyBudget,
1324 ) -> Result<()> {
1325 Ok(())
1327 }
1328
1329 fn name(&self) -> &str {
1330 "PrivateRandomSearch"
1331 }
1332}
1333
1334pub struct PrivateBayesianOptimization<T: Float + Debug + Send + Sync + 'static> {
1336 config: PrivateHPOConfig<T>,
1338
1339 gp_model: Option<GaussianProcessModel<T>>,
1341
1342 acquisition_fn: AcquisitionFunction<T>,
1344
1345 history: Vec<HPOEvaluation<T>>,
1347}
1348
1349impl<T: Float + Debug + Send + Sync + 'static> PrivateBayesianOptimization<T> {
1350 pub fn new(config: PrivateHPOConfig<T>) -> Result<Self> {
1351 Ok(Self {
1352 config,
1353 gp_model: None,
1354 acquisition_fn: AcquisitionFunction::new(),
1355 history: Vec::new(),
1356 })
1357 }
1358}
1359
1360impl<T: Float + Debug + Send + Sync + 'static> NoisyOptimizer<T>
1361 for PrivateBayesianOptimization<T>
1362{
1363 fn suggest_next(
1364 &mut self,
1365 parameterspace: &ParameterSpace<T>,
1366 evaluation_history: &[HPOEvaluation<T>],
1367 _privacy_budget: &PrivacyBudget,
1368 ) -> Result<ParameterConfiguration<T>> {
1369 if evaluation_history.is_empty() {
1370 let mut rng = scirs2_core::random::Random::seed(42);
1372 let mut values = HashMap::new();
1373
1374 for (param_name, param_def) in ¶meterspace.parameters {
1375 let value = match ¶m_def.param_type {
1376 ParameterType::Continuous => {
1377 let min = param_def.bounds.min.unwrap_or(T::zero());
1378 let max = param_def.bounds.max.unwrap_or(T::one());
1379 let random_val = T::from(rng.gen_range(0.0..1.0)).unwrap();
1380 ParameterValue::Continuous(min + random_val * (max - min))
1381 }
1382 _ => {
1383 ParameterValue::Continuous(T::from(0.5).unwrap_or_else(|| T::zero()))
1385 }
1386 };
1387 values.insert(param_name.clone(), value);
1388 }
1389
1390 return Ok(ParameterConfiguration {
1391 values,
1392 id: "initialconfig".to_string(),
1393 metadata: HashMap::new(),
1394 });
1395 }
1396
1397 Ok(ParameterConfiguration {
1400 values: HashMap::new(),
1401 id: format!("bayesianconfig_{}", evaluation_history.len()),
1402 metadata: HashMap::new(),
1403 })
1404 }
1405
1406 fn update(
1407 &mut self,
1408 config: &ParameterConfiguration<T>,
1409 _result: &HPOResult<T>,
1410 _privacy_budget: &PrivacyBudget,
1411 ) -> Result<()> {
1412 Ok(())
1415 }
1416
1417 fn name(&self) -> &str {
1418 "PrivateBayesianOptimization"
1419 }
1420}
1421
1422pub struct GaussianProcessModel<T: Float + Debug + Send + Sync + 'static> {
1424 training_inputs: Vec<Vec<T>>,
1426
1427 training_outputs: Vec<T>,
1429
1430 kernel: KernelFunction<T>,
1432
1433 hyperparameters: Vec<T>,
1435}
1436
1437pub struct KernelFunction<T: Float + Debug + Send + Sync + 'static> {
1439 kernel_type: KernelType,
1441
1442 parameters: Vec<T>,
1444}
1445
1446#[derive(Debug, Clone, Copy)]
1448pub enum KernelType {
1449 RBF,
1451
1452 Matern,
1454
1455 Linear,
1457
1458 Polynomial,
1460}
1461
1462pub struct AcquisitionFunction<T: Float + Debug + Send + Sync + 'static> {
1464 function_type: AcquisitionFunctionType,
1466
1467 parameters: Vec<T>,
1469
1470 exploration_weight: T,
1472}
1473
1474#[derive(Debug, Clone, Copy)]
1476pub enum AcquisitionFunctionType {
1477 ExpectedImprovement,
1479
1480 UpperConfidenceBound,
1482
1483 ProbabilityOfImprovement,
1485
1486 KnowledgeGradient,
1488}
1489
1490impl HPOBudgetManager {
1492 pub fn new(
1493 baseconfig: DifferentialPrivacyConfig,
1494 allocation_strategy: BudgetAllocationStrategy,
1495 num_evaluations: usize,
1496 ) -> Result<Self> {
1497 let total_budget = PrivacyBudget {
1498 epsilon_consumed: 0.0,
1499 delta_consumed: 0.0,
1500 epsilon_remaining: baseconfig.target_epsilon,
1501 delta_remaining: baseconfig.target_delta,
1502 steps_taken: 0,
1503 accounting_method: crate::privacy::AccountingMethod::MomentsAccountant,
1504 estimated_steps_remaining: num_evaluations,
1505 };
1506
1507 Ok(Self {
1508 total_budget,
1509 evaluation_budgets: Vec::new(),
1510 allocation_strategy,
1511 consumed_budget: PrivacyBudget {
1512 epsilon_consumed: 0.0,
1513 delta_consumed: 0.0,
1514 epsilon_remaining: baseconfig.target_epsilon,
1515 delta_remaining: baseconfig.target_delta,
1516 steps_taken: 0,
1517 accounting_method: crate::privacy::AccountingMethod::MomentsAccountant,
1518 estimated_steps_remaining: num_evaluations,
1519 },
1520 adaptive_controller: AdaptiveBudgetController::new(),
1521 })
1522 }
1523
1524 pub fn has_budget_remaining(&self) -> Result<bool> {
1525 Ok(self.consumed_budget.epsilon_remaining > 0.0
1526 && self.consumed_budget.delta_remaining > 0.0)
1527 }
1528
1529 pub fn get_evaluation_budget(&mut self, iteration: usize) -> Result<PrivacyBudget> {
1530 let remaining_evaluations = self
1531 .total_budget
1532 .estimated_steps_remaining
1533 .saturating_sub(iteration);
1534 let epsilon_per_eval =
1535 self.consumed_budget.epsilon_remaining / remaining_evaluations.max(1) as f64;
1536 let delta_per_eval =
1537 self.consumed_budget.delta_remaining / remaining_evaluations.max(1) as f64;
1538
1539 Ok(PrivacyBudget {
1540 epsilon_consumed: 0.0,
1541 delta_consumed: 0.0,
1542 epsilon_remaining: epsilon_per_eval,
1543 delta_remaining: delta_per_eval,
1544 steps_taken: 0,
1545 accounting_method: crate::privacy::AccountingMethod::MomentsAccountant,
1546 estimated_steps_remaining: 1,
1547 })
1548 }
1549
1550 pub fn record_evaluation(&mut self, budgetused: &PrivacyBudget, score: f64) -> Result<()> {
1551 self.consumed_budget.epsilon_consumed += budgetused.epsilon_remaining;
1552 self.consumed_budget.delta_consumed += budgetused.delta_remaining;
1553 self.consumed_budget.epsilon_remaining -= budgetused.epsilon_remaining;
1554 self.consumed_budget.delta_remaining -= budgetused.delta_remaining;
1555 self.consumed_budget.steps_taken += 1;
1556
1557 self.adaptive_controller.record_performance(score);
1558 Ok(())
1559 }
1560
1561 pub fn get_total_consumed_budget(&self) -> PrivacyBudget {
1562 self.consumed_budget.clone()
1563 }
1564}
1565
1566impl Default for AdaptiveBudgetController {
1567 fn default() -> Self {
1568 Self::new()
1569 }
1570}
1571
1572impl AdaptiveBudgetController {
1573 pub fn new() -> Self {
1574 Self {
1575 performance_history: Vec::new(),
1576 budget_efficiency: Vec::new(),
1577 allocation_weights: Vec::new(),
1578 adaptation_rate: 0.1,
1579 }
1580 }
1581
1582 pub fn record_performance(&mut self, score: f64) {
1583 self.performance_history.push(score);
1584 }
1585}
1586
1587impl<T: Float + Debug + Send + Sync + 'static> PrivateObjective<T> {
1588 pub fn new() -> Result<Self> {
1589 Ok(Self {
1590 objective_fn: Box::new(|_| Ok(0.0)),
1591 noise_mechanism: ObjectiveNoiseMechanism::new(),
1592 sensitivity_analyzer: ObjectiveSensitivityAnalyzer::new(),
1593 cv_evaluator: PrivateCrossValidation::new(),
1594 })
1595 }
1596
1597 pub fn set_objective(&mut self, objective_fn: ObjectiveFn<T>) -> Result<()> {
1598 self.objective_fn = objective_fn;
1599 Ok(())
1600 }
1601
1602 pub fn evaluate(
1603 &mut self,
1604 config: &ParameterConfiguration<T>,
1605 privacy_budget: &PrivacyBudget,
1606 ) -> Result<HPOResult<T>> {
1607 let objective_value = (self.objective_fn)(config)?;
1608 let noisy_value = self
1609 .noise_mechanism
1610 .add_noise(objective_value, privacy_budget)?;
1611
1612 Ok(HPOResult {
1613 objective_value: T::from(noisy_value).unwrap_or_else(|| T::zero()),
1614 standard_error: None,
1615 cv_scores: None,
1616 training_time: None,
1617 complexity_metrics: HashMap::new(),
1618 additional_metrics: HashMap::new(),
1619 status: EvaluationStatus::Success,
1620 })
1621 }
1622}
1623
1624impl<T: Float + Debug + Send + Sync + 'static> Default for ObjectiveNoiseMechanism<T> {
1625 fn default() -> Self {
1626 Self::new()
1627 }
1628}
1629
1630impl<T: Float + Debug + Send + Sync + 'static> ObjectiveNoiseMechanism<T> {
1631 pub fn new() -> Self {
1632 Self {
1633 mechanism_type: HyperparameterNoiseMechanism::Gaussian,
1634 noise_params: NoiseParameters {
1635 scale: T::one(),
1636 sensitivity: T::one(),
1637 epsilon: 1.0,
1638 delta: Some(1e-5),
1639 },
1640 rng: scirs2_core::random::Random::default(),
1641 }
1642 }
1643
1644 pub fn add_noise(&mut self, value: f64, _privacybudget: &PrivacyBudget) -> Result<f64> {
1645 use scirs2_core::random::{RandNormal, Rng};
1646
1647 match self.mechanism_type {
1648 HyperparameterNoiseMechanism::Gaussian => {
1649 let noise_scale = self.noise_params.scale.to_f64().unwrap_or(1.0);
1650 let normal = RandNormal::new(0.0, noise_scale)
1651 .map_err(|_| OptimError::InvalidConfig("Invalid noise scale".to_string()))?;
1652
1653 let noise = self.rng.sample(normal);
1654 Ok(value + noise)
1655 }
1656 _ => Ok(value), }
1658 }
1659}
1660
1661impl<T: Float + Debug + Send + Sync + 'static> Default for ObjectiveSensitivityAnalyzer<T> {
1662 fn default() -> Self {
1663 Self::new()
1664 }
1665}
1666
1667impl<T: Float + Debug + Send + Sync + 'static> ObjectiveSensitivityAnalyzer<T> {
1668 pub fn new() -> Self {
1669 Self {
1670 estimation_method: SensitivityEstimationMethod::Global,
1671 sensitivity_cache: HashMap::new(),
1672 sample_estimator: SampleBasedSensitivityEstimator::new(),
1673 }
1674 }
1675}
1676
1677impl<T: Float + Debug + Send + Sync + 'static> Default for SampleBasedSensitivityEstimator<T> {
1678 fn default() -> Self {
1679 Self::new()
1680 }
1681}
1682
1683impl<T: Float + Debug + Send + Sync + 'static> SampleBasedSensitivityEstimator<T> {
1684 pub fn new() -> Self {
1685 Self {
1686 num_samples: 1000,
1687 sampling_strategy: SamplingStrategy::Uniform,
1688 confidence_level: 0.95,
1689 bootstrap_estimator: BootstrapEstimator::new(),
1690 _phantom: std::marker::PhantomData,
1691 }
1692 }
1693}
1694
1695impl<T: Float + Debug + Send + Sync + 'static> Default for BootstrapEstimator<T> {
1696 fn default() -> Self {
1697 Self::new()
1698 }
1699}
1700
1701impl<T: Float + Debug + Send + Sync + 'static> BootstrapEstimator<T> {
1702 pub fn new() -> Self {
1703 Self {
1704 num_bootstrap: 1000,
1705 confidence_interval: (0.025, 0.975),
1706 bias_correction: true,
1707 _phantom: std::marker::PhantomData,
1708 }
1709 }
1710}
1711
1712impl<T: Float + Debug + Send + Sync + 'static> Default for PrivateCrossValidation<T> {
1713 fn default() -> Self {
1714 Self::new()
1715 }
1716}
1717
1718impl<T: Float + Debug + Send + Sync + 'static> PrivateCrossValidation<T> {
1719 pub fn new() -> Self {
1720 Self {
1721 num_folds: 5,
1722 fold_budgets: Vec::new(),
1723 fold_strategy: FoldStrategy::Random,
1724 private_aggregation: PrivateFoldAggregation::new(),
1725 }
1726 }
1727}
1728
1729impl<T: Float + Debug + Send + Sync + 'static> Default for PrivateFoldAggregation<T> {
1730 fn default() -> Self {
1731 Self::new()
1732 }
1733}
1734
1735impl<T: Float + Debug + Send + Sync + 'static> PrivateFoldAggregation<T> {
1736 pub fn new() -> Self {
1737 Self {
1738 aggregation_method: AggregationMethod::NoisyMean,
1739 noise_params: NoiseParameters {
1740 scale: T::one(),
1741 sensitivity: T::one(),
1742 epsilon: 1.0,
1743 delta: Some(1e-5),
1744 },
1745 confidence_estimation: ConfidenceEstimation::new(),
1746 }
1747 }
1748}
1749
1750impl<T: Float + Debug + Send + Sync + 'static> Default for ConfidenceEstimation<T> {
1751 fn default() -> Self {
1752 Self::new()
1753 }
1754}
1755
1756impl<T: Float + Debug + Send + Sync + 'static> ConfidenceEstimation<T> {
1757 pub fn new() -> Self {
1758 Self {
1759 confidence_level: 0.95,
1760 estimation_method: ConfidenceEstimationMethod::Normal,
1761 bootstrap_params: None,
1762 _phantom: std::marker::PhantomData,
1763 }
1764 }
1765}
1766
1767impl<T: Float + Debug + Send + Sync + 'static> Default for SearchStrategy<T> {
1768 fn default() -> Self {
1769 Self::new()
1770 }
1771}
1772
1773impl<T: Float + Debug + Send + Sync + 'static> SearchStrategy<T> {
1774 pub fn new() -> Self {
1775 Self {
1776 algorithm: SearchAlgorithm::RandomSearch,
1777 algorithm_params: HashMap::new(),
1778 exploration_factor: 0.1,
1779 convergence_criteria: ConvergenceCriteria {
1780 max_iterations: 100,
1781 tolerance: T::from(1e-6).unwrap_or_else(|| T::zero()),
1782 patience: 10,
1783 min_change: T::from(1e-4).unwrap_or_else(|| T::zero()),
1784 },
1785 }
1786 }
1787}
1788
1789impl<T: Float + Debug + Send + Sync + 'static> PrivateResultsAggregator<T> {
1790 pub fn new() -> Result<Self> {
1791 Ok(Self {
1792 aggregation_strategy: ResultAggregationStrategy::SelectBest,
1793 selection_budget: PrivacyBudget {
1794 epsilon_consumed: 0.0,
1795 delta_consumed: 0.0,
1796 epsilon_remaining: 0.1,
1797 delta_remaining: 1e-6,
1798 steps_taken: 0,
1799 accounting_method: crate::privacy::AccountingMethod::MomentsAccountant,
1800 estimated_steps_remaining: 1,
1801 },
1802 selection_mechanism: SelectionMechanism::new(),
1803 result_validator: ResultValidator::new(),
1804 })
1805 }
1806
1807 pub fn aggregate_results(
1808 &self,
1809 evaluations: &[HPOEvaluation<T>],
1810 ) -> Result<AggregatedResults<T>> {
1811 let mut topconfigs = Vec::new();
1812
1813 let mut sorted_evals = evaluations.to_vec();
1815 sorted_evals.sort_by(|a, b| {
1816 b.result
1817 .objective_value
1818 .partial_cmp(&a.result.objective_value)
1819 .unwrap_or(std::cmp::Ordering::Equal)
1820 });
1821
1822 for eval in sorted_evals.iter().take(5) {
1824 topconfigs.push((eval.configuration.clone(), eval.result.objective_value));
1825 }
1826
1827 let objective_values: Vec<T> = evaluations
1829 .iter()
1830 .map(|eval| eval.result.objective_value)
1831 .collect();
1832
1833 let mean = if !objective_values.is_empty() {
1834 objective_values.iter().fold(T::zero(), |acc, &x| acc + x)
1835 / T::from(objective_values.len()).unwrap()
1836 } else {
1837 T::zero()
1838 };
1839
1840 let summary_stats = SummaryStatistics {
1841 noisy_mean: mean,
1842 noisy_std: T::zero(), noisy_median: mean,
1844 noisy_quantiles: vec![(0.5, mean)],
1845 };
1846
1847 Ok(AggregatedResults {
1848 topconfigurations: topconfigs,
1849 confidence_intervals: None,
1850 summary_stats,
1851 model_selection: None,
1852 })
1853 }
1854}
1855
1856impl<T: Float + Debug + Send + Sync + 'static> Default for SelectionMechanism<T> {
1857 fn default() -> Self {
1858 Self::new()
1859 }
1860}
1861
1862impl<T: Float + Debug + Send + Sync + 'static> SelectionMechanism<T> {
1863 pub fn new() -> Self {
1864 Self {
1865 mechanism_type: HyperparameterNoiseMechanism::Exponential,
1866 selection_params: SelectionParameters {
1867 temperature: T::one(),
1868 utility_sensitivity: T::one(),
1869 epsilon: 1.0,
1870 threshold: None,
1871 },
1872 utility_function: UtilityFunction::new(),
1873 }
1874 }
1875}
1876
1877impl<T: Float + Debug + Send + Sync + 'static> Default for UtilityFunction<T> {
1878 fn default() -> Self {
1879 Self::new()
1880 }
1881}
1882
1883impl<T: Float + Debug + Send + Sync + 'static> UtilityFunction<T> {
1884 pub fn new() -> Self {
1885 Self {
1886 function_type: UtilityFunctionType::Linear,
1887 parameters: vec![T::one()],
1888 multi_objective_weights: None,
1889 }
1890 }
1891}
1892
1893impl<T: Float + Debug + Send + Sync + 'static> Default for ResultValidator<T> {
1894 fn default() -> Self {
1895 Self::new()
1896 }
1897}
1898
1899impl<T: Float + Debug + Send + Sync + 'static> ResultValidator<T> {
1900 pub fn new() -> Self {
1901 Self {
1902 validation_rules: Vec::new(),
1903 statistical_tests: Vec::new(),
1904 anomaly_detector: AnomalyDetector::new(),
1905 }
1906 }
1907}
1908
1909impl<T: Float + Debug + Send + Sync + 'static> Default for AnomalyDetector<T> {
1910 fn default() -> Self {
1911 Self::new()
1912 }
1913}
1914
1915impl<T: Float + Debug + Send + Sync + 'static> AnomalyDetector<T> {
1916 pub fn new() -> Self {
1917 Self {
1918 threshold: T::from(3.0).unwrap_or_else(|| T::zero()),
1919 detection_method: AnomalyDetectionMethod::ZScore,
1920 baseline: None,
1921 }
1922 }
1923}
1924
1925impl<T: Float + Debug + Send + Sync + 'static> Default for GaussianProcessModel<T> {
1926 fn default() -> Self {
1927 Self::new()
1928 }
1929}
1930
1931impl<T: Float + Debug + Send + Sync + 'static> GaussianProcessModel<T> {
1932 pub fn new() -> Self {
1933 Self {
1934 training_inputs: Vec::new(),
1935 training_outputs: Vec::new(),
1936 kernel: KernelFunction::new(),
1937 hyperparameters: Vec::new(),
1938 }
1939 }
1940}
1941
1942impl<T: Float + Debug + Send + Sync + 'static> Default for KernelFunction<T> {
1943 fn default() -> Self {
1944 Self::new()
1945 }
1946}
1947
1948impl<T: Float + Debug + Send + Sync + 'static> KernelFunction<T> {
1949 pub fn new() -> Self {
1950 Self {
1951 kernel_type: KernelType::RBF,
1952 parameters: vec![T::one()],
1953 }
1954 }
1955}
1956
1957impl<T: Float + Debug + Send + Sync + 'static> Default for AcquisitionFunction<T> {
1958 fn default() -> Self {
1959 Self::new()
1960 }
1961}
1962
1963impl<T: Float + Debug + Send + Sync + 'static> AcquisitionFunction<T> {
1964 pub fn new() -> Self {
1965 Self {
1966 function_type: AcquisitionFunctionType::ExpectedImprovement,
1967 parameters: Vec::new(),
1968 exploration_weight: T::from(0.1).unwrap_or_else(|| T::zero()),
1969 }
1970 }
1971}
1972
1973#[cfg(test)]
1974mod tests {
1975 use super::*;
1976
1977 #[test]
1978 fn test_private_hpoconfig() {
1979 let config = PrivateHPOConfig {
1980 base_privacyconfig: DifferentialPrivacyConfig::default(),
1981 budget_allocation: BudgetAllocationStrategy::Equal,
1982 search_algorithm: SearchAlgorithm::RandomSearch,
1983 num_evaluations: 100,
1984 cv_folds: 5,
1985 early_stopping: EarlyStoppingConfig {
1986 enabled: true,
1987 patience: 10,
1988 min_improvement: 0.01,
1989 max_evaluations: 100,
1990 },
1991 noise_mechanism: HyperparameterNoiseMechanism::Gaussian,
1992 sensitivity_bounds: SensitivityBounds {
1993 global_sensitivity: HashMap::<String, f64>::new(),
1994 local_sensitivity: HashMap::<String, (f64, f64)>::new(),
1995 smooth_sensitivity: HashMap::<String, SmoothSensitivityParams<f64>>::new(),
1996 },
1997 private_model_selection: true,
1998 validation_strategy: ValidationStrategy::KFoldCV,
1999 };
2000
2001 assert_eq!(config.num_evaluations, 100);
2002 assert_eq!(config.cv_folds, 5);
2003 assert!(config.early_stopping.enabled);
2004 }
2005
2006 #[test]
2007 fn test_parameter_space() {
2008 let mut parameters = HashMap::new();
2009
2010 parameters.insert(
2011 "learning_rate".to_string(),
2012 ParameterDefinition {
2013 name: "learning_rate".to_string(),
2014 param_type: ParameterType::Continuous,
2015 bounds: ParameterBounds {
2016 min: Some(0.001),
2017 max: Some(0.1),
2018 step: None,
2019 valid_values: None,
2020 },
2021 prior: Some(ParameterPrior::LogNormal(-3.0, 1.0)),
2022 transformation: Some(ParameterTransformation::Log),
2023 },
2024 );
2025
2026 let parameterspace = ParameterSpace {
2027 parameters,
2028 constraints: Vec::new(),
2029 defaultconfig: None,
2030 };
2031
2032 assert!(parameterspace.parameters.contains_key("learning_rate"));
2033 }
2034
2035 #[test]
2036 fn test_budget_manager() {
2037 let baseconfig = DifferentialPrivacyConfig::default();
2038 let budget_manager =
2039 HPOBudgetManager::new(baseconfig, BudgetAllocationStrategy::Equal, 10).unwrap();
2040
2041 assert!(budget_manager.has_budget_remaining().unwrap());
2042 }
2043
2044 #[test]
2045 fn test_private_random_search() {
2046 let config = PrivateHPOConfig {
2047 base_privacyconfig: DifferentialPrivacyConfig::default(),
2048 budget_allocation: BudgetAllocationStrategy::Equal,
2049 search_algorithm: SearchAlgorithm::RandomSearch,
2050 num_evaluations: 10,
2051 cv_folds: 3,
2052 early_stopping: EarlyStoppingConfig {
2053 enabled: false,
2054 patience: 5,
2055 min_improvement: 0.01,
2056 max_evaluations: 10,
2057 },
2058 noise_mechanism: HyperparameterNoiseMechanism::Gaussian,
2059 sensitivity_bounds: SensitivityBounds {
2060 global_sensitivity: HashMap::<String, f64>::new(),
2061 local_sensitivity: HashMap::<String, (f64, f64)>::new(),
2062 smooth_sensitivity: HashMap::<String, SmoothSensitivityParams<f64>>::new(),
2063 },
2064 private_model_selection: false,
2065 validation_strategy: ValidationStrategy::HoldOut,
2066 };
2067
2068 let optimizer = PrivateRandomSearch::new(config).unwrap();
2069 assert_eq!(optimizer.name(), "PrivateRandomSearch");
2070 }
2071}