Skip to main content

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}