From e62aa796944fe06a411b935f39325a121b5875b1 Mon Sep 17 00:00:00 2001 From: mickaelbegon Date: Wed, 22 Jul 2026 11:04:07 -0400 Subject: [PATCH] Interpolate spline guesses at collocation points --- bioptim/limits/path_conditions.py | 3 ++- bioptim/optimization/vector_utils.py | 2 +- tests/shard5/test_initial_condition.py | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/bioptim/limits/path_conditions.py b/bioptim/limits/path_conditions.py index 1def1a8b6..eaa31adad 100644 --- a/bioptim/limits/path_conditions.py +++ b/bioptim/limits/path_conditions.py @@ -317,7 +317,8 @@ def evaluate_at(self, shooting_point: Int, repeat: Int = 1): return self[:, shooting_point] elif self.type == InterpolationType.SPLINE: spline = interp1d(self.t, self) - return spline(shooting_point / self.n_shooting * (self.t[-1] - self.t[0])) + time = self.t[0] + shooting_point / (self.n_shooting * repeat) * (self.t[-1] - self.t[0]) + return spline(time) elif self.type == InterpolationType.CUSTOM: if self.slice_list is not None: slice_list = self.slice_list diff --git a/bioptim/optimization/vector_utils.py b/bioptim/optimization/vector_utils.py index 112de5738..fb13f31e4 100644 --- a/bioptim/optimization/vector_utils.py +++ b/bioptim/optimization/vector_utils.py @@ -109,7 +109,7 @@ def _compute_value_for_node( real_keys = [key for key in defined_values.keys() if key != "None"] for key in real_keys: - if defined_values[key].type == InterpolationType.ALL_POINTS: + if defined_values[key].type in (InterpolationType.ALL_POINTS, InterpolationType.SPLINE): point = node * repeat + sub_node else: point = _get_interpolation_point(node, sub_node) diff --git a/tests/shard5/test_initial_condition.py b/tests/shard5/test_initial_condition.py index 49747a6d7..1c86d59d0 100644 --- a/tests/shard5/test_initial_condition.py +++ b/tests/shard5/test_initial_condition.py @@ -1,4 +1,5 @@ import re +from types import SimpleNamespace import numpy as np import numpy.testing as npt @@ -19,6 +20,7 @@ SolutionMerge, ) from bioptim.limits.path_conditions import InitialGuess +from bioptim.optimization.vector_utils import _compute_value_for_node from ..utils import TestUtils # TODO: Add negative test for sizes @@ -137,6 +139,24 @@ def test_initial_guess_spline(): npt.assert_almost_equal(init.init.evaluate_at(t), expected_val) +def test_initial_guess_spline_at_collocation_points(): + class VariableContainer(dict): + shape = 1 + + initial_guesses = InitialGuessList() + initial_guesses.add("x", [[0.0, 6.0]], t=[2.0, 8.0], interpolation=InterpolationType.SPLINE) + initial_guesses["x"].check_and_adjust_dimensions(1, 2) + variables = VariableContainer(x=SimpleNamespace(index=[0])) + scaling = {"x": SimpleNamespace(scaling=np.ones((1, 1)))} + + values = [ + _compute_value_for_node(node, sub_node, 0, 3, variables, initial_guesses, scaling).item() + for node in range(2) + for sub_node in range(3) + ] + npt.assert_allclose(values, [0, 1, 2, 3, 4, 5]) + + @pytest.mark.parametrize("phase_dynamics", [PhaseDynamics.SHARED_DURING_THE_PHASE, PhaseDynamics.ONE_PER_NODE]) def test_initial_guess_update(phase_dynamics): # Load pendulum