optirs_core/metrics/
optimizer.rs1#[cfg(not(feature = "metrics-integration"))]
6use crate::error::OptimError;
7use crate::error::Result;
8use crate::optimizers::Optimizer;
9#[cfg(feature = "metrics-integration")]
10use scirs2_core::ndarray::{Array, Dimension, ScalarOperand};
11#[cfg(not(feature = "metrics-integration"))]
12use scirs2_core::ndarray::{Dimension, ScalarOperand};
13use scirs2_core::numeric::{Float, FromPrimitive};
14#[cfg(feature = "metrics-integration")]
15use std::collections::HashMap;
16use std::fmt::{Debug, Display};
17use std::marker::PhantomData;
18
19#[cfg(feature = "metrics-integration")]
21pub struct MetricOptimizer<F, D>
22where
23 F: Float + Debug + Display + FromPrimitive + ScalarOperand,
24 D: Dimension,
25{
26 base_optimizer: Box<dyn Optimizer<F, D>>,
28 current_lr: F,
30 metric_adapter: scirs2_metrics::integration::optim::MetricOptimizer<F>,
32 history: Vec<HashMap<String, Array<F, D>>>,
34 best_params: Option<HashMap<String, Array<F, D>>>,
36 _phantom: PhantomData<(F, D)>,
38}
39
40#[cfg(feature = "metrics-integration")]
41impl<F, D> MetricOptimizer<F, D>
42where
43 F: Float + Debug + Display + FromPrimitive + ScalarOperand + Send + Sync + 'static,
44 D: Dimension + 'static,
45{
46 pub fn new<O>(optimizer: O, metric_name: &str, maximize: bool) -> Result<Self>
53 where
54 O: Optimizer<F, D> + 'static,
55 {
56 let initial_lr = optimizer.get_learning_rate();
57 Ok(Self {
58 base_optimizer: Box::new(optimizer),
59 current_lr: initial_lr,
60 metric_adapter: scirs2_metrics::integration::optim::MetricOptimizer::new(
61 metric_name,
62 maximize,
63 ),
64 history: Vec::new(),
65 best_params: None,
66 _phantom: PhantomData,
67 })
68 }
69
70 pub fn update_metric(&mut self, metric: F) -> Result<()> {
72 self.metric_adapter.add_value(metric);
73 Ok(())
74 }
75
76 pub fn update_metrics(&mut self, metrics: HashMap<String, F>) -> Result<()> {
78 if let Some(value) = metrics.get(self.metric_adapter.metric_name()) {
80 self.metric_adapter.add_value(*value);
81 }
82
83 for (name, value) in metrics {
85 if name != self.metric_adapter.metric_name() {
86 self.metric_adapter.add_additional_value(&name, value);
87 }
88 }
89
90 Ok(())
91 }
92
93 pub fn metric_adapter(&self) -> &scirs2_metrics::integration::optim::MetricOptimizer<F> {
95 &self.metric_adapter
96 }
97
98 pub fn metric_adapter_mut(
100 &mut self,
101 ) -> &mut scirs2_metrics::integration::optim::MetricOptimizer<F> {
102 &mut self.metric_adapter
103 }
104
105 pub fn base_optimizer(&self) -> &dyn Optimizer<F, D> {
107 &*self.base_optimizer
108 }
109
110 pub fn base_optimizer_mut(&mut self) -> &mut dyn Optimizer<F, D> {
112 &mut *self.base_optimizer
113 }
114
115 pub fn best_params(&self) -> Option<&HashMap<String, Array<F, D>>> {
117 self.best_params.as_ref()
118 }
119
120 pub fn history(&self) -> &[HashMap<String, Array<F, D>>] {
122 &self.history
123 }
124
125 pub fn reset(&mut self) {
127 self.metric_adapter.reset();
128 self.history.clear();
129 self.best_params = None;
130 }
131
132 pub fn create_lr_scheduler(
134 &self,
135 initial_lr: F,
136 factor: F,
137 patience: usize,
138 min_lr: F,
139 ) -> crate::schedulers::ReduceOnPlateau<F> {
140 let mut scheduler =
141 crate::schedulers::ReduceOnPlateau::new(initial_lr, factor, patience, min_lr);
142
143 match self.metric_adapter.mode() {
145 scirs2_metrics::integration::optim::OptimizationMode::Minimize => {
146 scheduler.mode_min();
147 }
148 scirs2_metrics::integration::optim::OptimizationMode::Maximize => {
149 scheduler.mode_max();
150 }
151 }
152
153 scheduler
154 }
155}
156
157#[cfg(feature = "metrics-integration")]
158impl<F, D> Optimizer<F, D> for MetricOptimizer<F, D>
159where
160 F: Float + Debug + Display + FromPrimitive + ScalarOperand + 'static,
161 D: Dimension + 'static,
162{
163 fn step(&mut self, params: &Array<F, D>, gradients: &Array<F, D>) -> Result<Array<F, D>> {
164 let updated_params = self.base_optimizer.step(params, gradients)?;
166
167 let mut param_update = HashMap::new();
169 param_update.insert("params".to_string(), updated_params.clone());
170 param_update.insert("gradients".to_string(), gradients.clone());
171 self.history.push(param_update);
172
173 if let Some(best_value) = self.metric_adapter.best_value() {
175 let is_improvement = match self.metric_adapter.mode() {
176 scirs2_metrics::integration::optim::OptimizationMode::Maximize => {
177 if let Some(last_value) = self.metric_adapter.history().last() {
179 *last_value > best_value
180 } else {
181 false
182 }
183 }
184 scirs2_metrics::integration::optim::OptimizationMode::Minimize => {
185 if let Some(last_value) = self.metric_adapter.history().last() {
187 *last_value < best_value
188 } else {
189 false
190 }
191 }
192 };
193
194 if is_improvement {
195 let mut best_params = HashMap::new();
196 best_params.insert("params".to_string(), updated_params.clone());
197 self.best_params = Some(best_params);
198 }
199 }
200
201 Ok(updated_params)
202 }
203
204 fn get_learning_rate(&self) -> F {
205 self.current_lr
206 }
207
208 fn set_learning_rate(&mut self, learning_rate: F) {
209 self.current_lr = learning_rate;
210 }
211}
212
213#[cfg(not(feature = "metrics-integration"))]
215#[derive(Debug)]
216pub struct MetricOptimizer<F, D>
217where
218 F: Float + Debug + Display + FromPrimitive + ScalarOperand,
219 D: Dimension,
220{
221 _phantom: PhantomData<(F, D)>,
222}
223
224#[cfg(not(feature = "metrics-integration"))]
225impl<F, D> MetricOptimizer<F, D>
226where
227 F: Float + Debug + Display + FromPrimitive + ScalarOperand,
228 D: Dimension,
229{
230 pub fn new<O>(_optimizer: O, _metric_name: &str, _maximize: bool) -> Result<Self>
236 where
237 O: Optimizer<F, D>,
238 {
239 Err(OptimError::MissingDependency(
240 "metrics-integration feature is not enabled - enable it in your Cargo.toml".to_string(),
241 ))
242 }
243}