Skip to content

Commit 80deb3a

Browse files
committed
Merge branch 'features/spline' into dev
2 parents 571d93f + ae9a000 commit 80deb3a

File tree

8 files changed

+231
-0
lines changed

8 files changed

+231
-0
lines changed

example_data/bspline_calculus.png

98.5 KB
Loading
119 KB
Loading

example_data/bspline_integral.png

92.6 KB
Loading

example_data/bspline_original.png

91.7 KB
Loading

example_data/bspline_with_area.png

86.8 KB
Loading

examples/area_under_spline.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use peroxide::fuga::*;
2+
use anyhow::Result;
3+
4+
fn main() -> Result<()> {
5+
// 1. Define the B-spline
6+
let degree = 3;
7+
let knots = vec![0f64, 1f64, 2f64, 3f64, 4f64];
8+
let control_points = vec![
9+
vec![0f64, 0f64],
10+
vec![1f64, 2f64],
11+
vec![2f64, -1f64],
12+
vec![3f64, 1f64],
13+
vec![4f64, -1f64],
14+
vec![5f64, 2f64],
15+
vec![6f64, 0f64],
16+
];
17+
18+
let spline = BSpline::clamped(degree, knots, control_points.clone())?;
19+
let deriv_spline = spline.derivative()?;
20+
21+
// 2. Define the integrand for ∫y dx = ∫ y(t) * (dx/dt) dt
22+
let integrand = |t: f64, s: &BSpline, ds: &BSpline| -> f64 {
23+
let p = s.eval(t);
24+
let dp = ds.eval(t);
25+
let y_t = p.1;
26+
let dx_dt = dp.0;
27+
y_t * dx_dt
28+
};
29+
30+
// 3. Perform numerical integration using self-contained Gauss-Legendre quadrature
31+
let t_start = 0f64;
32+
let t_end = 4f64;
33+
34+
//let area = gauss_legendre_integrate(
35+
// |t| integrand(t, &spline, &deriv_spline),
36+
// t_start,
37+
// t_end,
38+
// 64,
39+
//);
40+
let area = integrate(|t| integrand(t, &spline, &deriv_spline), (t_start, t_end), G7K15R(1e-4, 20));
41+
42+
// 4. Print the result
43+
println!("The area under the B-spline curve (∫y dx) is: {}", area);
44+
45+
// For verification, let's plot the original curve
46+
#[cfg(feature = "plot")]
47+
{
48+
let t = linspace(t_start, t_end, 200);
49+
let (x, y): (Vec<f64>, Vec<f64>) = spline.eval_vec(&t).into_iter().unzip();
50+
51+
let mut plt = Plot2D::new();
52+
plt.set_title(&format!("Original B-Spline (Area approx. {:.4})", area))
53+
.set_xlabel("x")
54+
.set_ylabel("y")
55+
.insert_pair((x.clone(), y.clone()))
56+
.insert_pair((control_points.iter().map(|p| p[0]).collect(), control_points.iter().map(|p| p[1]).collect()))
57+
.set_plot_type(vec![(0, PlotType::Line), (1, PlotType::Scatter)])
58+
.set_color(vec![(0, "blue"), (1, "red")])
59+
.set_legend(vec!["Spline", "Control Points"])
60+
.set_style(PlotStyle::Nature)
61+
.set_path("example_data/bspline_with_area.png")
62+
.set_dpi(600)
63+
.savefig()?;
64+
65+
println!("Generated plot: example_data/bspline_with_area.png");
66+
}
67+
68+
Ok(())
69+
}

