diff --git a/pybezier/composite_bezier_curve.py b/pybezier/composite_bezier_curve.py index 503daa6..81ce391 100644 --- a/pybezier/composite_bezier_curve.py +++ b/pybezier/composite_bezier_curve.py @@ -21,6 +21,16 @@ def __init__(self, curves): self.duration = self.final_time - self.initial_time self.knot_times = [self.initial_time] + [curve.final_time for curve in curves] + def curve_segment(self, time : float) -> int: + segment = 0 + while self[segment].final_time < time: + segment += 1 + return segment + + def __call__(self, time : float) -> np.array: + segment = self.curve_segment(time) + return self[segment](time) + def initial_point(self) -> np.array: return self[0].initial_point() @@ -33,16 +43,6 @@ def __iter__(self) -> Iterable[BezierCurve]: def __getitem__(self, i : int) -> BezierCurve: return self.curves[i] - def __call__(self, time : float) -> np.array: - segment = self.find_segment(time) - return self[segment](time) - - def find_segment(self, time : float) -> int: - segment = 0 - while self[segment].final_time < time: - segment += 1 - return segment - def __len__(self) -> int: return(len(self.curves)) @@ -82,7 +82,8 @@ def __neg__(self) -> Self: return 0 - self def knot_points(self) -> np.array: - knots = [curve.points[0] for curve in self] + # assumes that the curve is continuous + knots = [curve.initial_point() for curve in self] knots.append(self.final_point()) return np.array(knots) diff --git a/tests/test_bezier_curve.py b/tests/test_bezier_curve.py index ccbf14e..103412e 100644 --- a/tests/test_bezier_curve.py +++ b/tests/test_bezier_curve.py @@ -1,6 +1,5 @@ import unittest import numpy as np - from pybezier.bezier_curve import BezierCurve class TestBezierCurve(unittest.TestCase): @@ -14,6 +13,7 @@ def setUp(self): self.curve_1 = self.curve self.points_2 = np.random.rand(7, 3) self.curve_2 = BezierCurve(self.points_2, self.initial_time, self.final_time) + self.time_samples = np.linspace(self.initial_time, self.final_time) def test_init(self): np.testing.assert_equal(self.curve.points, self.points) @@ -30,11 +30,11 @@ def test_init_deafult(self): self.assertEqual(curve.final_time, 1) def test_initial_final_point(self): - np.testing.assert_array_almost_equal(self.curve(self.initial_time), self.points[0]) - np.testing.assert_array_almost_equal(self.curve(self.final_time), self.points[-1]) + np.testing.assert_array_almost_equal(self.curve(self.initial_time), self.curve.initial_point()) + np.testing.assert_array_almost_equal(self.curve(self.final_time), self.curve.final_point()) def test_berstein(self): - for time in np.linspace(self.initial_time, self.final_time): + for time in self.time_samples: values = [self.curve._berstein(time, n) for n in range(self.curve.degree + 1)] # tests partition of unity self.assertTrue(min(values) >= 0) @@ -62,64 +62,63 @@ def test_call(self): self.assertAlmostEqual(p1[1], p2[1]) def test_scalar_mul(self): - np.random.seed(0) c = 3.66 prod_1 = self.curve * c prod_2 = c * self.curve - for time in np.linspace(self.initial_time, self.final_time): + for time in self.time_samples: value = self.curve(time) * c np.testing.assert_array_almost_equal(prod_1(time), value) np.testing.assert_array_almost_equal(prod_2(time), value) def test_elementwise_mul(self): prod = self.curve_1 * self.curve_2 - for time in np.linspace(self.initial_time, self.final_time): + for time in self.time_samples: np.testing.assert_array_almost_equal(prod(time), self.curve_1(time) * self.curve_2(time)) def test_elevate_degree(self): curve = self.curve.elevate_degree(11) - for time in np.linspace(self.initial_time, self.final_time): + for time in self.time_samples: np.testing.assert_array_almost_equal(self.curve(time), curve(time)) def test_scalar_add_sub(self): c = 3.66 - curve_sum_1 = self.curve + c - curve_sum_2 = c + self.curve - curve_sub_1 = self.curve - c - curve_sub_2 = c - self.curve - for time in np.linspace(self.initial_time, self.final_time): + sum_1 = self.curve + c + sum_2 = c + self.curve + sub_1 = self.curve - c + sub_2 = c - self.curve + for time in self.time_samples: value = self.curve(time) + c - np.testing.assert_array_almost_equal(curve_sum_1(time), value) - np.testing.assert_array_almost_equal(curve_sum_2(time), value) + np.testing.assert_array_almost_equal(sum_1(time), value) + np.testing.assert_array_almost_equal(sum_2(time), value) value -= 2 * c - np.testing.assert_array_almost_equal(curve_sub_1(time), value) - np.testing.assert_array_almost_equal(curve_sub_2(time), -value) + np.testing.assert_array_almost_equal(sub_1(time), value) + np.testing.assert_array_almost_equal(sub_2(time), -value) def test_elementwise_add_sub(self): - curve_sum = self.curve_1 + self.curve_2 - curve_sub = self.curve_1 - self.curve_2 - for time in np.linspace(self.initial_time, self.final_time): + sum = self.curve_1 + self.curve_2 + sub = self.curve_1 - self.curve_2 + for time in self.time_samples: value = self.curve_1(time) + self.curve_2(time) - np.testing.assert_array_almost_equal(curve_sum(time), value) + np.testing.assert_array_almost_equal(sum(time), value) value -= 2 * self.curve_2(time) - np.testing.assert_array_almost_equal(curve_sub(time), value) + np.testing.assert_array_almost_equal(sub(time), value) def test_neg(self): - curve = - self.curve - for time in np.linspace(0, 1): - np.testing.assert_array_almost_equal(curve(time), -self.curve(time)) + neg = - self.curve + for time in self.time_samples: + np.testing.assert_array_almost_equal(neg(time), -self.curve(time)) def test_derivative(self): - derivative = self.curve.derivative() + der = self.curve.derivative() time_step = 1e-9 for time in np.linspace(self.initial_time, self.final_time - time_step): value = (self.curve(time + time_step) - self.curve(time)) / time_step - np.testing.assert_array_almost_equal(derivative(time), value) + np.testing.assert_array_almost_equal(der(time), value) def test_split(self): split_time = (self.initial_time + self.final_time) / 2 curve_1, curve_2 = self.curve.split(split_time) - for time in np.linspace(self.initial_time, self.final_time): + for time in self.time_samples: if time < split_time: np.testing.assert_array_almost_equal(self.curve(time), curve_1(time)) elif time > split_time: @@ -138,6 +137,7 @@ def test_integral_of_convex(self): # upper bound for curve lenght is equal to distance of control points derivative = self.curve.derivative() value = sum(np.linalg.norm(y - x) for x, y in zip(self.points[:-1], self.points[1:])) + self.assertAlmostEqual(derivative.integral_of_convex(np.linalg.norm), value) if __name__ == '__main__': diff --git a/tests/test_binomial.py b/tests/test_binomial.py index 22de79e..1c23ca2 100644 --- a/tests/test_binomial.py +++ b/tests/test_binomial.py @@ -1,5 +1,4 @@ import unittest - from pybezier.binomial import binomial class TestBinomial(unittest.TestCase): diff --git a/tests/test_composite_bezier_curve.py b/tests/test_composite_bezier_curve.py new file mode 100644 index 0000000..82f8279 --- /dev/null +++ b/tests/test_composite_bezier_curve.py @@ -0,0 +1,156 @@ +import unittest +import numpy as np +from pybezier.bezier_curve import BezierCurve +from pybezier.composite_bezier_curve import CompositeBezierCurve + +class TestCompositeBezierCurve(unittest.TestCase): + + @staticmethod + def random_composite_curve(dimension, n_curves, n_points): + points = np.random.rand((n_points - 1) * n_curves + 1, dimension) + curves = [] + for i in range(n_curves): + start = n_points * i - i + stop = n_points * (i + 1) - i + points_i = points[start:stop] + curve_i = BezierCurve(points_i, i, i + 1) + curves.append(curve_i) + return CompositeBezierCurve(curves) + + def setUp(self): + np.random.seed(0) + self.dimension = 3 + self.n_curves = 4 + self.n_points = 5 + self.composite_curve = self.random_composite_curve(self.dimension, self.n_curves, self.n_points) + self.initial_time = 0 + self.final_time = self.n_curves + self.time_samples = np.linspace(self.initial_time, self.final_time) + self.composite_curve_1 = self.composite_curve + self.composite_curve_2 = self.random_composite_curve(self.dimension, self.n_curves, self.n_points) + + def test_init(self): + self.assertEqual(len(self.composite_curve.curves), self.n_curves) + for curve in self.composite_curve.curves: + self.assertEqual(curve.points.shape, (self.n_points, self.dimension)) + self.assertEqual(self.composite_curve.dimension, self.dimension) + self.assertEqual(self.composite_curve.initial_time, 0) + self.assertEqual(self.composite_curve.final_time, self.n_curves) + self.assertEqual(self.composite_curve.duration, self.n_curves) + self.assertEqual(self.composite_curve.knot_times, list(range(self.n_curves + 1))) + + def test_curve_segment(self): + for time in self.time_samples[:-1]: + self.assertEqual(self.composite_curve.curve_segment(time), int(time)) + self.assertEqual(self.composite_curve.curve_segment(self.final_time), self.n_curves - 1) + + def test_call(self): + for time in self.time_samples: + value = self.composite_curve(time) + i = self.composite_curve.curve_segment(time) + curve = self.composite_curve.curves[i] + np.testing.assert_array_almost_equal(value, curve(time)) + + def test_initial_final_point(self): + initial_value = self.composite_curve(self.initial_time) + initial_point = self.composite_curve.initial_point() + np.testing.assert_array_almost_equal(initial_value, initial_point) + final_value = self.composite_curve(self.final_time) + final_point = self.composite_curve.final_point() + np.testing.assert_array_almost_equal(final_value, final_point) + + def test_iter(self): + for curve in self.composite_curve: + self.assertEqual(curve.duration, 1) + + def test_getitem(self): + for i in range(self.n_curves): + curve = self.composite_curve[i] + self.assertEqual(curve.duration, 1) + + def test_len(self): + self.assertEqual(len(self.composite_curve), self.n_curves) + + def test_scalar_mul(self): + c = 3.66 + prod_1 = self.composite_curve * c + prod_2 = c * self.composite_curve + for time in self.time_samples: + value = self.composite_curve(time) * c + np.testing.assert_array_almost_equal(prod_1(time), value) + np.testing.assert_array_almost_equal(prod_2(time), value) + + def test_elementwise_mul(self): + prod = self.composite_curve_1 * self.composite_curve_2 + for time in self.time_samples: + value = self.composite_curve_1(time) * self.composite_curve_2(time) + np.testing.assert_array_almost_equal(prod(time), value) + + def test_elevate_degree(self): + composite_curve = self.composite_curve.elevate_degree(11) + for time in self.time_samples: + np.testing.assert_array_almost_equal(self.composite_curve(time), composite_curve(time)) + + def test_scalar_add_sub(self): + c = 3.66 + sum_1 = self.composite_curve + c + sum_2 = c + self.composite_curve + sub_1 = self.composite_curve - c + sub_2 = c - self.composite_curve + for time in self.time_samples: + value = self.composite_curve(time) + c + np.testing.assert_array_almost_equal(sum_1(time), value) + np.testing.assert_array_almost_equal(sum_2(time), value) + value -= 2 * c + np.testing.assert_array_almost_equal(sub_1(time), value) + np.testing.assert_array_almost_equal(sub_2(time), -value) + + def test_elementwise_add_sub(self): + sum = self.composite_curve_1 + self.composite_curve_2 + sub = self.composite_curve_1 - self.composite_curve_2 + for time in self.time_samples: + value = self.composite_curve_1(time) + self.composite_curve_2(time) + np.testing.assert_array_almost_equal(sum(time), value) + value -= 2 * self.composite_curve_2(time) + np.testing.assert_array_almost_equal(sub(time), value) + + def test_neg(self): + neg = - self.composite_curve + for time in self.time_samples: + np.testing.assert_array_almost_equal(neg(time), -self.composite_curve(time)) + + def test_derivative(self): + der = self.composite_curve.derivative() + time_step = 1e-9 + for time in np.linspace(self.initial_time, self.final_time - time_step): + value = (self.composite_curve(time + time_step) - self.composite_curve(time)) / time_step + np.testing.assert_array_almost_equal(der(time), value) + + def test_knot_points(self): + for i, point in enumerate(self.composite_curve.knot_points()): + np.testing.assert_array_almost_equal(point, self.composite_curve(i)) + + def test_durations(self): + durations = list(self.composite_curve.durations()) + self.assertEqual(durations, [1] * self.n_curves) + + def test_concatenate(self): + conc = self.composite_curve_1.concatenate(self.composite_curve_2) + for time in self.time_samples * 2: + if time < self.final_time: + value = self.composite_curve_1(time) + np.testing.assert_array_almost_equal(conc(time), value) + elif time > self.final_time: + value = self.composite_curve_2(time - self.final_time) + np.testing.assert_array_almost_equal(conc(time), value) + + def test_l2_squared(self): + n_samples = 5000 + times = np.linspace(self.initial_time, self.final_time, n_samples) + squared_norm = lambda time: np.linalg.norm(self.composite_curve(time)) ** 2 + values = [squared_norm(time) for time in times] + integral = np.trapezoid(values, times) + self.assertAlmostEqual(self.composite_curve.l2_squared(), integral, places=4) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file