mathhook_core/calculus/derivatives/partial/
jacobian.rs

1//! Jacobian matrix operations for vector-valued functions
2
3use super::{gradient::GradientOperations, utils::MatrixUtils};
4use crate::calculus::derivatives::Derivative;
5use crate::core::{Expression, Symbol};
6use crate::simplify::Simplify;
7use std::collections::HashMap;
8
9/// Jacobian matrix operations
10pub struct JacobianOperations;
11
12impl JacobianOperations {
13    /// Compute Jacobian matrix for vector-valued function F: ℝⁿ → ℝᵐ
14    ///
15    /// # Examples
16    ///
17    /// ```rust
18    /// use mathhook_core::simplify::Simplify;
19    /// use mathhook_core::calculus::derivatives::Derivative;
20    /// use mathhook_core::calculus::derivatives::PartialUtils;
21    /// use mathhook_core::calculus::derivatives::MatrixUtils;
22    /// use mathhook_core::{Expression};
23    /// use mathhook_core::symbol;
24    /// use mathhook_core::calculus::derivatives::JacobianOperations;
25    ///
26    /// let x = symbol!(x);
27    /// let y = symbol!(y);
28    /// let functions = vec![
29    ///     Expression::mul(vec![Expression::symbol(x.clone()), Expression::symbol(y.clone())]),
30    ///     Expression::add(vec![Expression::symbol(x.clone()), Expression::symbol(y.clone())])
31    /// ];
32    /// let variables = vec![x, y];
33    /// let jacobian = JacobianOperations::compute(&functions, &variables);
34    /// ```
35    pub fn compute(functions: &[Expression], variables: &[Symbol]) -> Vec<Vec<Expression>> {
36        let m = functions.len();
37        let mut jacobian = Vec::with_capacity(m);
38
39        for function in functions {
40            let gradient = GradientOperations::compute(function, variables.to_vec());
41            jacobian.push(gradient);
42        }
43
44        jacobian
45    }
46
47    /// Compute Jacobian matrix with caching for repeated variable sets
48    ///
49    /// # Examples
50    ///
51    /// ```rust
52    /// use mathhook_core::{Expression};
53    /// use mathhook_core::symbol;
54    /// use mathhook_core::calculus::derivatives::JacobianOperations;
55    /// use std::collections::HashMap;
56    ///
57    /// let x = symbol!(x);
58    /// let y = symbol!(y);
59    /// let functions = vec![
60    ///     Expression::symbol(x.clone()),
61    ///     Expression::symbol(y.clone())
62    /// ];
63    /// let variables = vec![x, y];
64    /// let mut cache = HashMap::new();
65    /// let jacobian = JacobianOperations::compute_cached(&functions, &variables, &mut cache);
66    /// ```
67    pub fn compute_cached(
68        functions: &[Expression],
69        variables: &[Symbol],
70        cache: &mut HashMap<String, Expression>,
71    ) -> Vec<Vec<Expression>> {
72        let m = functions.len();
73        let n = variables.len();
74        let mut jacobian = Vec::with_capacity(m);
75
76        for (func_idx, function) in functions.iter().enumerate() {
77            let mut gradient_row = Vec::with_capacity(n);
78
79            for (var_idx, var) in variables.iter().enumerate() {
80                let cache_key = format!("f{}_{}", func_idx, var_idx);
81                let partial = cache
82                    .entry(cache_key)
83                    .or_insert_with(|| function.derivative(var.clone()).simplify())
84                    .clone();
85                gradient_row.push(partial);
86            }
87
88            jacobian.push(gradient_row);
89        }
90
91        jacobian
92    }
93
94    /// Compute transpose of Jacobian matrix
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// use mathhook_core::{Expression};
100    /// use mathhook_core::symbol;
101    /// use mathhook_core::calculus::derivatives::JacobianOperations;
102    ///
103    /// let x = symbol!(x);
104    /// let y = symbol!(y);
105    /// let functions = vec![
106    ///     Expression::symbol(x.clone()),
107    ///     Expression::symbol(y.clone())
108    /// ];
109    /// let variables = vec![x, y];
110    /// let jacobian_t = JacobianOperations::transpose(&functions, &variables);
111    /// ```
112    pub fn transpose(functions: &[Expression], variables: &[Symbol]) -> Vec<Vec<Expression>> {
113        let jacobian = Self::compute(functions, variables);
114        Self::matrix_transpose(&jacobian)
115    }
116
117    /// Helper function to transpose a matrix
118    fn matrix_transpose(matrix: &[Vec<Expression>]) -> Vec<Vec<Expression>> {
119        if matrix.is_empty() || matrix[0].is_empty() {
120            return Vec::new();
121        }
122
123        let rows = matrix.len();
124        let cols = matrix[0].len();
125
126        (0..cols)
127            .map(|j| (0..rows).map(|i| matrix[i][j].clone()).collect())
128            .collect()
129    }
130}
131
132/// Jacobian determinant operations
133pub struct JacobianDeterminant;
134
135impl JacobianDeterminant {
136    /// Compute Jacobian determinant
137    ///
138    /// # Examples
139    ///
140    /// ```rust
141    /// use mathhook_core::{Expression};
142    /// use mathhook_core::symbol;
143    /// use mathhook_core::calculus::derivatives::JacobianDeterminant;
144    ///
145    /// let x = symbol!(x);
146    /// let y = symbol!(y);
147    /// let functions = vec![
148    ///     Expression::mul(vec![Expression::symbol(x.clone()), Expression::symbol(y.clone())]),
149    ///     Expression::add(vec![Expression::symbol(x.clone()), Expression::symbol(y.clone())])
150    /// ];
151    /// let variables = vec![x, y];
152    /// let jac_det = JacobianDeterminant::compute(&functions, &variables);
153    /// ```
154    pub fn compute(functions: &[Expression], variables: &[Symbol]) -> Expression {
155        if functions.len() != variables.len() {
156            panic!(
157                "Jacobian determinant requires square matrix: {} functions vs {} variables",
158                functions.len(),
159                variables.len()
160            );
161        }
162
163        let jacobian_matrix = JacobianOperations::compute(functions, variables);
164        MatrixUtils::determinant(&jacobian_matrix)
165    }
166
167    /// Compute absolute value of Jacobian determinant (for coordinate transformations)
168    ///
169    /// # Examples
170    ///
171    /// ```rust
172    /// use mathhook_core::{Expression, Symbol};
173    /// use mathhook_core::symbol;
174    /// use mathhook_core::calculus::derivatives::JacobianDeterminant;
175    ///
176    /// let r = symbol!(r);
177    /// let theta = symbol!(theta);
178    /// let functions = vec![
179    ///     Expression::mul(vec![
180    ///         Expression::symbol(r.clone()),
181    ///         Expression::function("cos", vec![Expression::symbol(theta.clone())])
182    ///     ]),
183    ///     Expression::mul(vec![
184    ///         Expression::symbol(r.clone()),
185    ///         Expression::function("sin", vec![Expression::symbol(theta.clone())])
186    ///     ])
187    /// ];
188    /// let variables = vec![r, theta];
189    /// let abs_jac_det = JacobianDeterminant::absolute(&functions, &variables);
190    /// ```
191    pub fn absolute(functions: &[Expression], variables: &[Symbol]) -> Expression {
192        let det = Self::compute(functions, variables);
193        Expression::function("abs", vec![det])
194    }
195
196    /// Check if Jacobian is singular (determinant = 0)
197    ///
198    /// # Examples
199    ///
200    /// ```rust
201    /// use mathhook_core::{Expression};
202    /// use mathhook_core::symbol;
203    /// use mathhook_core::calculus::derivatives::JacobianDeterminant;
204    ///
205    /// let x = symbol!(x);
206    /// let y = symbol!(y);
207    /// let functions = vec![
208    ///     Expression::symbol(x.clone()),
209    ///     Expression::symbol(x.clone())  // Linearly dependent
210    /// ];
211    /// let variables = vec![x, y];
212    /// let is_singular = JacobianDeterminant::is_singular(&functions, &variables);
213    /// ```
214    pub fn is_singular(functions: &[Expression], variables: &[Symbol]) -> bool {
215        let det = Self::compute(functions, variables);
216        super::utils::PartialUtils::is_zero(&det)
217    }
218
219    /// Compute condition number estimate (for numerical stability analysis)
220    ///
221    /// # Examples
222    ///
223    /// ```rust
224    /// use mathhook_core::{Expression};
225    /// use mathhook_core::symbol;
226    /// use mathhook_core::calculus::derivatives::JacobianDeterminant;
227    ///
228    /// let x = symbol!(x);
229    /// let y = symbol!(y);
230    /// let functions = vec![
231    ///     Expression::add(vec![Expression::symbol(x.clone()), Expression::symbol(y.clone())]),
232    ///     Expression::add(vec![
233    ///         Expression::symbol(x.clone()),
234    ///         Expression::mul(vec![
235    ///             Expression::mul(vec![
236    ///                 Expression::integer(1),
237    ///                 Expression::pow(Expression::integer(1000), Expression::integer(-1))
238    ///             ]),
239    ///             Expression::symbol(y.clone())
240    ///         ])
241    ///     ])
242    /// ];
243    /// let variables = vec![x, y];
244    /// let condition = JacobianDeterminant::condition_estimate(&functions, &variables);
245    /// ```
246    pub fn condition_estimate(functions: &[Expression], variables: &[Symbol]) -> Expression {
247        let det = Self::compute(functions, variables);
248        let abs_det = Expression::function("abs", vec![det]);
249
250        Expression::pow(abs_det, Expression::integer(-1))
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use crate::symbol;
258
259    #[test]
260    fn test_linear_transformation_jacobian() {
261        let x = symbol!(x);
262        let y = symbol!(y);
263
264        let functions = vec![
265            Expression::add(vec![
266                Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]),
267                Expression::mul(vec![Expression::integer(3), Expression::symbol(y.clone())]),
268            ]),
269            Expression::add(vec![
270                Expression::symbol(x.clone()),
271                Expression::mul(vec![Expression::integer(4), Expression::symbol(y.clone())]),
272            ]),
273        ];
274
275        let variables = vec![x.clone(), y.clone()];
276        let jacobian = JacobianOperations::compute(&functions, &variables);
277
278        assert_eq!(jacobian.len(), 2);
279        assert_eq!(jacobian[0].len(), 2);
280        assert_eq!(jacobian[1].len(), 2);
281
282        assert_eq!(jacobian[0][0].simplify(), Expression::integer(2));
283        assert_eq!(jacobian[0][1].simplify(), Expression::integer(3));
284        assert_eq!(jacobian[1][0].simplify(), Expression::integer(1));
285        assert_eq!(jacobian[1][1].simplify(), Expression::integer(4));
286    }
287
288    #[test]
289    fn test_nonlinear_transformation_jacobian() {
290        let x = symbol!(x);
291        let y = symbol!(y);
292
293        let functions = vec![
294            Expression::pow(Expression::symbol(x.clone()), Expression::integer(2)),
295            Expression::mul(vec![
296                Expression::symbol(x.clone()),
297                Expression::symbol(y.clone()),
298            ]),
299        ];
300
301        let variables = vec![x.clone(), y.clone()];
302        let jacobian = JacobianOperations::compute(&functions, &variables);
303
304        assert_eq!(jacobian.len(), 2);
305        assert_eq!(jacobian[0].len(), 2);
306        assert_eq!(jacobian[1].len(), 2);
307
308        assert!(!jacobian[0][0].is_zero());
309        assert_eq!(jacobian[0][1].simplify(), Expression::integer(0));
310        assert!(!jacobian[1][0].is_zero());
311        assert!(!jacobian[1][1].is_zero());
312    }
313
314    #[test]
315    fn test_polar_to_cartesian_jacobian() {
316        let r = symbol!(r);
317        let theta = symbol!(theta);
318
319        let functions = vec![
320            Expression::mul(vec![
321                Expression::symbol(r.clone()),
322                Expression::function("cos", vec![Expression::symbol(theta.clone())]),
323            ]),
324            Expression::mul(vec![
325                Expression::symbol(r.clone()),
326                Expression::function("sin", vec![Expression::symbol(theta.clone())]),
327            ]),
328        ];
329
330        let variables = vec![r.clone(), theta.clone()];
331        let jacobian = JacobianOperations::compute(&functions, &variables);
332
333        assert_eq!(jacobian.len(), 2);
334        assert_eq!(jacobian[0].len(), 2);
335        assert_eq!(jacobian[1].len(), 2);
336
337        assert!(!jacobian[0][0].is_zero());
338        assert!(!jacobian[0][1].is_zero());
339        assert!(!jacobian[1][0].is_zero());
340        assert!(!jacobian[1][1].is_zero());
341    }
342
343    #[test]
344    fn test_single_variable_jacobian() {
345        let x = symbol!(x);
346
347        let functions = vec![Expression::pow(
348            Expression::symbol(x.clone()),
349            Expression::integer(2),
350        )];
351
352        let variables = vec![x.clone()];
353        let jacobian = JacobianOperations::compute(&functions, &variables);
354
355        assert_eq!(jacobian.len(), 1);
356        assert_eq!(jacobian[0].len(), 1);
357        assert!(!jacobian[0][0].is_zero());
358    }
359
360    #[test]
361    fn test_three_variable_jacobian() {
362        let x = symbol!(x);
363        let y = symbol!(y);
364        let z = symbol!(z);
365
366        let functions = vec![
367            Expression::add(vec![
368                Expression::symbol(x.clone()),
369                Expression::symbol(y.clone()),
370                Expression::symbol(z.clone()),
371            ]),
372            Expression::mul(vec![
373                Expression::symbol(x.clone()),
374                Expression::symbol(y.clone()),
375            ]),
376            Expression::pow(Expression::symbol(z.clone()), Expression::integer(2)),
377        ];
378
379        let variables = vec![x.clone(), y.clone(), z.clone()];
380        let jacobian = JacobianOperations::compute(&functions, &variables);
381
382        assert_eq!(jacobian.len(), 3);
383        for row in &jacobian {
384            assert_eq!(row.len(), 3);
385        }
386
387        assert_eq!(jacobian[0][0].simplify(), Expression::integer(1));
388        assert_eq!(jacobian[0][1].simplify(), Expression::integer(1));
389        assert_eq!(jacobian[0][2].simplify(), Expression::integer(1));
390
391        assert!(!jacobian[1][0].is_zero());
392        assert!(!jacobian[1][1].is_zero());
393        assert_eq!(jacobian[1][2].simplify(), Expression::integer(0));
394
395        assert_eq!(jacobian[2][0].simplify(), Expression::integer(0));
396        assert_eq!(jacobian[2][1].simplify(), Expression::integer(0));
397        assert!(!jacobian[2][2].is_zero());
398    }
399
400    #[test]
401    fn test_jacobian_caching() {
402        let x = symbol!(x);
403        let y = symbol!(y);
404
405        let functions = vec![
406            Expression::function("sin", vec![Expression::symbol(x.clone())]),
407            Expression::function("cos", vec![Expression::symbol(y.clone())]),
408        ];
409
410        let variables = vec![x.clone(), y.clone()];
411        let mut cache = HashMap::new();
412
413        let jacobian1 = JacobianOperations::compute_cached(&functions, &variables, &mut cache);
414        let jacobian2 = JacobianOperations::compute_cached(&functions, &variables, &mut cache);
415
416        assert_eq!(jacobian1.len(), 2);
417        assert_eq!(jacobian2.len(), 2);
418        assert_eq!(jacobian1[0][0], jacobian2[0][0]);
419        assert_eq!(jacobian1[1][1], jacobian2[1][1]);
420        assert_eq!(cache.len(), 4);
421    }
422
423    #[test]
424    fn test_jacobian_transpose() {
425        let x = symbol!(x);
426        let y = symbol!(y);
427
428        let functions = vec![
429            Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]),
430            Expression::mul(vec![Expression::integer(3), Expression::symbol(y.clone())]),
431        ];
432
433        let variables = vec![x.clone(), y.clone()];
434        let jacobian = JacobianOperations::compute(&functions, &variables);
435        let jacobian_t = JacobianOperations::transpose(&functions, &variables);
436
437        assert_eq!(jacobian.len(), 2);
438        assert_eq!(jacobian_t.len(), 2);
439        assert_eq!(jacobian[0][1], jacobian_t[1][0]);
440        assert_eq!(jacobian[1][0], jacobian_t[0][1]);
441    }
442
443    #[test]
444    fn test_jacobian_determinant_2x2() {
445        let x = symbol!(x);
446        let y = symbol!(y);
447
448        let functions = vec![
449            Expression::add(vec![
450                Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]),
451                Expression::symbol(y.clone()),
452            ]),
453            Expression::add(vec![
454                Expression::symbol(x.clone()),
455                Expression::mul(vec![Expression::integer(3), Expression::symbol(y.clone())]),
456            ]),
457        ];
458
459        let variables = vec![x.clone(), y.clone()];
460        let det = JacobianDeterminant::compute(&functions, &variables);
461
462        assert!(!det.is_zero());
463    }
464
465    #[test]
466    fn test_identity_transformation_jacobian() {
467        let x = symbol!(x);
468        let y = symbol!(y);
469
470        let functions = vec![Expression::symbol(x.clone()), Expression::symbol(y.clone())];
471
472        let variables = vec![x.clone(), y.clone()];
473        let jacobian = JacobianOperations::compute(&functions, &variables);
474
475        assert_eq!(jacobian[0][0].simplify(), Expression::integer(1));
476        assert_eq!(jacobian[0][1].simplify(), Expression::integer(0));
477        assert_eq!(jacobian[1][0].simplify(), Expression::integer(0));
478        assert_eq!(jacobian[1][1].simplify(), Expression::integer(1));
479
480        let det = JacobianDeterminant::compute(&functions, &variables);
481        assert_eq!(det.simplify(), Expression::integer(1));
482    }
483
484    #[test]
485    fn test_constant_functions_jacobian() {
486        let x = symbol!(x);
487        let y = symbol!(y);
488
489        let functions = vec![Expression::integer(5), Expression::integer(10)];
490
491        let variables = vec![x.clone(), y.clone()];
492        let jacobian = JacobianOperations::compute(&functions, &variables);
493
494        for row in &jacobian {
495            for elem in row {
496                assert_eq!(elem.simplify(), Expression::integer(0));
497            }
498        }
499
500        let det = JacobianDeterminant::compute(&functions, &variables);
501        assert_eq!(det.simplify(), Expression::integer(0));
502    }
503
504    #[test]
505    #[should_panic(expected = "Jacobian determinant requires square matrix")]
506    fn test_non_square_jacobian_determinant() {
507        let x = symbol!(x);
508        let y = symbol!(y);
509
510        let functions = vec![Expression::symbol(x.clone())];
511
512        let variables = vec![x, y];
513        JacobianDeterminant::compute(&functions, &variables);
514    }
515
516    #[test]
517    fn test_singular_jacobian() {
518        let x = symbol!(x);
519        let y = symbol!(y);
520
521        let functions = vec![Expression::symbol(x.clone()), Expression::symbol(x.clone())];
522
523        let variables = vec![x.clone(), y.clone()];
524        let is_singular = JacobianDeterminant::is_singular(&functions, &variables);
525
526        assert!(is_singular);
527    }
528
529    #[test]
530    fn test_jacobian_absolute_determinant() {
531        let x = symbol!(x);
532        let y = symbol!(y);
533
534        let functions = vec![
535            Expression::mul(vec![Expression::integer(-1), Expression::symbol(x.clone())]),
536            Expression::symbol(y.clone()),
537        ];
538
539        let variables = vec![x.clone(), y.clone()];
540        let abs_det = JacobianDeterminant::absolute(&functions, &variables);
541
542        assert!(!abs_det.is_zero());
543    }
544
545    #[test]
546    fn test_empty_jacobian() {
547        let functions: Vec<Expression> = vec![];
548        let variables: Vec<Symbol> = vec![];
549
550        let jacobian = JacobianOperations::compute(&functions, &variables);
551        assert_eq!(jacobian.len(), 0);
552    }
553
554    #[test]
555    fn test_rectangular_jacobian() {
556        let x = symbol!(x);
557        let y = symbol!(y);
558
559        let functions = vec![
560            Expression::symbol(x.clone()),
561            Expression::symbol(y.clone()),
562            Expression::add(vec![
563                Expression::symbol(x.clone()),
564                Expression::symbol(y.clone()),
565            ]),
566        ];
567
568        let variables = vec![x.clone(), y.clone()];
569        let jacobian = JacobianOperations::compute(&functions, &variables);
570
571        assert_eq!(jacobian.len(), 3);
572        for row in &jacobian {
573            assert_eq!(row.len(), 2);
574        }
575    }
576
577    #[test]
578    fn test_trigonometric_jacobian() {
579        let x = symbol!(x);
580        let y = symbol!(y);
581
582        let functions = vec![
583            Expression::function("sin", vec![Expression::symbol(x.clone())]),
584            Expression::function("cos", vec![Expression::symbol(y.clone())]),
585        ];
586
587        let variables = vec![x.clone(), y.clone()];
588        let jacobian = JacobianOperations::compute(&functions, &variables);
589
590        assert_eq!(jacobian.len(), 2);
591        assert!(!jacobian[0][0].is_zero());
592        assert_eq!(jacobian[0][1].simplify(), Expression::integer(0));
593        assert_eq!(jacobian[1][0].simplify(), Expression::integer(0));
594        assert!(!jacobian[1][1].is_zero());
595    }
596
597    #[test]
598    fn test_condition_number_estimate() {
599        let x = symbol!(x);
600        let y = symbol!(y);
601
602        let functions = vec![Expression::symbol(x.clone()), Expression::symbol(y.clone())];
603
604        let variables = vec![x.clone(), y.clone()];
605        let condition = JacobianDeterminant::condition_estimate(&functions, &variables);
606
607        assert!(!condition.is_zero());
608    }
609}