-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
Copy pathleast_square_approx.rs
115 lines (102 loc) · 3.15 KB
/
least_square_approx.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/// Least Square Approximation <p>
/// Function that returns a polynomial which very closely passes through the given points (in 2D)
///
/// The result is made of coeficients, in descending order (from x^degree to free term)
///
/// Parameters:
///
/// points -> coordinates of given points
///
/// degree -> degree of the polynomial
///
pub fn least_square_approx<T: Into<f64> + Copy, U: Into<f64> + Copy>(
points: &[(T, U)],
degree: i32,
) -> Option<Vec<f64>> {
use nalgebra::{DMatrix, DVector};
/* Used for rounding floating numbers */
fn round_to_decimals(value: f64, decimals: i32) -> f64 {
let multiplier = 10f64.powi(decimals);
(value * multiplier).round() / multiplier
}
/* Casting the data parsed to this function to f64 (as some points can have decimals) */
let vals: Vec<(f64, f64)> = points
.iter()
.map(|(x, y)| ((*x).into(), (*y).into()))
.collect();
/* Because of collect we need the Copy Trait for T and U */
/* Computes the sums in the system of equations */
let mut sums = Vec::<f64>::new();
for i in 1..=(2 * degree + 1) {
sums.push(vals.iter().map(|(x, _)| x.powi(i - 1)).sum());
}
/* Compute the free terms column vector */
let mut free_col = Vec::<f64>::new();
for i in 1..=(degree + 1) {
free_col.push(vals.iter().map(|(x, y)| y * (x.powi(i - 1))).sum());
}
let b = DVector::from_row_slice(&free_col);
/* Create and fill the system's matrix */
let size = (degree + 1) as usize;
let a = DMatrix::from_fn(size, size, |i, j| sums[degree as usize + i - j]);
/* Solve the system of equations: A * x = b */
match a.qr().solve(&b) {
Some(x) => {
let rez: Vec<f64> = x.iter().map(|x| round_to_decimals(*x, 5)).collect();
Some(rez)
}
None => None, //<-- The system cannot be solved (badly conditioned system's matrix)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ten_points_1st_degree() {
let points = vec![
(5.3, 7.8),
(4.9, 8.1),
(6.1, 6.9),
(4.7, 8.3),
(6.5, 7.7),
(5.6, 7.0),
(5.8, 8.2),
(4.5, 8.0),
(6.3, 7.2),
(5.1, 8.4),
];
assert_eq!(
least_square_approx(&points, 1),
Some(vec![-0.49069, 10.44898])
);
}
#[test]
fn eight_points_5th_degree() {
let points = vec![
(4f64, 8f64),
(8f64, 2f64),
(1f64, 7f64),
(10f64, 3f64),
(11.0, 0.0),
(7.0, 3.0),
(10.0, 1.0),
(13.0, 13.0),
];
assert_eq!(
least_square_approx(&points, 5),
Some(vec![
0.00603, -0.21304, 2.79929, -16.53468, 40.29473, -19.35771
])
);
}
#[test]
fn four_points_2nd_degree() {
let points = vec![
(2.312, 8.345344),
(-2.312, 8.345344),
(-0.7051, 3.49716601),
(0.7051, 3.49716601),
];
assert_eq!(least_square_approx(&points, 2), Some(vec![1.0, 0.0, 3.0]));
}
}