examples/bspline_calculus.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use peroxide::fuga::*;
2+
use anyhow::Result;
3+
4+
fn main() -> Result<()> {
5+
// 1. Define the B-spline
6+
let degree = 3;
7+
let knots = vec![0f64, 1f64, 2f64, 3f64, 4f64];
8+
let control_points = vec![
9+
vec![0f64, 0f64],
10+
vec![1f64, 2f64],
11+
vec![2f64, -1f64],
12+
vec![3f64, 1f64],
13+
vec![4f64, -1f64],
14+
vec![5f64, 2f64],
15+
vec![6f64, 0f64],
16+
];
17+
18+
let spline = BSpline::clamped(degree, knots.clone(), control_points.clone())?;
19+
20+
// 2. Get the derivative and integral
21+
let deriv_spline = spline.derivative()?;
22+
let integ_spline = spline.integral()?;
23+
24+
// 3. Evaluate points on all splines
25+
let t = linspace(0f64, 4f64, 200);
26+
let (x, y): (Vec<f64>, Vec<f64>) = spline.eval_vec(&t).into_iter().unzip();
27+
let (dx, dy): (Vec<f64>, Vec<f64>) = deriv_spline.eval_vec(&t).into_iter().unzip();
28+
let (ix, iy): (Vec<f64>, Vec<f64>) = integ_spline.eval_vec(&t).into_iter().unzip();
29+
30+
// 4. Plotting
31+
#[cfg(feature = "plot")]
32+
{
33+
// Plot Original Spline and its control points
34+
let control_x: Vec<f64> = control_points.iter().map(|p| p[0]).collect();
35+
let control_y: Vec<f64> = control_points.iter().map(|p| p[1]).collect();
36+
37+
let mut plt = Plot2D::new();
38+
plt.set_title("Original B-Spline")
39+
.set_xlabel("x")
40+
.set_ylabel("y")
41+
.insert_pair((x.clone(), y.clone()))
42+
.insert_pair((control_x, control_y))
43+
.set_plot_type(vec![(0, PlotType::Line), (1, PlotType::Scatter)])
44+
.set_color(vec![(0, "darkblue"), (1, "darkred")])
45+
.set_legend(vec!["Spline", "Control Points"])
46+
.set_style(PlotStyle::Nature)
47+
.set_path("example_data/bspline_original.png")
48+
.set_dpi(600)
49+
.savefig()?;
50+
51+
// Plot Derivative (Hodograph) and its control points
52+
let deriv_control_x: Vec<f64> = deriv_spline.control_points.iter().map(|p| p[0]).collect();
53+
let deriv_control_y: Vec<f64> = deriv_spline.control_points.iter().map(|p| p[1]).collect();
54+
55+
let mut plt_deriv = Plot2D::new();
56+
plt_deriv.set_title("B-Spline Derivative (Hodograph)")
57+
.set_xlabel("dx/dt")
58+
.set_ylabel("dy/dt")
59+
.insert_pair((dx, dy))
60+
.insert_pair((deriv_control_x, deriv_control_y))
61+
.set_plot_type(vec![(0, PlotType::Line), (1, PlotType::Scatter)])
62+
.set_color(vec![(0, "darkblue"), (1, "darkred")])
63+
.set_legend(vec!["Derivative", "Derivative Control Points"])
64+
.set_style(PlotStyle::Nature)
65+
.set_path("example_data/bspline_derivative.png")
66+
.set_dpi(600)
67+
.savefig()?;
68+
69+
// Plot Integral and its control points
70+
let integ_control_x: Vec<f64> = integ_spline.control_points.iter().map(|p| p[0]).collect();
71+
let integ_control_y: Vec<f64> = integ_spline.control_points.iter().map(|p| p[1]).collect();
72+
73+
let mut plt_integ = Plot2D::new();
74+
plt_integ.set_title("B-Spline Integral")
75+
.set_xlabel("Integral of x")
76+
.set_ylabel("Integral of y")
77+
.insert_pair((ix, iy))
78+
.insert_pair((integ_control_x, integ_control_y))
79+
.set_plot_type(vec![(0, PlotType::Line), (1, PlotType::Scatter)])
80+
.set_color(vec![(0, "darkblue"), (1, "darkred")])
81+
.set_legend(vec!["Integral", "Integral Control Points"])
82+
.set_style(PlotStyle::Nature)
83+
.set_path("example_data/bspline_integral.png")
84+
.set_dpi(600)
85+
.savefig()?;
86+
}
87+
88+
println!("Generated plots for original, derivative, and integral splines.");
89+
println!("Please check 'example_data/bspline_original.png', 'example_data/bspline_derivative.png', and 'example_data/bspline_integral.png'.");
90+
91+
Ok(())
92+
}

