Skip to content

Commit b3474a0

Browse files
Add calculation of derivatives of 2D curve data.
This can be used to plot curve data and analyse how the curves are moving, so we can detect jittery values. GitHub issue #268
1 parent 31cfa81 commit b3474a0

File tree

3 files changed

+305
-0
lines changed

3 files changed

+305
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// Copyright (C) 2024 David Cattermole.
3+
//
4+
// This file is part of mmSolver.
5+
//
6+
// mmSolver is free software: you can redistribute it and/or modify it
7+
// under the terms of the GNU Lesser General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// mmSolver is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Lesser General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Lesser General Public License
17+
// along with mmSolver. If not, see <https://www.gnu.org/licenses/>.
18+
// ====================================================================
19+
//
20+
21+
use anyhow::bail;
22+
use anyhow::Result;
23+
use log::debug;
24+
25+
use crate::constant::Real;
26+
27+
/// Given arrays of time and value points, calculate the derivative using
28+
/// previous and next values (except for the first and last points).
29+
/// Writes results into the provided output slice.
30+
///
31+
/// # Arguments
32+
/// * `times` - Slice of time points
33+
/// * `values` - Slice of corresponding values
34+
/// * `out` - Slice where derivatives will be written, must be same length as inputs
35+
///
36+
/// # Returns
37+
/// `Ok(())` if successful, `Err` if slice lengths don't match
38+
///
39+
/// This function avoids allocations and is designed to be easy for
40+
/// compilers to auto-vectorize.
41+
fn calc_derivative(
42+
times: &[Real],
43+
values: &[Real],
44+
out: &mut [Real],
45+
) -> Result<()> {
46+
// Verify slice lengths match
47+
if times.len() != values.len() || times.len() != out.len() {
48+
bail!("Input and output slices must have equal length");
49+
}
50+
51+
let n = times.len();
52+
if n < 2 {
53+
// Zero out the output for small inputs, because only a single
54+
// time/value cannot change, so there is no derivative.
55+
out.fill(0.0);
56+
57+
return Ok(());
58+
}
59+
60+
// First point, using forward difference: (f(x + h) - f(x)) / h
61+
out[0] = (values[1] - values[0]) / (times[1] - times[0]);
62+
63+
// Middle points, using central difference: (f(x + h) - f(x - h)) / (2h)
64+
for i in 1..n - 1 {
65+
// Forward and backward time differences
66+
let dt_forward = times[i + 1] - times[i];
67+
let dt_backward = times[i] - times[i - 1];
68+
let total_dt = dt_forward + dt_backward;
69+
70+
// Weights for weighted average
71+
let w_backward = dt_forward / total_dt;
72+
let w_forward = dt_backward / total_dt;
73+
74+
// Forward and backward value differences
75+
let dv_forward = values[i + 1] - values[i];
76+
let dv_backward = values[i] - values[i - 1];
77+
78+
// Weighted average of forward and backward derivatives
79+
out[i] = w_backward * (dv_backward / dt_backward)
80+
+ w_forward * (dv_forward / dt_forward);
81+
}
82+
83+
// Last point, using backward difference: (f(x) - f(x - h)) / h
84+
let last = n - 1;
85+
out[last] =
86+
(values[last] - values[last - 1]) / (times[last] - times[last - 1]);
87+
88+
Ok(())
89+
}
90+
91+
/// Calculate derivatives (velocity, acceleration, jerk) from time and
92+
/// height data.
93+
///
94+
/// Returns vectors of the same length as input by using forward,
95+
/// central, and backward differences
96+
pub fn calculate_derivatives(
97+
times: &[Real],
98+
heights: &[Real],
99+
) -> Result<(Vec<Real>, Vec<Real>, Vec<Real>)> {
100+
let n = heights.len();
101+
if n < 4 {
102+
bail!("Need at least 4 points for derivative analysis");
103+
}
104+
105+
// TODO: Should these vectors be given to this function to be
106+
// allocated?
107+
let mut velocity = vec![0.0; n];
108+
let mut acceleration = vec![0.0; n];
109+
let mut jerk = vec![0.0; n];
110+
111+
calc_derivative(times, heights, &mut velocity)?;
112+
calc_derivative(times, &velocity, &mut acceleration)?;
113+
calc_derivative(times, &acceleration, &mut jerk)?;
114+
115+
Ok((velocity, acceleration, jerk))
116+
}

lib/rust/mmscenegraph/src/math/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
//
2020

