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    pub fn new(tape: BytecodeTape<F>) -> Self {
46        TapeObjective {
47            tape,
48            func_evals: 0,
49        }
50    }
51
52    /// Number of function evaluations performed so far.
53    pub fn func_evals(&self) -> usize {
54        self.func_evals
55    }
56
57    /// Borrow the underlying tape.
58    pub fn tape(&self) -> &BytecodeTape<F> {
59        &self.tape
60    }
61}
62
63impl<F: Float> Objective<F> for TapeObjective<F> {
64    fn dim(&self) -> usize {
65        self.tape.num_inputs()
66    }
67
68    fn eval_grad(&mut self, x: &[F]) -> (F, Vec<F>) {
69        self.func_evals += 1;
70        let grad = self.tape.gradient(x);
71        let value = self.tape.output_value();
72        (value, grad)
73    }
74
75    fn eval_hessian(&mut self, x: &[F]) -> (F, Vec<F>, Vec<Vec<F>>) {
76        self.func_evals += 1;
77        self.tape.hessian(x)
78    }
79
80    fn hvp(&mut self, x: &[F], v: &[F]) -> (Vec<F>, Vec<F>) {
81        self.func_evals += 1;
82        self.tape.hvp(x, v)
83    }
84}