apex_solver/core/
factors.rs

1//! Factor implementations for graph-based optimization problems.
2//!
3//! Factors (also called constraints or error functions) represent measurements or relationships
4//! between variables in a factor graph. Each factor computes a residual (error) vector and its
5//! Jacobian with respect to the connected variables.
6//!
7//! # Factor Graph Formulation
8//!
9//! In graph-based SLAM and bundle adjustment, the optimization problem is represented as:
10//!
11//! ```text
12//! minimize Σ_i ||r_i(x)||²
13//! ```
14//!
15//! where:
16//! - `x` is the set of variables (poses, landmarks, etc.)
17//! - `r_i(x)` is the residual function for factor i
18//! - Each factor connects one or more variables
19//!
20//! # Factor Types
21//!
22//! - **Unary factors**: Connect to a single variable (e.g., [`PriorFactor`])
23//! - **Binary factors**: Connect to two variables (e.g., [`BetweenFactorSE2`], [`BetweenFactorSE3`])
24//! - **N-ary factors**: Connect to N variables (not yet implemented)
25//!
26//! # Linearization
27//!
28//! Each factor must provide a `linearize` method that computes:
29//! 1. **Residual** `r(x)`: The error at the current variable values
30//! 2. **Jacobian** `J = ∂r/∂x`: How the residual changes with each variable
31//!
32//! This information is used by the optimizer to compute parameter updates via Newton-type methods.
33//!
34//! # Example: Creating and Using a Factor
35//!
36//! ```
37//! use apex_solver::core::factors::{Factor, BetweenFactorSE2};
38//! use nalgebra::DVector;
39//!
40//! // Create a relative pose constraint between two SE2 poses
41//! let between_factor = BetweenFactorSE2::new(
42//!     1.0,  // dx: relative x translation
43//!     0.0,  // dy: relative y translation
44//!     0.1,  // dtheta: relative rotation
45//! );
46//!
47//! // Current variable values (two SE2 poses in [x, y, theta] format)
48//! let pose1 = DVector::from_vec(vec![0.0, 0.0, 0.0]);
49//! let pose2 = DVector::from_vec(vec![0.9, 0.1, 0.15]);
50//!
51//! // Linearize: compute residual and Jacobian
52//! let params = vec![pose1, pose2];
53//! let (residual, jacobian) = between_factor.linearize(&params, true);
54//!
55//! // residual: 3x1 vector showing how far pose2 deviates from expected
56//! // jacobian: 3x6 matrix showing derivatives w.r.t. both poses
57//! println!("Residual: {:?}", residual);
58//! if let Some(jac) = jacobian {
59//!     println!("Jacobian shape: {} x {}", jac.nrows(), jac.ncols());
60//! }
61//! ```
62
63use crate::manifold::{LieGroup, se2::SE2, se3::SE3};
64use nalgebra;
65
66/// Trait for factor (constraint) implementations in factor graph optimization.
67///
68/// A factor represents a measurement or constraint connecting one or more variables.
69/// It computes the residual (error) and Jacobian for the current variable values,
70/// which are used by the optimizer to minimize the total cost.
71///
72/// # Implementing Custom Factors
73///
74/// To create a custom factor:
75/// 1. Implement this trait
76/// 2. Define the residual function `r(x)` (how to compute error from variable values)
77/// 3. Compute the Jacobian `J = ∂r/∂x` (analytically or numerically)
78/// 4. Return the residual dimension
79///
80/// # Thread Safety
81///
82/// Factors must be `Send + Sync` to enable parallel residual/Jacobian evaluation.
83///
84/// # Example
85///
86/// ```
87/// use apex_solver::core::factors::Factor;
88/// use nalgebra::{DMatrix, DVector};
89///
90/// // Simple 1D range measurement factor
91/// struct RangeFactor {
92///     measurement: f64,  // Measured distance
93/// }
94///
95/// impl Factor for RangeFactor {
96///     fn linearize(&self, params: &[DVector<f64>], compute_jacobian: bool) -> (DVector<f64>, Option<DMatrix<f64>>) {
97///         // params[0] is a 2D point [x, y]
98///         let x = params[0][0];
99///         let y = params[0][1];
100///
101///         // Residual: measured distance - actual distance
102///         let predicted_distance = (x * x + y * y).sqrt();
103///         let residual = DVector::from_vec(vec![self.measurement - predicted_distance]);
104///
105///         // Jacobian: ∂(residual)/∂[x, y]
106///         let jacobian = if compute_jacobian {
107///             Some(DMatrix::from_row_slice(1, 2, &[
108///                 -x / predicted_distance,
109///                 -y / predicted_distance,
110///             ]))
111///         } else {
112///             None
113///         };
114///
115///         (residual, jacobian)
116///     }
117///
118///     fn get_dimension(&self) -> usize { 1 }
119/// }
120/// ```
121pub trait Factor: Send + Sync {
122    /// Compute the residual and Jacobian at the given parameter values.
123    ///
124    /// # Arguments
125    ///
126    /// * `params` - Slice of variable values (one `DVector` per connected variable)
127    ///
128    /// # Returns
129    ///
130    /// Tuple `(residual, jacobian)` where:
131    /// - `residual`: N-dimensional error vector
132    /// - `jacobian`: N × M matrix where M is the total DOF of all variables
133    ///
134    /// # Example
135    ///
136    /// For a between factor connecting two SE2 poses (3 DOF each):
137    /// - Input: `params = [pose1 (3×1), pose2 (3×1)]`
138    /// - Output: `(residual (3×1), jacobian (3×6))`
139    fn linearize(
140        &self,
141        params: &[nalgebra::DVector<f64>],
142        compute_jacobian: bool,
143    ) -> (nalgebra::DVector<f64>, Option<nalgebra::DMatrix<f64>>);
144
145    /// Get the dimension of the residual vector.
146    ///
147    /// # Returns
148    ///
149    /// Number of elements in the residual vector (number of constraints)
150    ///
151    /// # Example
152    ///
153    /// - SE2 between factor: 3 (dx, dy, dtheta)
154    /// - SE3 between factor: 6 (translation + rotation)
155    /// - Prior factor: dimension of the variable
156    fn get_dimension(&self) -> usize;
157}
158
159/// Between factor for SE(2) poses (2D pose graph constraint).
160///
161/// Represents a relative pose measurement between two SE(2) poses in 2D. This is the
162/// fundamental building block for 2D SLAM, pose graph optimization, and trajectory estimation.
163///
164/// # Mathematical Formulation
165///
166/// Given two poses `T_i` and `T_j` in SE(2), and a measurement `T_ij`, the residual is:
167///
168/// ```text
169/// r = log(T_ij⁻¹ ⊕ T_i⁻¹ ⊕ T_j)
170/// ```
171///
172/// where:
173/// - `⊕` is SE(2) composition
174/// - `log` is the SE(2) logarithm map (converts to tangent space)
175/// - The residual is a 3D vector `[dx, dy, dtheta]` in the tangent space
176///
177/// # Jacobian Computation
178///
179/// The Jacobian is computed analytically using the chain rule and Lie group derivatives:
180///
181/// ```text
182/// J = ∂r/∂[T_i, T_j]
183/// ```
184///
185/// This is a 3×6 matrix (3 residual dimensions, 6 DOF from two SE(2) poses).
186///
187/// # Use Cases
188///
189/// - 2D SLAM: Odometry measurements, loop closure constraints
190/// - Pose graph optimization: Relative pose constraints
191/// - 2D trajectory estimation
192///
193/// # Example
194///
195/// ```
196/// use apex_solver::core::factors::{Factor, BetweenFactorSE2};
197/// use nalgebra::DVector;
198///
199/// // Measurement: robot moved 1m forward and rotated 0.1 rad
200/// let between = BetweenFactorSE2::new(1.0, 0.0, 0.1);
201///
202/// // Current pose estimates
203/// let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);     // Origin
204/// let pose_j = DVector::from_vec(vec![0.95, 0.05, 0.12]);  // Slightly off
205///
206/// // Compute residual (should be small if poses are consistent)
207/// let (residual, jacobian) = between.linearize(&[pose_i, pose_j], true);
208/// println!("Residual: {:?}", residual);  // Shows deviation from measurement
209/// ```
210#[derive(Debug, Clone)]
211pub struct BetweenFactorSE2 {
212    /// The measured relative pose transformation between the two connected poses
213    pub relative_pose: SE2,
214}
215
216impl BetweenFactorSE2 {
217    /// Create a new SE(2) between factor from translation and rotation components.
218    ///
219    /// # Arguments
220    ///
221    /// * `dx` - Relative translation in x direction (meters)
222    /// * `dy` - Relative translation in y direction (meters)
223    /// * `dtheta` - Relative rotation angle (radians)
224    ///
225    /// # Returns
226    ///
227    /// A new `BetweenFactorSE2` instance
228    ///
229    /// # Example
230    ///
231    /// ```
232    /// use apex_solver::core::factors::BetweenFactorSE2;
233    ///
234    /// // Robot moved 2m forward, 0.5m left, rotated 0.1 rad counterclockwise
235    /// let factor = BetweenFactorSE2::new(2.0, 0.5, 0.1);
236    /// ```
237    pub fn new(dx: f64, dy: f64, dtheta: f64) -> Self {
238        let relative_pose = SE2::from_xy_angle(dx, dy, dtheta);
239        Self { relative_pose }
240    }
241
242    /// Create a new SE(2) between factor from an existing SE2 transformation.
243    ///
244    /// # Arguments
245    ///
246    /// * `relative_pose` - The measured relative pose
247    ///
248    /// # Returns
249    ///
250    /// A new `BetweenFactorSE2` instance
251    ///
252    /// # Example
253    ///
254    /// ```
255    /// use apex_solver::core::factors::BetweenFactorSE2;
256    /// use apex_solver::manifold::se2::SE2;
257    ///
258    /// let relative = SE2::from_xy_angle(1.0, 0.0, 0.0);
259    /// let factor = BetweenFactorSE2::from_se2(relative);
260    /// ```
261    pub fn from_se2(relative_pose: SE2) -> Self {
262        Self { relative_pose }
263    }
264}
265
266impl Factor for BetweenFactorSE2 {
267    /// Compute residual and Jacobian for SE(2) between factor.
268    ///
269    /// # Arguments
270    ///
271    /// * `params` - Two SE(2) poses in G2O format: `[x, y, theta]`
272    ///
273    /// # Returns
274    ///
275    /// - Residual: 3×1 vector `[dx_error, dy_error, dtheta_error]`
276    /// - Jacobian: 3×6 matrix `[∂r/∂pose_i, ∂r/∂pose_j]`
277    ///
278    /// # Algorithm
279    ///
280    /// Uses analytical Jacobians computed via chain rule through:
281    /// 1. Inverse: `T_i⁻¹`
282    /// 2. Composition: `T_i⁻¹ ⊕ T_j`
283    /// 3. Composition: `(T_i⁻¹ ⊕ T_j) ⊕ T_ij`
284    /// 4. Logarithm: `log(...)`
285    fn linearize(
286        &self,
287        params: &[nalgebra::DVector<f64>],
288        compute_jacobian: bool,
289    ) -> (nalgebra::DVector<f64>, Option<nalgebra::DMatrix<f64>>) {
290        // Use analytical jacobians for SE2 between factor (same pattern as SE3)
291        // Input: params = [x, y, theta] for each pose (G2O FORMAT)
292        let se2_origin_k0 = SE2::from(params[0].clone());
293        let se2_origin_k1 = SE2::from(params[1].clone());
294        let se2_k0_k1_measured = &self.relative_pose;
295
296        // Step 1: se2_origin_k1.inverse()
297        let mut j_k1_inv_wrt_k1 = nalgebra::Matrix3::zeros();
298        let se2_k1_inv = se2_origin_k1.inverse(Some(&mut j_k1_inv_wrt_k1));
299
300        // Step 2: se2_k1_inv * se2_origin_k0
301        let mut j_compose1_wrt_k1_inv = nalgebra::Matrix3::zeros();
302        let mut j_compose1_wrt_k0 = nalgebra::Matrix3::zeros();
303        let se2_temp = se2_k1_inv.compose(
304            &se2_origin_k0,
305            Some(&mut j_compose1_wrt_k1_inv),
306            Some(&mut j_compose1_wrt_k0),
307        );
308
309        // Step 3: se2_temp * se2_k0_k1_measured
310        let mut j_compose2_wrt_temp = nalgebra::Matrix3::zeros();
311        let se2_diff = se2_temp.compose(se2_k0_k1_measured, Some(&mut j_compose2_wrt_temp), None);
312
313        // Step 4: se2_diff.log()
314        let mut j_log_wrt_diff = nalgebra::Matrix3::zeros();
315        let residual = se2_diff.log(Some(&mut j_log_wrt_diff));
316
317        let jacobian = if compute_jacobian {
318            // Chain rule: d(residual)/d(k0) and d(residual)/d(k1)
319            let j_temp_wrt_k1 = j_compose1_wrt_k1_inv * j_k1_inv_wrt_k1;
320            let j_diff_wrt_k0 = j_compose2_wrt_temp * j_compose1_wrt_k0;
321            let j_diff_wrt_k1 = j_compose2_wrt_temp * j_temp_wrt_k1;
322
323            let jacobian_wrt_k0 = j_log_wrt_diff * j_diff_wrt_k0;
324            let jacobian_wrt_k1 = j_log_wrt_diff * j_diff_wrt_k1;
325
326            // Assemble full Jacobian: [∂r/∂pose_i | ∂r/∂pose_j]
327            let mut jacobian = nalgebra::DMatrix::<f64>::zeros(3, 6);
328            jacobian
329                .fixed_view_mut::<3, 3>(0, 0)
330                .copy_from(&jacobian_wrt_k0);
331            jacobian
332                .fixed_view_mut::<3, 3>(0, 3)
333                .copy_from(&jacobian_wrt_k1);
334
335            Some(jacobian)
336        } else {
337            None
338        };
339
340        (residual.into(), jacobian)
341    }
342
343    fn get_dimension(&self) -> usize {
344        3 // SE(2) between factor has 3D residual: [dx, dy, dtheta]
345    }
346}
347
348/// Between factor for SE(3) poses (3D pose graph constraint).
349///
350/// Represents a relative pose measurement between two SE(3) poses in 3D. This is the
351/// fundamental building block for 3D SLAM, structure from motion, and bundle adjustment.
352///
353/// # Mathematical Formulation
354///
355/// Given two poses `T_i` and `T_j` in SE(3), and a measurement `T_ij`, the residual is:
356///
357/// ```text
358/// r = log(T_ij⁻¹ ⊕ T_i⁻¹ ⊕ T_j)
359/// ```
360///
361/// where:
362/// - `⊕` is SE(3) composition
363/// - `log` is the SE(3) logarithm map (converts to tangent space se(3))
364/// - The residual is a 6D vector `[v_x, v_y, v_z, ω_x, ω_y, ω_z]` in the tangent space
365///
366/// # Tangent Space
367///
368/// The 6D tangent space se(3) consists of:
369/// - **Translation**: `[v_x, v_y, v_z]` - Linear velocity
370/// - **Rotation**: `[ω_x, ω_y, ω_z]` - Angular velocity (axis-angle)
371///
372/// # Jacobian Computation
373///
374/// The Jacobian is computed analytically using the chain rule and Lie group derivatives:
375///
376/// ```text
377/// J = ∂r/∂[T_i, T_j]
378/// ```
379///
380/// This is a 6×12 matrix (6 residual dimensions, 12 DOF from two SE(3) poses).
381///
382/// # Use Cases
383///
384/// - 3D SLAM: Visual odometry, loop closure constraints
385/// - Pose graph optimization: Relative pose constraints
386/// - Bundle adjustment: Camera pose relationships
387/// - Multi-view geometry: Relative camera poses
388///
389/// # Example
390///
391/// ```
392/// use apex_solver::core::factors::{Factor, BetweenFactorSE3};
393/// use apex_solver::manifold::se3::SE3;
394/// use nalgebra::{Vector3, Quaternion, DVector};
395///
396/// // Measurement: relative transformation between two poses
397/// let relative_pose = SE3::from_translation_quaternion(
398///     Vector3::new(1.0, 0.0, 0.0),        // 1m forward
399///     Quaternion::new(1.0, 0.0, 0.0, 0.0) // No rotation
400/// );
401/// let between = BetweenFactorSE3::new(relative_pose);
402///
403/// // Current pose estimates (in [tx, ty, tz, qw, qx, qy, qz] format)
404/// let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
405/// let pose_j = DVector::from_vec(vec![0.95, 0.05, 0.0, 1.0, 0.0, 0.0, 0.0]);
406///
407/// // Compute residual
408/// let (residual, jacobian) = between.linearize(&[pose_i, pose_j], true);
409/// println!("Residual dimension: {}", residual.len());  // 6
410/// if let Some(jac) = jacobian {
411///     println!("Jacobian shape: {} x {}", jac.nrows(), jac.ncols());  // 6x12
412/// }
413/// ```
414#[derive(Debug, Clone)]
415pub struct BetweenFactorSE3 {
416    /// The measured relative pose transformation between the two connected poses
417    pub relative_pose: SE3,
418}
419
420impl BetweenFactorSE3 {
421    /// Create a new SE(3) between factor from a relative pose measurement.
422    ///
423    /// # Arguments
424    ///
425    /// * `relative_pose` - The measured relative SE(3) transformation
426    ///
427    /// # Returns
428    ///
429    /// A new `BetweenFactorSE3` instance
430    ///
431    /// # Example
432    ///
433    /// ```
434    /// use apex_solver::core::factors::BetweenFactorSE3;
435    /// use apex_solver::manifold::se3::SE3;
436    ///
437    /// // Create relative pose: move 2m in x, rotate 90° around z-axis
438    /// let relative = SE3::from_translation_euler(
439    ///     2.0, 0.0, 0.0,                      // translation (x, y, z)
440    ///     0.0, 0.0, std::f64::consts::FRAC_PI_2  // rotation (roll, pitch, yaw)
441    /// );
442    ///
443    /// let factor = BetweenFactorSE3::new(relative);
444    /// ```
445    pub fn new(relative_pose: SE3) -> Self {
446        Self { relative_pose }
447    }
448}
449
450impl Factor for BetweenFactorSE3 {
451    /// Compute residual and Jacobian for SE(3) between factor.
452    ///
453    /// # Arguments
454    ///
455    /// * `params` - Two SE(3) poses in format: `[tx, ty, tz, qw, qx, qy, qz]`
456    ///
457    /// # Returns
458    ///
459    /// - Residual: 6×1 vector `[v_x, v_y, v_z, ω_x, ω_y, ω_z]` in tangent space
460    /// - Jacobian: 6×12 matrix `[∂r/∂pose_i, ∂r/∂pose_j]`
461    ///
462    /// # Algorithm
463    ///
464    /// Uses analytical Jacobians computed via chain rule through:
465    /// 1. Inverse: `T_i⁻¹`
466    /// 2. Composition: `T_i⁻¹ ⊕ T_j`
467    /// 3. Composition: `(T_i⁻¹ ⊕ T_j) ⊕ T_ij`
468    /// 4. Logarithm: `log(...)`
469    fn linearize(
470        &self,
471        params: &[nalgebra::DVector<f64>],
472        compute_jacobian: bool,
473    ) -> (nalgebra::DVector<f64>, Option<nalgebra::DMatrix<f64>>) {
474        let se3_origin_k0 = SE3::from(params[0].clone());
475        let se3_origin_k1 = SE3::from(params[1].clone());
476        let se3_k0_k1_measured = &self.relative_pose;
477
478        // Step 1: se3_origin_k1.inverse()
479        let mut j_k1_inv_wrt_k1 = nalgebra::Matrix6::zeros();
480        let se3_k1_inv = se3_origin_k1.inverse(Some(&mut j_k1_inv_wrt_k1));
481
482        // Step 2: se3_k1_inv * se3_origin_k0
483        let mut j_compose1_wrt_k1_inv = nalgebra::Matrix6::zeros();
484        let mut j_compose1_wrt_k0 = nalgebra::Matrix6::zeros();
485        let se3_temp = se3_k1_inv.compose(
486            &se3_origin_k0,
487            Some(&mut j_compose1_wrt_k1_inv),
488            Some(&mut j_compose1_wrt_k0),
489        );
490
491        // Step 3: se3_temp * se3_k0_k1_measured
492        let mut j_compose2_wrt_temp = nalgebra::Matrix6::zeros();
493        let se3_diff = se3_temp.compose(se3_k0_k1_measured, Some(&mut j_compose2_wrt_temp), None);
494
495        // Step 4: se3_diff.log()
496        let mut j_log_wrt_diff = nalgebra::Matrix6::zeros();
497        let residual = se3_diff.log(Some(&mut j_log_wrt_diff));
498
499        let jacobian = if compute_jacobian {
500            // Chain rule: d(residual)/d(k0) and d(residual)/d(k1)
501            let j_temp_wrt_k1 = j_compose1_wrt_k1_inv * j_k1_inv_wrt_k1;
502            let j_diff_wrt_k0 = j_compose2_wrt_temp * j_compose1_wrt_k0;
503            let j_diff_wrt_k1 = j_compose2_wrt_temp * j_temp_wrt_k1;
504
505            let jacobian_wrt_k0 = j_log_wrt_diff * j_diff_wrt_k0;
506            let jacobian_wrt_k1 = j_log_wrt_diff * j_diff_wrt_k1;
507
508            // Assemble full Jacobian: [∂r/∂pose_i | ∂r/∂pose_j]
509            let mut jacobian = nalgebra::DMatrix::<f64>::zeros(6, 12);
510            jacobian
511                .fixed_view_mut::<6, 6>(0, 0)
512                .copy_from(&jacobian_wrt_k0);
513            jacobian
514                .fixed_view_mut::<6, 6>(0, 6)
515                .copy_from(&jacobian_wrt_k1);
516
517            Some(jacobian)
518        } else {
519            None
520        };
521        (residual.into(), jacobian)
522    }
523
524    fn get_dimension(&self) -> usize {
525        6 // SE(3) between factor has 6D residual: [translation (3D), rotation (3D)]
526    }
527}
528
529/// Prior factor (unary constraint) on a single variable.
530///
531/// Represents a direct measurement or prior belief about a variable's value. This is used
532/// to anchor variables to known values or to incorporate prior knowledge into the optimization.
533///
534/// # Mathematical Formulation
535///
536/// The residual is simply the difference between the current value and the prior:
537///
538/// ```text
539/// r = x - x_prior
540/// ```
541///
542/// The Jacobian is the identity matrix: `J = I`.
543///
544/// # Use Cases
545///
546/// - **Anchoring**: Fix the first pose in SLAM to prevent drift
547/// - **GPS measurements**: Constrain a pose to a known global position
548/// - **Prior knowledge**: Incorporate measurements from other sensors
549/// - **Regularization**: Prevent variables from drifting too far from initial values
550///
551/// # Example
552///
553/// ```
554/// use apex_solver::core::factors::{Factor, PriorFactor};
555/// use nalgebra::DVector;
556///
557/// // Prior: first pose should be at origin
558/// let prior = PriorFactor {
559///     data: DVector::from_vec(vec![0.0, 0.0, 0.0]),
560/// };
561///
562/// // Current estimate (slightly off)
563/// let current_pose = DVector::from_vec(vec![0.1, 0.05, 0.02]);
564///
565/// // Compute residual (shows deviation from prior)
566/// let (residual, jacobian) = prior.linearize(&[current_pose], true);
567/// println!("Deviation from origin: {:?}", residual);
568/// ```
569///
570/// # Implementation Note
571///
572/// This is a simple "Euclidean" prior that works for any vector space. For manifold
573/// variables (SE2, SE3, etc.), consider using manifold-aware priors that respect the
574/// geometry (not yet implemented).
575#[derive(Debug, Clone)]
576pub struct PriorFactor {
577    /// The prior value (measurement or known value)
578    pub data: nalgebra::DVector<f64>,
579}
580
581impl Factor for PriorFactor {
582    /// Compute residual and Jacobian for prior factor.
583    ///
584    /// # Arguments
585    ///
586    /// * `params` - Single variable value
587    ///
588    /// # Returns
589    ///
590    /// - Residual: N×1 vector `(x_current - x_prior)`
591    /// - Jacobian: N×N identity matrix
592    ///
593    /// # Example
594    ///
595    /// ```
596    /// use apex_solver::core::factors::{Factor, PriorFactor};
597    /// use nalgebra::DVector;
598    ///
599    /// let prior = PriorFactor {
600    ///     data: DVector::from_vec(vec![1.0, 2.0]),
601    /// };
602    ///
603    /// let current = DVector::from_vec(vec![1.5, 2.3]);
604    /// let (residual, jacobian) = prior.linearize(&[current], true);
605    ///
606    /// // Residual shows difference
607    /// assert!((residual[0] - 0.5).abs() < 1e-10);
608    /// assert!((residual[1] - 0.3).abs() < 1e-10);
609    ///
610    /// // Jacobian is identity
611    /// if let Some(jac) = jacobian {
612    ///     assert_eq!(jac[(0, 0)], 1.0);
613    ///     assert_eq!(jac[(1, 1)], 1.0);
614    /// }
615    /// ```
616    fn linearize(
617        &self,
618        params: &[nalgebra::DVector<f64>],
619        compute_jacobian: bool,
620    ) -> (nalgebra::DVector<f64>, Option<nalgebra::DMatrix<f64>>) {
621        let residual = &params[0] - &self.data;
622        let jacobian = if compute_jacobian {
623            Some(nalgebra::DMatrix::<f64>::identity(
624                residual.nrows(),
625                residual.nrows(),
626            ))
627        } else {
628            None
629        };
630        (residual, jacobian)
631    }
632
633    fn get_dimension(&self) -> usize {
634        self.data.len()
635    }
636}