2121
pub mod camera;
22+
pub mod curve_analysis;
2223
pub mod curve_fit;
2324
pub mod dag;
2425
pub mod line;
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
//
2+
// Copyright (C) 2024 David Cattermole.
3+
//
4+
// This file is part of mmSolver.
5+
//
6+
// mmSolver is free software: you can redistribute it and/or modify it
7+
// under the terms of the GNU Lesser General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// mmSolver is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Lesser General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Lesser General Public License
17+
// along with mmSolver. If not, see <https://www.gnu.org/licenses/>.
18+
// ====================================================================
19+
//
20+
21+
mod common;
22+
23+
use anyhow::Result;
24+
use approx::assert_relative_eq;
25+
26+
use mmscenegraph_rust::math::curve_analysis::calculate_derivatives;
27+
28+
fn print_arrays(velocity: &[f64], acceleration: &[f64], jerk: &[f64]) {
29+
println!("Found {} velocity:", velocity.len());
30+
for (i, v) in velocity.iter().enumerate() {
31+
println!("i={i} v={v}");
32+
}
33+
34+
println!("Found {} acceleration:", acceleration.len());
35+
for (i, v) in acceleration.iter().enumerate() {
36+
println!("i={i} v={v}");
37+
}
38+
39+
println!("Found {} jerk:", jerk.len());
40+
for (i, v) in jerk.iter().enumerate() {
41+
println!("i={i} v={v}");
42+
}
43+
}
44+
45+
#[test]
46+
fn calculate_derivatives1() -> Result<()> {
47+
// Constant speed, upwards.
48+
let times = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
49+
let heights = vec![1.0, 1.1, 1.2, 1.3, 1.4, 1.5];
50+
51+
let (velocity, acceleration, jerk) =
52+
calculate_derivatives(&times, &heights)?;
53+
54+
print_arrays(&velocity, &acceleration, &jerk);
55+
56+
assert_eq!(velocity.len(), 6);
57+
assert_eq!(acceleration.len(), 6);
58+
assert_eq!(jerk.len(), 6);
59+
60+
assert_relative_eq!(velocity[0], 0.1, epsilon = 1.0e-9);
61+
assert_relative_eq!(velocity[1], 0.1, epsilon = 1.0e-9);
62+
assert_relative_eq!(velocity[2], 0.1, epsilon = 1.0e-9);
63+
assert_relative_eq!(velocity[3], 0.1, epsilon = 1.0e-9);
64+
assert_relative_eq!(velocity[4], 0.1, epsilon = 1.0e-9);
65+
assert_relative_eq!(velocity[5], 0.1, epsilon = 1.0e-9);
66+
67+
assert_relative_eq!(acceleration[0], 0.0, epsilon = 1.0e-9);
68+
assert_relative_eq!(acceleration[1], 0.0, epsilon = 1.0e-9);
69+
assert_relative_eq!(acceleration[2], 0.0, epsilon = 1.0e-9);
70+
assert_relative_eq!(acceleration[3], 0.0, epsilon = 1.0e-9);
71+
assert_relative_eq!(acceleration[4], 0.0, epsilon = 1.0e-9);
72+
assert_relative_eq!(acceleration[5], 0.0, epsilon = 1.0e-9);
73+
74+
assert_relative_eq!(jerk[0], 0.0, epsilon = 1.0e-9);
75+
assert_relative_eq!(jerk[1], 0.0, epsilon = 1.0e-9);
76+
assert_relative_eq!(jerk[2], 0.0, epsilon = 1.0e-9);
77+
assert_relative_eq!(jerk[3], 0.0, epsilon = 1.0e-9);
78+
assert_relative_eq!(jerk[4], 0.0, epsilon = 1.0e-9);
79+
assert_relative_eq!(jerk[5], 0.0, epsilon = 1.0e-9);
80+
81+
Ok(())
82+
}
83+
84+
#[test]
85+
fn calculate_derivatives2() -> Result<()> {
86+
// Constant velocity, downwards.
87+
let times = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
88+
let heights = vec![-1.0, -1.1, -1.2, -1.3, -1.4, -1.5];
89+
90+
let (velocity, acceleration, jerk) =
91+
calculate_derivatives(&times, &heights)?;
92+
93+
print_arrays(&velocity, &acceleration, &jerk);
94+
95+
assert_eq!(velocity.len(), 6);
96+
assert_eq!(acceleration.len(), 6);
97+
assert_eq!(jerk.len(), 6);
98+
99+
assert_relative_eq!(velocity[0], -0.1, epsilon = 1.0e-9);
100+
assert_relative_eq!(velocity[1], -0.1, epsilon = 1.0e-9);
101+
assert_relative_eq!(velocity[2], -0.1, epsilon = 1.0e-9);
102+
assert_relative_eq!(velocity[3], -0.1, epsilon = 1.0e-9);
103+
assert_relative_eq!(velocity[4], -0.1, epsilon = 1.0e-9);
104+
assert_relative_eq!(velocity[5], -0.1, epsilon = 1.0e-9);
105+
106+
assert_relative_eq!(acceleration[0], 0.0, epsilon = 1.0e-9);
107+
assert_relative_eq!(acceleration[1], 0.0, epsilon = 1.0e-9);
108+
assert_relative_eq!(acceleration[2], 0.0, epsilon = 1.0e-9);
109+
assert_relative_eq!(acceleration[3], 0.0, epsilon = 1.0e-9);
110+
assert_relative_eq!(acceleration[4], 0.0, epsilon = 1.0e-9);
111+
assert_relative_eq!(acceleration[5], 0.0, epsilon = 1.0e-9);
112+
113+
assert_relative_eq!(jerk[0], 0.0, epsilon = 1.0e-9);
114+
assert_relative_eq!(jerk[1], 0.0, epsilon = 1.0e-9);
115+
assert_relative_eq!(jerk[2], 0.0, epsilon = 1.0e-9);
116+
assert_relative_eq!(jerk[3], 0.0, epsilon = 1.0e-9);
117+
assert_relative_eq!(jerk[4], 0.0, epsilon = 1.0e-9);
118+
assert_relative_eq!(jerk[5], 0.0, epsilon = 1.0e-9);
119+
120+
Ok(())
121+
}
122+
123+
#[test]
124+
fn calculate_derivatives3() -> Result<()> {
125+
// Variable velocity.
126+
let times = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
127+
let heights = vec![1.0, 1.1, 1.25, 1.5, 2.0, 4.0];
128+
129+
let (velocity, acceleration, jerk) =
130+
calculate_derivatives(&times, &heights)?;
131+
132+
print_arrays(&velocity, &acceleration, &jerk);
133+
134+
assert_eq!(velocity.len(), 6);
135+
assert_eq!(acceleration.len(), 6);
136+
assert_eq!(jerk.len(), 6);
137+
138+
assert_relative_eq!(velocity[0], 0.1, epsilon = 1.0e-9);
139+
assert_relative_eq!(velocity[1], 0.125, epsilon = 1.0e-9);
140+
assert_relative_eq!(velocity[2], 0.2, epsilon = 1.0e-9);
141+
assert_relative_eq!(velocity[3], 0.375, epsilon = 1.0e-9);
142+
assert_relative_eq!(velocity[4], 1.25, epsilon = 1.0e-9);
143+
assert_relative_eq!(velocity[5], 2.0, epsilon = 1.0e-9);
144+
145+
Ok(())
146+
}
147+
148+
#[test]
149+
fn calculate_derivatives4() -> Result<()> {
150+
let times = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
151+
let heights = vec![0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0]; // f(x) = x^2
152+
153+
let (velocity, acceleration, jerk) =
154+
calculate_derivatives(&times, &heights)?;
155+
156+
print_arrays(&velocity, &acceleration, &jerk);
157+
158+
assert_eq!(velocity.len(), 7);
159+
assert_eq!(acceleration.len(), 7);
160+
assert_eq!(jerk.len(), 7);
161+
162+
// Expected derivatives of x^2;
163+
assert_relative_eq!(velocity[0], 1.0, epsilon = 1.0e-9);
164+
assert_relative_eq!(velocity[1], 2.0, epsilon = 1.0e-9);
165+
assert_relative_eq!(velocity[2], 4.0, epsilon = 1.0e-9);
166+
assert_relative_eq!(velocity[3], 6.0, epsilon = 1.0e-9);
167+
assert_relative_eq!(velocity[4], 8.0, epsilon = 1.0e-9);
168+
assert_relative_eq!(velocity[5], 10.0, epsilon = 1.0e-9);
169+
assert_relative_eq!(velocity[6], 11.0, epsilon = 1.0e-9);
170+
171+
assert_relative_eq!(acceleration[0], 1.0, epsilon = 1.0e-9);
172+
assert_relative_eq!(acceleration[1], 1.5, epsilon = 1.0e-9);
173+
assert_relative_eq!(acceleration[2], 2.0, epsilon = 1.0e-9);
174+
assert_relative_eq!(acceleration[3], 2.0, epsilon = 1.0e-9);
175+
assert_relative_eq!(acceleration[4], 2.0, epsilon = 1.0e-9);
176+
assert_relative_eq!(acceleration[5], 1.5, epsilon = 1.0e-9);
177+
assert_relative_eq!(acceleration[6], 1.0, epsilon = 1.0e-9);
178+
179+
assert_relative_eq!(jerk[0], 0.5, epsilon = 1.0e-9);
180+
assert_relative_eq!(jerk[1], 0.5, epsilon = 1.0e-9);
181+
assert_relative_eq!(jerk[2], 0.25, epsilon = 1.0e-9);
182+
assert_relative_eq!(jerk[3], 0.0, epsilon = 1.0e-9);
183+
assert_relative_eq!(jerk[4], -0.25, epsilon = 1.0e-9);
184+
assert_relative_eq!(jerk[5], -0.5, epsilon = 1.0e-9);
185+
assert_relative_eq!(jerk[6], -0.5, epsilon = 1.0e-9);
186+
187+
Ok(())
188+
}

0 commit comments

Comments
 (0)