src/numerical/spline.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,76 @@ impl BSpline {
12061206

12071207
B[0][p]
12081208
}
1209+
1210+
/// Returns the derivative of the B-spline.
1211+
/// The derivative of a B-spline of degree p is a B-spline of degree p-1.
1212+
pub fn derivative(&self) -> Result<Self> {
1213+
if self.degree == 0 {
1214+
bail!("Cannot take the derivative of a B-spline with degree 0.");
1215+
}
1216+
1217+
let p = self.degree;
1218+
let c = self.control_points.len();
1219+
1220+
// The new control points (Q_i) are calculated from the old ones (P_i)
1221+
// Q_i = p * (P_{i+1} - P_i) / (t_{i+p+1} - t_{i+1})
1222+
let mut new_control_points = Vec::with_capacity(c - 1);
1223+
for i in 0..c - 1 {
1224+
let p_i = &self.control_points[i];
1225+
let p_i1 = &self.control_points[i + 1];
1226+
let denominator = self.knots[i + p + 1] - self.knots[i + 1];
1227+
if denominator == 0.0 {
1228+
new_control_points.push(vec![0.0; p_i.len()]);
1229+
} else {
1230+
let factor = p as f64 / denominator;
1231+
let q_i = p_i1
1232+
.iter()
1233+
.zip(p_i)
1234+
.map(|(a, b)| factor * (a - b))
1235+
.collect();
1236+
new_control_points.push(q_i);
1237+
}
1238+
}
1239+
1240+
// The new knot vector is the old one with the first and last knots removed.
1241+
let new_knots = self.knots[1..self.knots.len() - 1].to_vec();
1242+
let new_degree = self.degree - 1;
1243+
1244+
BSpline::open(new_degree, new_knots, new_control_points)
1245+
}
1246+
1247+
/// Returns the integral of the B-spline.
1248+
/// The integral of a B-spline of degree p is a B-spline of degree p+1.
1249+
/// The first control point of the integral is set to zero (integration constant).
1250+
pub fn integral(&self) -> Result<Self> {
1251+
let p = self.degree;
1252+
let c = self.control_points.len();
1253+
let dim = self.control_points[0].len();
1254+
1255+
// The new knot vector is the old one with the first and last knots duplicated.
1256+
let mut new_knots = self.knots.clone();
1257+
new_knots.insert(0, self.knots[0]);
1258+
new_knots.push(self.knots[self.knots.len() - 1]);
1259+
1260+
// The new control points (R_i) are calculated recursively.
1261+
// R_j = R_{j-1} + P_{j-1} * (t_{j+p} - t_{j-1}) / (p+1)
1262+
let mut new_control_points = vec![vec![0.0; dim]; c + 1]; // R_0 is the zero vector
1263+
1264+
for i in 1..=c {
1265+
let factor = (self.knots[i + p] - self.knots[i - 1]) / ((p + 1) as f64);
1266+
let prev_r = new_control_points[i - 1].clone();
1267+
let p_im1 = &self.control_points[i - 1];
1268+
let mut r_i = Vec::with_capacity(dim);
1269+
for j in 0..dim {
1270+
r_i.push(prev_r[j] + factor * p_im1[j]);
1271+
}
1272+
new_control_points[i] = r_i;
1273+
}
1274+
1275+
let new_degree = self.degree + 1;
1276+
1277+
BSpline::open(new_degree, new_knots, new_control_points)
1278+
}
12091279
}
12101280

12111281
impl Spline<(f64, f64)> for BSpline {

0 commit comments

Comments
 (0)