finitediff/vec/
jacobian.rs

1// Copyright 2018-2024 argmin developers
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use std::ops::AddAssign;
9
10use anyhow::Error;
11use num::{Float, FromPrimitive};
12
13use crate::pert::PerturbationVectors;
14use crate::utils::mod_and_calc;
15
16use super::OpFn;
17
18pub fn forward_jacobian_vec<F>(x: &Vec<F>, fs: OpFn<'_, F>) -> Result<Vec<Vec<F>>, Error>
19where
20    F: Float + FromPrimitive,
21{
22    let fx = (fs)(x)?;
23    let mut xt = x.clone();
24    let eps_sqrt = F::epsilon().sqrt();
25    let mut out: Vec<Vec<F>> = vec![vec![F::from_f64(0.0).unwrap(); x.len()]; fx.len()];
26    for j in 0..x.len() {
27        let fx1 = mod_and_calc(&mut xt, fs, j, eps_sqrt)?;
28        for i in 0..fx.len() {
29            out[i][j] = (fx1[i] - fx[i]) / eps_sqrt;
30        }
31    }
32    Ok(out)
33}
34
35pub fn central_jacobian_vec<F>(x: &[F], fs: OpFn<'_, F>) -> Result<Vec<Vec<F>>, Error>
36where
37    F: Float + FromPrimitive,
38{
39    let mut xt = x.to_owned();
40    let eps_cbrt = F::epsilon().cbrt();
41
42    let comp = |(a, b): (&F, &F)| (*a - *b) / (F::from_f64(2.0).unwrap() * eps_cbrt);
43
44    // We need to compute first iteration here, in order to know which dimension the output
45    // of `fs` has.
46    let fx1 = mod_and_calc(&mut xt, fs, 0, eps_cbrt)?;
47    let fx2 = mod_and_calc(&mut xt, fs, 0, -eps_cbrt)?;
48    let t0 = fx1.iter().zip(fx2.iter()).map(comp).collect::<Vec<F>>();
49
50    // Now we can create the actual Jacobian
51    let mut out: Vec<Vec<F>> = vec![vec![F::from_f64(0.0).unwrap(); x.len()]; fx1.len()];
52
53    // Fill in first column
54    for i in 0..t0.len() {
55        out[i][0] = t0[i];
56    }
57
58    // Fill in all the other columns
59    for j in 1..x.len() {
60        let fx1 = mod_and_calc(&mut xt, fs, j, eps_cbrt)?;
61        let fx2 = mod_and_calc(&mut xt, fs, j, -eps_cbrt)?;
62        for i in 0..fx1.len() {
63            out[i][j] = comp((&fx1[i], &fx2[i]));
64        }
65    }
66    Ok(out)
67}
68
69pub fn forward_jacobian_vec_prod_vec<F>(
70    x: &Vec<F>,
71    fs: OpFn<'_, F>,
72    p: &[F],
73) -> Result<Vec<F>, Error>
74where
75    F: Float,
76{
77    let fx = (fs)(x)?;
78    let eps_sqrt = F::epsilon().sqrt();
79    let x1 = x
80        .iter()
81        .zip(p.iter())
82        .map(|(&xi, &pi)| xi + eps_sqrt * pi)
83        .collect();
84    let fx1 = (fs)(&x1)?;
85    fx1.iter()
86        .zip(fx.iter())
87        .map(|(&a, &b)| Ok((a - b) / eps_sqrt))
88        .collect::<Result<Vec<F>, Error>>()
89}
90
91pub fn central_jacobian_vec_prod_vec<F>(x: &[F], fs: OpFn<'_, F>, p: &[F]) -> Result<Vec<F>, Error>
92where
93    F: Float + FromPrimitive,
94{
95    let eps_cbrt = F::epsilon().cbrt();
96    // TODO: Do this in a single vec
97    let x1 = x
98        .iter()
99        .zip(p.iter())
100        .map(|(&xi, &pi)| xi + eps_cbrt * pi)
101        .collect();
102    let x2 = x
103        .iter()
104        .zip(p.iter())
105        .map(|(&xi, &pi)| xi - eps_cbrt * pi)
106        .collect();
107    let fx1 = (fs)(&x1)?;
108    let fx2 = (fs)(&x2)?;
109    fx1.iter()
110        .zip(fx2.iter())
111        .map(|(&a, &b)| Ok((a - b) / (F::from_f64(2.0).unwrap() * eps_cbrt)))
112        .collect::<Result<Vec<F>, Error>>()
113}
114
115pub fn forward_jacobian_pert_vec<F>(
116    x: &Vec<F>,
117    fs: OpFn<'_, F>,
118    pert: &PerturbationVectors,
119) -> Result<Vec<Vec<F>>, Error>
120where
121    F: Float + FromPrimitive + AddAssign,
122{
123    let fx = (fs)(x)?;
124    let eps_sqrt = F::epsilon().sqrt();
125    let mut xt = x.clone();
126    let mut out = vec![vec![F::from_f64(0.0).unwrap(); x.len()]; fx.len()];
127    for pert_item in pert.iter() {
128        for i in pert_item.x_idx.iter() {
129            xt[*i] += eps_sqrt;
130        }
131
132        let fx1 = (fs)(&xt)?;
133
134        for i in pert_item.x_idx.iter() {
135            xt[*i] = x[*i];
136        }
137
138        for (k, x_idx) in pert_item.x_idx.iter().enumerate() {
139            for i in pert_item.r_idx[k].iter() {
140                out[*i][*x_idx] = (fx1[*i] - fx[*i]) / eps_sqrt;
141            }
142        }
143    }
144    Ok(out)
145}
146
147pub fn central_jacobian_pert_vec<F>(
148    x: &[F],
149    fs: OpFn<'_, F>,
150    pert: &PerturbationVectors,
151) -> Result<Vec<Vec<F>>, Error>
152where
153    F: Float + FromPrimitive + AddAssign,
154{
155    let mut out = vec![];
156    let eps_cbrt = F::epsilon().cbrt();
157    let mut xt = x.to_owned();
158    for (i, pert_item) in pert.iter().enumerate() {
159        for j in pert_item.x_idx.iter() {
160            xt[*j] += eps_cbrt;
161        }
162
163        let fx1 = (fs)(&xt)?;
164
165        for j in pert_item.x_idx.iter() {
166            xt[*j] = x[*j] - eps_cbrt;
167        }
168
169        let fx2 = (fs)(&xt)?;
170
171        for j in pert_item.x_idx.iter() {
172            xt[*j] = x[*j];
173        }
174
175        // TODO: Move this out of loop (probably compute iteration 0 prior to rest of loop)
176        if i == 0 {
177            out = vec![vec![F::from_f64(0.0).unwrap(); x.len()]; fx1.len()];
178        }
179
180        for (k, x_idx) in pert_item.x_idx.iter().enumerate() {
181            for j in pert_item.r_idx[k].iter() {
182                out[*j][*x_idx] = (fx1[*j] - fx2[*j]) / (F::from_f64(2.0).unwrap() * eps_cbrt);
183            }
184        }
185    }
186    Ok(out)
187}
188
189#[cfg(test)]
190mod tests {
191    use crate::PerturbationVector;
192
193    use super::*;
194
195    const COMP_ACC: f64 = 1e-6;
196
197    fn f(x: &Vec<f64>) -> Result<Vec<f64>, Error> {
198        Ok(vec![
199            2.0 * (x[1].powi(3) - x[0].powi(2)),
200            3.0 * (x[1].powi(3) - x[0].powi(2)) + 2.0 * (x[2].powi(3) - x[1].powi(2)),
201            3.0 * (x[2].powi(3) - x[1].powi(2)) + 2.0 * (x[3].powi(3) - x[2].powi(2)),
202            3.0 * (x[3].powi(3) - x[2].powi(2)) + 2.0 * (x[4].powi(3) - x[3].powi(2)),
203            3.0 * (x[4].powi(3) - x[3].powi(2)) + 2.0 * (x[5].powi(3) - x[4].powi(2)),
204            3.0 * (x[5].powi(3) - x[4].powi(2)),
205        ])
206    }
207
208    fn res1() -> Vec<Vec<f64>> {
209        vec![
210            vec![-4.0, 6.0, 0.0, 0.0, 0.0, 0.0],
211            vec![-6.0, 5.0, 6.0, 0.0, 0.0, 0.0],
212            vec![0.0, -6.0, 5.0, 6.0, 0.0, 0.0],
213            vec![0.0, 0.0, -6.0, 5.0, 6.0, 0.0],
214            vec![0.0, 0.0, 0.0, -6.0, 5.0, 6.0],
215            vec![0.0, 0.0, 0.0, 0.0, -6.0, 9.0],
216        ]
217    }
218
219    fn res2() -> Vec<f64> {
220        vec![8.0, 22.0, 27.0, 32.0, 37.0, 24.0]
221    }
222
223    fn x() -> Vec<f64> {
224        vec![1.0f64, 1.0, 1.0, 1.0, 1.0, 1.0]
225    }
226
227    fn p() -> Vec<f64> {
228        vec![1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0]
229    }
230
231    fn pert() -> PerturbationVectors {
232        vec![
233            PerturbationVector::new()
234                .add(0, vec![0, 1])
235                .add(3, vec![2, 3, 4]),
236            PerturbationVector::new()
237                .add(1, vec![0, 1, 2])
238                .add(4, vec![3, 4, 5]),
239            PerturbationVector::new()
240                .add(2, vec![1, 2, 3])
241                .add(5, vec![4, 5]),
242        ]
243    }
244
245    #[test]
246    fn test_forward_jacobian_vec_f64() {
247        let jacobian = forward_jacobian_vec(&x(), &f).unwrap();
248        let res = res1();
249        // println!("{:?}", jacobian);
250        for i in 0..6 {
251            for j in 0..6 {
252                assert!((res[i][j] - jacobian[i][j]).abs() < COMP_ACC)
253            }
254        }
255    }
256
257    #[test]
258    fn test_central_jacobian_vec_f64() {
259        let jacobian = central_jacobian_vec(&x(), &f).unwrap();
260        let res = res1();
261        // println!("{:?}", jacobian);
262        for i in 0..6 {
263            for j in 0..6 {
264                assert!((res[i][j] - jacobian[i][j]).abs() < COMP_ACC);
265            }
266        }
267    }
268
269    #[test]
270    fn test_forward_jacobian_vec_prod_vec_f64() {
271        let jacobian = forward_jacobian_vec_prod_vec(&x(), &f, &p()).unwrap();
272        let res = res2();
273        // println!("{:?}", jacobian);
274        // the accuracy for this is pretty bad!!
275        for i in 0..6 {
276            assert!((res[i] - jacobian[i]).abs() < 11.0 * COMP_ACC)
277        }
278    }
279
280    #[test]
281    fn test_central_jacobian_vec_prod_vec_f64() {
282        let jacobian = central_jacobian_vec_prod_vec(&x(), &f, &p()).unwrap();
283        let res = res2();
284        // println!("{:?}", jacobian);
285        for i in 0..6 {
286            assert!((res[i] - jacobian[i]).abs() < COMP_ACC)
287        }
288    }
289
290    #[test]
291    fn test_forward_jacobian_pert_vec_f64() {
292        let jacobian = forward_jacobian_pert_vec(&x(), &f, &pert()).unwrap();
293        let res = res1();
294        // println!("jacobian:\n{:?}", jacobian);
295        // println!("res:\n{:?}", res);
296        for i in 0..6 {
297            for j in 0..6 {
298                assert!((res[i][j] - jacobian[i][j]).abs() < COMP_ACC)
299            }
300        }
301    }
302
303    #[test]
304    fn test_central_jacobian_pert_vec_f64() {
305        let jacobian = central_jacobian_pert_vec(&x(), &f, &pert()).unwrap();
306        let res = res1();
307        // println!("jacobian:\n{:?}", jacobian);
308        // println!("res:\n{:?}", res);
309        for i in 0..6 {
310            for j in 0..6 {
311                assert!((res[i][j] - jacobian[i][j]).abs() < COMP_ACC)
312            }
313        }
314    }
315}