qualia_core_db/specialized_libs/
multivar_calculus.rs1use std::collections::HashMap;
17
18use super::symbolic_algebra::{differentiate, simplify, Expr};
19
20pub fn partial(expr: &Expr, var: &str) -> Expr {
22 simplify(&differentiate(expr, var))
23}
24
25pub fn gradient(expr: &Expr, vars: &[&str]) -> Vec<Expr> {
27 vars.iter().map(|v| partial(expr, v)).collect()
28}
29
30pub fn jacobian(exprs: &[Expr], vars: &[&str]) -> Vec<Vec<Expr>> {
33 exprs.iter().map(|f| gradient(f, vars)).collect()
34}
35
36pub fn hessian(expr: &Expr, vars: &[&str]) -> Vec<Vec<Expr>> {
40 let grad = gradient(expr, vars);
41 grad.iter().map(|gi| gradient(gi, vars)).collect()
42}
43
44pub fn gradient_at(expr: &Expr, vars: &[&str], point: &HashMap<String, f64>) -> Option<Vec<f64>> {
47 gradient(expr, vars).iter().map(|g| g.eval(point)).collect()
48}
49
50pub fn hessian_at(
52 expr: &Expr,
53 vars: &[&str],
54 point: &HashMap<String, f64>,
55) -> Option<Vec<Vec<f64>>> {
56 hessian(expr, vars)
57 .iter()
58 .map(|row| {
59 row.iter()
60 .map(|h| h.eval(point))
61 .collect::<Option<Vec<f64>>>()
62 })
63 .collect()
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::specialized_libs::symbolic_algebra::{add, c, mul, pow, var};
70
71 fn env(pairs: &[(&str, f64)]) -> HashMap<String, f64> {
72 pairs.iter().map(|&(k, v)| (k.to_string(), v)).collect()
73 }
74
75 #[test]
76 fn gradient_of_a_quadratic_form() {
77 let f = add(
79 add(pow(var("x"), 2), mul(var("x"), var("y"))),
80 pow(var("y"), 2),
81 );
82 let g = gradient(&f, &["x", "y"]);
83 let p = env(&[("x", 3.0), ("y", 5.0)]);
84 assert!((g[0].eval(&p).unwrap() - 11.0).abs() < 1e-9);
86 assert!((g[1].eval(&p).unwrap() - 13.0).abs() < 1e-9);
87 }
88
89 #[test]
90 fn hessian_of_a_quadratic_is_constant() {
91 let f = add(
93 add(pow(var("x"), 2), mul(var("x"), var("y"))),
94 pow(var("y"), 2),
95 );
96 let h = hessian_at(&f, &["x", "y"], &env(&[("x", 0.0), ("y", 0.0)])).unwrap();
97 assert!((h[0][0] - 2.0).abs() < 1e-9);
98 assert!((h[0][1] - 1.0).abs() < 1e-9);
99 assert!((h[1][0] - 1.0).abs() < 1e-9); assert!((h[1][1] - 2.0).abs() < 1e-9);
101 }
102
103 #[test]
104 fn jacobian_shape_and_values() {
105 let f1 = mul(var("x"), var("y"));
107 let f2 = add(var("x"), var("y"));
108 let j = jacobian(&[f1, f2], &["x", "y"]);
109 let p = env(&[("x", 2.0), ("y", 7.0)]);
110 assert_eq!(j.len(), 2);
111 assert!((j[0][0].eval(&p).unwrap() - 7.0).abs() < 1e-9); assert!((j[0][1].eval(&p).unwrap() - 2.0).abs() < 1e-9); assert!((j[1][0].eval(&p).unwrap() - 1.0).abs() < 1e-9);
114 assert!((j[1][1].eval(&p).unwrap() - 1.0).abs() < 1e-9);
115 }
116
117 #[test]
118 fn gradient_at_evaluates_numerically() {
119 let f = mul(c(3.0), pow(var("x"), 2));
121 let g = gradient_at(&f, &["x"], &env(&[("x", 4.0)])).unwrap();
122 assert!((g[0] - 24.0).abs() < 1e-9);
123 }
124}