raddy/sparse/
objective.rs

1use crate::{make::var, types::advec, Ad};
2use faer::{
3    sparse::{CreationError, SparseColMat},
4    Col,
5};
6use itertools::Itertools;
7
8/// Represents the computed results of an objective function evaluation
9/// including the function value, gradient, and Hessian triplets.
10///
11/// ## Type Parameters
12/// - `N`: The problem size/dimension of a single objective
13///
14/// ## Fields
15/// - `value`: The computed objective function value
16/// - `grad`: The gradient vector (first derivatives)
17/// - `hess_trips`: Hessian matrix entries stored as (row, col, value) triplets
18pub struct ComputedObjective<const N: usize> {
19    pub value: f64,
20    pub grad: Col<f64>,
21    pub hess_trips: Vec<(usize, usize, f64)>,
22}
23
24/// Defines the interface for sparse objective functions
25///
26/// ## Type Parameters
27/// - `N`: The problem size/dimension of a single objective
28///
29/// ## Associated Types
30/// - `EvalArgs`: Additional arguments needed for objective evaluation
31///
32/// ## Example
33/// ```ignore
34/// struct MyObjective;
35///
36/// impl Objective<3> for MyObjective {
37///     type EvalArgs = f64; // Example args type
38///     
39///     fn eval(&self, variables: &advec<3, 3>, args: &f64) -> Ad<3> {
40///         todo!("Your implementation")
41///     }
42/// }
43/// ```
44pub trait Objective<const N: usize> {
45    type EvalArgs;
46
47    /// Evaluates the objective function for given variables
48    ///
49    /// ## Arguments
50    /// - `variables`: The input variables as an advec
51    /// - `args`: Additional evaluation arguments
52    ///
53    /// ## Returns
54    /// An `Ad<N>` containing the function value, gradient and Hessian
55    fn eval(&self, variables: &advec<N, N>, args: &Self::EvalArgs) -> Ad<N>;
56
57    /// Helper method to evaluate objective for given indices
58    ///
59    /// ## Arguments
60    /// - `global_inds`: Global indices of variables to evaluate
61    /// - `x`: The full variable vector
62    /// - `args`: Additional evaluation arguments
63    ///
64    /// ## Returns
65    /// An `Ad<N>` containing the local evaluation results
66    fn evaluate_for_indices(
67        &self,
68        global_inds: [usize; N],
69        x: &Col<f64>,
70        args: &Self::EvalArgs,
71    ) -> Ad<N> {
72        let vals = global_inds.map(|i| x[i]);
73        let vals_slice = vals.as_slice();
74        let vars = var::vector_from_slice(vals_slice);
75        self.eval(&vars, args)
76    }
77
78    /// Computes value, gradient and Hessian triplets in one operation
79    ///
80    /// ## Arguments
81    /// - `x`: The full variable vector, may be large
82    /// - `operand_indices`: Slice of indices of variables to evaluate
83    /// - `args`: Additional evaluation arguments
84    ///
85    /// ## Returns
86    /// A `ComputedObjective<N>` containing all computed results
87    fn compute(
88        &self,
89        x: &Col<f64>,
90        operand_indices: &[[usize; N]],
91        args: &Self::EvalArgs,
92    ) -> ComputedObjective<N> {
93        let mut value = 0.0;
94        let mut grad = Col::zeros(x.nrows());
95        let mut hess_trips = Vec::new();
96
97        for &global_inds in operand_indices {
98            let obj = self.evaluate_for_indices(global_inds, x, args);
99
100            let ind = global_inds.into_iter().enumerate();
101
102            value += obj.value;
103
104            ind.clone()
105                .for_each(|(ilocal, iglobal)| grad[iglobal] += obj.grad[ilocal]);
106
107            ind.clone().cartesian_product(ind).for_each(
108                |((ixlocal, ixglobal), (iylocal, iyglobal))| {
109                    hess_trips.push((ixglobal, iyglobal, obj.hess[(ixlocal, iylocal)]));
110                },
111            );
112        }
113
114        ComputedObjective {
115            value,
116            grad,
117            hess_trips,
118        }
119    }
120
121    /// Computes just the objective function value
122    ///
123    /// ## Arguments
124    /// - `x`: The full variable vector
125    /// - `operand_indices`: Slice of indices of variables to evaluate
126    /// - `args`: Additional evaluation arguments
127    ///
128    /// ## Returns
129    /// The computed objective function value
130    fn value(&self, x: &Col<f64>, operand_indices: &[[usize; N]], args: &Self::EvalArgs) -> f64 {
131        let mut res = 0.0;
132
133        operand_indices.iter().for_each(|&ind| {
134            let obj = self.evaluate_for_indices(ind, x, args);
135            res += obj.value;
136        });
137
138        res
139    }
140
141    /// Computes just the gradient vector
142    ///
143    /// ## Arguments
144    /// - `x`: The full variable vector
145    /// - `operand_indices`: Slice of indices of variables to evaluate
146    /// - `args`: Additional evaluation arguments
147    ///
148    /// ## Returns
149    /// The computed gradient vector
150    fn grad(
151        &self,
152        x: &Col<f64>,
153        operand_indices: &[[usize; N]],
154        args: &Self::EvalArgs,
155    ) -> Col<f64> {
156        let mut res = Col::zeros(x.nrows());
157
158        operand_indices.iter().for_each(|&ind| {
159            let obj = self.evaluate_for_indices(ind, x, args);
160            ind.into_iter()
161                .enumerate()
162                .for_each(|(ilocal, iglobal)| res[iglobal] += obj.grad[ilocal]);
163        });
164
165        res
166    }
167
168    /// Computes Hessian matrix entries as triplets
169    ///
170    /// ## Arguments
171    /// - `x`: The full variable vector
172    /// - `operand_indices`: Slice of indices of variables to evaluate
173    /// - `args`: Additional evaluation arguments
174    ///
175    /// ## Returns
176    /// Vector of (row, col, value) triplets representing the Hessian matrix
177    fn hess_trips(
178        &self,
179        x: &Col<f64>,
180        operand_indices: &[[usize; N]],
181        args: &Self::EvalArgs,
182    ) -> Vec<(usize, usize, f64)> {
183        let mut trips = Vec::new();
184
185        operand_indices.iter().for_each(|&ind| {
186            let obj = self.evaluate_for_indices(ind, x, args);
187            let ind = ind.into_iter().enumerate();
188
189            ind.clone().cartesian_product(ind).for_each(
190                |((ixlocal, ixglobal), (iylocal, iyglobal))| {
191                    trips.push((ixglobal, iyglobal, obj.hess[(ixlocal, iylocal)]));
192                },
193            );
194        });
195
196        trips
197    }
198
199    /// Computes the Hessian matrix as a sparse matrix
200    ///
201    /// ## Arguments
202    /// - `x`: The full variable vector
203    /// - `operand_indices`: Slice of indices of variables to evaluate
204    /// - `args`: Additional evaluation arguments
205    ///
206    /// ## Returns
207    /// A sparse matrix representation of the Hessian
208    fn hess(
209        &self,
210        x: &Col<f64>,
211        operand_indices: &[[usize; N]],
212        args: &Self::EvalArgs,
213    ) -> Result<SparseColMat<usize, f64>, CreationError> {
214        let n = x.nrows();
215        SparseColMat::try_new_from_triplets(n, n, &self.hess_trips(x, operand_indices, args))
216    }
217}