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(¶ms, 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 = ¶ms[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}