echidna_optim/objective.rs
1use echidna::{BytecodeTape, Float};
2
3/// Trait for optimization objectives.
4///
5/// Implementors provide function evaluation and gradient computation.
6/// Methods take `&mut self` to allow caching, eval counting, and internal buffers.
7pub trait Objective<F: num_traits::Float> {
8 /// Number of input variables.
9 fn dim(&self) -> usize;
10
11 /// Evaluate the objective and its gradient at `x`.
12 ///
13 /// Returns `(f(x), ∇f(x))`.
14 fn eval_grad(&mut self, x: &[F]) -> (F, Vec<F>);
15
16 /// Evaluate the objective, gradient, and full Hessian at `x`.
17 ///
18 /// Returns `(f(x), ∇f(x), H(x))` where `H[i][j] = ∂²f/∂x_i∂x_j`.
19 ///
20 /// Default implementation panics. Only solvers that need the Hessian call this.
21 fn eval_hessian(&mut self, x: &[F]) -> (F, Vec<F>, Vec<Vec<F>>) {
22 let _ = x;
23 unimplemented!("eval_hessian not implemented for this objective")
24 }
25
26 /// Compute the Hessian-vector product H(x)·v.
27 ///
28 /// Returns `(∇f(x), H(x)·v)`.
29 ///
30 /// Default implementation panics. Only solvers that need HVP call this.
31 fn hvp(&mut self, x: &[F], v: &[F]) -> (Vec<F>, Vec<F>) {
32 let _ = (x, v);
33 unimplemented!("hvp not implemented for this objective")
34 }
35}
36
37/// Adapter wrapping a [`BytecodeTape`] as an [`Objective`].
38pub struct TapeObjective<F: Float> {
39 tape: BytecodeTape<F>,
40 func_evals: usize,
41}
42
43impl<F: Float> TapeObjective<F> {
44 /// Create a new `TapeObjective` from a recorded tape.
45 ///
46 /// ```
47 /// use echidna_optim::{lbfgs, LbfgsConfig, TapeObjective};
48 ///
49 /// let (tape, _) = echidna::record(|x| x[0] * x[0] + x[1] * x[1], &[1.0_f64, 1.0]);
50 /// let mut objective = TapeObjective::new(tape);
51 /// let result = lbfgs(&mut objective, &[1.0, 1.0], &LbfgsConfig::default());
52 /// assert!(result.x.iter().all(|&xi| xi.abs() < 1e-6));
53 /// ```
54 pub fn new(tape: BytecodeTape<F>) -> Self {
55 TapeObjective {
56 tape,
57 func_evals: 0,
58 }
59 }
60
61 /// Number of function evaluations performed so far.
62 pub fn func_evals(&self) -> usize {
63 self.func_evals
64 }
65}
66
67impl<F: Float> Objective<F> for TapeObjective<F> {
68 fn dim(&self) -> usize {
69 self.tape.num_inputs()
70 }
71
72 fn eval_grad(&mut self, x: &[F]) -> (F, Vec<F>) {
73 self.func_evals += 1;
74 let grad = self.tape.gradient(x);
75 let value = self.tape.output_value();
76 (value, grad)
77 }
78
79 fn eval_hessian(&mut self, x: &[F]) -> (F, Vec<F>, Vec<Vec<F>>) {
80 self.func_evals += 1;
81 self.tape.hessian(x)
82 }
83
84 fn hvp(&mut self, x: &[F], v: &[F]) -> (Vec<F>, Vec<F>) {
85 self.func_evals += 1;
86 self.tape.hvp(x, v)
87 }
88}