InterpolatePy 3.0.1__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.github/workflows/docs.yml +10 -10
  2. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.github/workflows/pre-commit.yml +3 -3
  3. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.github/workflows/publish.yml +2 -2
  4. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.github/workflows/test.yml +3 -3
  5. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/InterpolatePy.egg-info/PKG-INFO +1 -1
  6. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/InterpolatePy.egg-info/SOURCES.txt +2 -1
  7. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/PKG-INFO +1 -1
  8. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/bspline_interpolator.cpp +26 -13
  9. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/log_quaternion_interpolation.cpp +0 -3
  10. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/modified_log_quaternion_interpolation.cpp +0 -3
  11. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_bspline_variants.cpp +67 -4
  12. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/tutorials/quaternion-interpolation.md +1 -1
  13. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/log_quat_new_ex.py +0 -4
  14. interpolatepy-3.2.0/examples/lqi_spiral_frenet_ex.py +342 -0
  15. interpolatepy-3.2.0/examples/mlqi_spiral_frenet_ex.py +210 -0
  16. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/b_spline_interpolate.py +13 -9
  17. interpolatepy-3.2.0/interpolatepy/log_quat.py +465 -0
  18. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/quat_core.py +13 -4
  19. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/version.py +1 -1
  20. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/pyproject.toml +4 -1
  21. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_b_spline_variants.py +65 -4
  22. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_log_quat.py +28 -226
  23. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/uv.lock +62 -0
  24. interpolatepy-3.0.1/examples/log_quat_ex.py +0 -207
  25. interpolatepy-3.0.1/interpolatepy/log_quat.py +0 -950
  26. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.editorconfig +0 -0
  27. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.gitattributes +0 -0
  28. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.github/FUNDING.yml +0 -0
  29. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.gitignore +0 -0
  30. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.pre-commit-config.yaml +0 -0
  31. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/.python-version +0 -0
  32. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/ALGORITHMS.md +0 -0
  33. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/InterpolatePy.egg-info/dependency_links.txt +0 -0
  34. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/InterpolatePy.egg-info/requires.txt +0 -0
  35. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/InterpolatePy.egg-info/top_level.txt +0 -0
  36. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/LICENSE +0 -0
  37. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/README.md +0 -0
  38. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/codecov.yml +0 -0
  39. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/CMakeLists.txt +0 -0
  40. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/CMakeLists.txt +0 -0
  41. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_acc_splines.cpp +0 -0
  42. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_bspline.cpp +0 -0
  43. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_cubic_spline.cpp +0 -0
  44. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_motion.cpp +0 -0
  45. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_paths.cpp +0 -0
  46. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_quaternion.cpp +0 -0
  47. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_smoothing_search.cpp +0 -0
  48. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_smoothing_spline.cpp +0 -0
  49. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/bind_tridiagonal.cpp +0 -0
  50. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/bindings/module.cpp +0 -0
  51. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/cmake/InterpolateCppConfig.cmake.in +0 -0
  52. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/cmake/version.hpp.in +0 -0
  53. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/CMakeLists.txt +0 -0
  54. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/bspline_approx_smooth_example.cpp +0 -0
  55. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/bspline_cubic_example.cpp +0 -0
  56. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/bspline_example.cpp +0 -0
  57. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/bspline_interpolator_example.cpp +0 -0
  58. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/concepts_example.cpp +0 -0
  59. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/cubic_smoothing_example.cpp +0 -0
  60. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/cubic_spline_acc1_example.cpp +0 -0
  61. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/cubic_spline_acc2_example.cpp +0 -0
  62. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/cubic_spline_example.cpp +0 -0
  63. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/double_s_example.cpp +0 -0
  64. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/parabolic_linear_example.cpp +0 -0
  65. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/paths_example.cpp +0 -0
  66. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/polynomial_example.cpp +0 -0
  67. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/quaternion_example.cpp +0 -0
  68. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/shared/example_utils.hpp +0 -0
  69. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/examples/trapezoidal_example.cpp +0 -0
  70. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/bspline/approximation_bspline.hpp +0 -0
  71. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/bspline/bspline.hpp +0 -0
  72. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/bspline/bspline_interpolator.hpp +0 -0
  73. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/bspline/bspline_parameters.hpp +0 -0
  74. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/bspline/cubic_bspline_interpolation.hpp +0 -0
  75. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/bspline/smoothing_cubic_bspline.hpp +0 -0
  76. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/concepts.hpp +0 -0
  77. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/config.hpp +0 -0
  78. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/motion/double_s_trajectory.hpp +0 -0
  79. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/motion/motion_types.hpp +0 -0
  80. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/motion/parabolic_blend_trajectory.hpp +0 -0
  81. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/motion/polynomial_trajectory.hpp +0 -0
  82. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/motion/trapezoidal_trajectory.hpp +0 -0
  83. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/path/circular_path.hpp +0 -0
  84. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/path/frenet_frame.hpp +0 -0
  85. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/path/linear_path.hpp +0 -0
  86. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/path/linear_traj.hpp +0 -0
  87. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/quat/log_quaternion_interpolation.hpp +0 -0
  88. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/quat/modified_log_quaternion_interpolation.hpp +0 -0
  89. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/quat/quaternion.hpp +0 -0
  90. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/quat/quaternion_spline.hpp +0 -0
  91. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/quat/squad_c2.hpp +0 -0
  92. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/spline/cubic_smoothing_spline.hpp +0 -0
  93. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/spline/cubic_spline.hpp +0 -0
  94. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/spline/cubic_spline_with_acc1.hpp +0 -0
  95. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/spline/cubic_spline_with_acc2.hpp +0 -0
  96. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/spline/smoothing_search.hpp +0 -0
  97. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/spline/spline_parameters.hpp +0 -0
  98. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/include/interpolatecpp/tridiagonal.hpp +0 -0
  99. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/approximation_bspline.cpp +0 -0
  100. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/bspline.cpp +0 -0
  101. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/circular_path.cpp +0 -0
  102. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/cubic_bspline_interpolation.cpp +0 -0
  103. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/cubic_smoothing_spline.cpp +0 -0
  104. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/cubic_spline.cpp +0 -0
  105. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/cubic_spline_with_acc1.cpp +0 -0
  106. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/cubic_spline_with_acc2.cpp +0 -0
  107. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/double_s_trajectory.cpp +0 -0
  108. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/frenet_frame.cpp +0 -0
  109. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/linear_path.cpp +0 -0
  110. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/linear_traj.cpp +0 -0
  111. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/parabolic_blend_trajectory.cpp +0 -0
  112. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/polynomial_trajectory.cpp +0 -0
  113. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/quaternion.cpp +0 -0
  114. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/quaternion_spline.cpp +0 -0
  115. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/smoothing_cubic_bspline.cpp +0 -0
  116. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/smoothing_search.cpp +0 -0
  117. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/squad_c2.cpp +0 -0
  118. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/src/trapezoidal_trajectory.cpp +0 -0
  119. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/CMakeLists.txt +0 -0
  120. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/shared/test_data.hpp +0 -0
  121. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_bspline.cpp +0 -0
  122. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_concepts.cpp +0 -0
  123. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_cubic_smoothing_spline.cpp +0 -0
  124. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_cubic_spline.cpp +0 -0
  125. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_cubic_spline_with_acc1.cpp +0 -0
  126. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_cubic_spline_with_acc2.cpp +0 -0
  127. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_double_s_trajectory.cpp +0 -0
  128. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_parabolic_blend_trajectory.cpp +0 -0
  129. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_paths.cpp +0 -0
  130. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_polynomial_trajectory.cpp +0 -0
  131. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_quaternion.cpp +0 -0
  132. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_quaternion_spline.cpp +0 -0
  133. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_smoothing_search.cpp +0 -0
  134. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_trapezoidal_trajectory.cpp +0 -0
  135. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/cpp/tests/test_tridiagonal.cpp +0 -0
  136. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/algorithms.md +0 -0
  137. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/api-reference.md +0 -0
  138. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/architecture.md +0 -0
  139. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/assets/extra.css +0 -0
  140. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/assets/extra.js +0 -0
  141. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/changelog.md +0 -0
  142. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/contributing.md +0 -0
  143. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/examples.md +0 -0
  144. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/index.md +0 -0
  145. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/installation.md +0 -0
  146. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/quickstart.md +0 -0
  147. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/troubleshooting.md +0 -0
  148. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/tutorials/motion-profiles.md +0 -0
  149. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/tutorials/path-planning.md +0 -0
  150. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/tutorials/spline-interpolation.md +0 -0
  151. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/docs/user-guide.md +0 -0
  152. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/b_spline_approx_ex.py +0 -0
  153. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/b_spline_cubic_ex.py +0 -0
  154. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/b_spline_ex.py +0 -0
  155. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/b_spline_interpolate_ex.py +0 -0
  156. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/b_spline_smooth_ex.py +0 -0
  157. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/c_s_smoot_search_ex.py +0 -0
  158. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/c_s_smoothing_ex.py +0 -0
  159. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/c_s_with_acc1_ex.py +0 -0
  160. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/c_s_with_acc2_ex.py +0 -0
  161. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/cubic_spline_ex.py +0 -0
  162. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/double_s_ex.py +0 -0
  163. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/frenet_frame_ex.py +0 -0
  164. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/lin_poly_parabolic_ex.py +0 -0
  165. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/linear_ex.py +0 -0
  166. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/main.py +0 -0
  167. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/polynomials_ex.py +0 -0
  168. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/protocols_ex.py +0 -0
  169. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/quat_visualization_ex.py +0 -0
  170. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/simple_paths_ex.py +0 -0
  171. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/squad_c2_ex.py +0 -0
  172. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/examples/trapezoidal_ex.py +0 -0
  173. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/__init__.py +0 -0
  174. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_adapters/__init__.py +0 -0
  175. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_adapters/_bspline.py +0 -0
  176. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_adapters/_direct.py +0 -0
  177. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_adapters/_motion.py +0 -0
  178. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_adapters/_paths.py +0 -0
  179. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_adapters/_quaternion.py +0 -0
  180. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_adapters/_spline.py +0 -0
  181. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_api.py +0 -0
  182. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/_backend.py +0 -0
  183. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/b_spline.py +0 -0
  184. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/b_spline_approx.py +0 -0
  185. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/b_spline_cubic.py +0 -0
  186. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/b_spline_smooth.py +0 -0
  187. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/c_s_smoot_search.py +0 -0
  188. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/c_s_smoothing.py +0 -0
  189. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/c_s_with_acc1.py +0 -0
  190. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/c_s_with_acc2.py +0 -0
  191. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/cubic_spline.py +0 -0
  192. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/double_s.py +0 -0
  193. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/frenet_frame.py +0 -0
  194. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/lin_poly_parabolic.py +0 -0
  195. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/linear.py +0 -0
  196. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/polynomials.py +0 -0
  197. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/protocols.py +0 -0
  198. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/quat_spline.py +0 -0
  199. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/quat_visualization.py +0 -0
  200. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/simple_paths.py +0 -0
  201. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/squad_c2.py +0 -0
  202. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/trapezoidal.py +0 -0
  203. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/interpolatepy/tridiagonal_inv.py +0 -0
  204. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/mkdocs.yml +0 -0
  205. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/setup.cfg +0 -0
  206. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/__init__.py +0 -0
  207. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/inv_test.py +0 -0
  208. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_b_spline.py +0 -0
  209. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_backend.py +0 -0
  210. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_cubic_spline.py +0 -0
  211. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_lin_poly_parabolic.py +0 -0
  212. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_linear.py +0 -0
  213. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_motion_profiles.py +0 -0
  214. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_path_planning.py +0 -0
  215. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_polynomials.py +0 -0
  216. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_protocols.py +0 -0
  217. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_quat_interp.py +0 -0
  218. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_quat_visualization.py +0 -0
  219. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_smoothing.py +0 -0
  220. {interpolatepy-3.0.1 → interpolatepy-3.2.0}/tests/test_squad_c2.py +0 -0
@@ -28,15 +28,15 @@ concurrency:
28
28
  jobs:
29
29
  build:
30
30
  runs-on: ubuntu-latest
31
-
31
+
32
32
  steps:
33
33
  - name: Checkout repository
34
- uses: actions/checkout@v4
34
+ uses: actions/checkout@v6
35
35
  with:
36
36
  fetch-depth: 0
37
-
37
+
38
38
  - name: Install uv
39
- uses: astral-sh/setup-uv@v6
39
+ uses: astral-sh/setup-uv@v8.1.0
40
40
  with:
41
41
  enable-cache: true
42
42
  cache-dependency-glob: uv.lock
@@ -46,14 +46,14 @@ jobs:
46
46
 
47
47
  - name: Build documentation
48
48
  run: uv run mkdocs build --clean --strict
49
-
49
+
50
50
  - name: Setup Pages
51
51
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
52
- uses: actions/configure-pages@v4
53
-
52
+ uses: actions/configure-pages@v6
53
+
54
54
  - name: Upload artifact
55
55
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
56
- uses: actions/upload-pages-artifact@v3
56
+ uses: actions/upload-pages-artifact@v5
57
57
  with:
58
58
  path: ./site
59
59
 
@@ -64,9 +64,9 @@ jobs:
64
64
  url: ${{ steps.deployment.outputs.page_url }}
65
65
  runs-on: ubuntu-latest
66
66
  needs: build
67
-
67
+
68
68
  steps:
69
69
  - name: Deploy to GitHub Pages
70
70
  id: deployment
71
- uses: actions/deploy-pages@v4
71
+ uses: actions/deploy-pages@v5
72
72
 
@@ -9,16 +9,16 @@ jobs:
9
9
  pre-commit:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
- - uses: actions/checkout@v4
12
+ - uses: actions/checkout@v6
13
13
  - name: Install uv
14
- uses: astral-sh/setup-uv@v6
14
+ uses: astral-sh/setup-uv@v8.1.0
15
15
  with:
16
16
  enable-cache: true
17
17
  cache-dependency-glob: uv.lock
18
18
  - name: Sync dependencies
19
19
  run: uv sync --locked --no-default-groups --group dev
20
20
  - name: Cache pre-commit hook environments
21
- uses: actions/cache@v4
21
+ uses: actions/cache@v5
22
22
  with:
23
23
  path: ~/.cache/pre-commit
24
24
  key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
@@ -12,11 +12,11 @@ jobs:
12
12
  contents: read
13
13
  environment: pypi
14
14
  steps:
15
- - uses: actions/checkout@v4
15
+ - uses: actions/checkout@v6
16
16
  with:
17
17
  fetch-depth: 0
18
18
  - name: Install uv
19
- uses: astral-sh/setup-uv@v6
19
+ uses: astral-sh/setup-uv@v8.1.0
20
20
  with:
21
21
  enable-cache: true
22
22
  cache-dependency-glob: uv.lock
@@ -16,9 +16,9 @@ jobs:
16
16
  env:
17
17
  UV_PYTHON: ${{ matrix.python-version }}
18
18
  steps:
19
- - uses: actions/checkout@v4
19
+ - uses: actions/checkout@v6
20
20
  - name: Install uv
21
- uses: astral-sh/setup-uv@v6
21
+ uses: astral-sh/setup-uv@v8.1.0
22
22
  with:
23
23
  enable-cache: true
24
24
  cache-dependency-glob: uv.lock
@@ -27,7 +27,7 @@ jobs:
27
27
  - name: Testing with coverage
28
28
  run: uv run pytest tests --cov=interpolatepy --cov-report=xml --cov-report=term-missing
29
29
  - name: Upload coverage to Codecov
30
- uses: codecov/codecov-action@v5
30
+ uses: codecov/codecov-action@v6
31
31
  with:
32
32
  token: ${{ secrets.CODECOV_TOKEN }}
33
33
  files: ./coverage.xml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: InterpolatePy
3
- Version: 3.0.1
3
+ Version: 3.2.0
4
4
  Summary: A comprehensive Python library for generating smooth trajectories and curves with precise control over position, velocity, acceleration, and jerk profiles
5
5
  Author-email: Giorgio Medico <giorgio.medico11@gmail.com>
6
6
  Maintainer-email: Giorgio Medico <giorgio.medico11@gmail.com>
@@ -152,9 +152,10 @@ examples/double_s_ex.py
152
152
  examples/frenet_frame_ex.py
153
153
  examples/lin_poly_parabolic_ex.py
154
154
  examples/linear_ex.py
155
- examples/log_quat_ex.py
156
155
  examples/log_quat_new_ex.py
156
+ examples/lqi_spiral_frenet_ex.py
157
157
  examples/main.py
158
+ examples/mlqi_spiral_frenet_ex.py
158
159
  examples/polynomials_ex.py
159
160
  examples/protocols_ex.py
160
161
  examples/quat_visualization_ex.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: InterpolatePy
3
- Version: 3.0.1
3
+ Version: 3.2.0
4
4
  Summary: A comprehensive Python library for generating smooth trajectories and curves with precise control over position, velocity, acceleration, and jerk profiles
5
5
  Author-email: Giorgio Medico <giorgio.medico11@gmail.com>
6
6
  Maintainer-email: Giorgio Medico <giorgio.medico11@gmail.com>
@@ -176,22 +176,32 @@ Eigen::MatrixXd BSplineInterpolator::compute_control_points(
176
176
  ++row;
177
177
  }
178
178
 
179
- // Fill remaining rows with natural spline conditions
180
- while (row < total_rows) {
181
- int deriv_order = std::min(p - 1, 2);
182
- double t = (row % 2 == 0) ? times[0] : times[n];
179
+ // Fill remaining rows with natural spline conditions (zero higher
180
+ // derivatives at the endpoints). Walk the (derivative order, endpoint)
181
+ // pairs from order 2 up to p-1, skipping any pair an explicit
182
+ // acceleration already pinned -- reusing one would duplicate a row and
183
+ // make the system rank-deficient.
184
+ const bool pinned[2] = {initial_acceleration_.has_value(),
185
+ final_acceleration_.has_value()};
186
+ for (int deriv_order = 2; deriv_order < p && row < total_rows; ++deriv_order) {
187
+ for (int endpoint = 0; endpoint < 2 && row < total_rows; ++endpoint) {
188
+ if (deriv_order == 2 && pinned[endpoint]) {
189
+ continue;
190
+ }
191
+ const double t = (endpoint == 0) ? times[0] : times[n];
183
192
 
184
- const int span = temp_spline.find_knot_span(t);
185
- const Eigen::MatrixXd ders =
186
- temp_spline.basis_function_derivatives(t, span, deriv_order);
193
+ const int span = temp_spline.find_knot_span(t);
194
+ const Eigen::MatrixXd ders =
195
+ temp_spline.basis_function_derivatives(t, span, deriv_order);
187
196
 
188
- for (int j = 0; j <= p; ++j) {
189
- int col = span - p + j;
190
- if (col >= 0 && col < num_cp) {
191
- a_matrix(row, col) = ders(deriv_order, j);
197
+ for (int j = 0; j <= p; ++j) {
198
+ int col = span - p + j;
199
+ if (col >= 0 && col < num_cp) {
200
+ a_matrix(row, col) = ders(deriv_order, j);
201
+ }
192
202
  }
203
+ ++row;
193
204
  }
194
- ++row;
195
205
  }
196
206
  }
197
207
 
@@ -223,7 +233,10 @@ BSplineInterpolator::BSplineInterpolator(
223
233
 
224
234
  Eigen::MatrixXd pts = points;
225
235
  const int num_points = static_cast<int>(pts.rows());
226
- const int min_points = degree + 1;
236
+ // The system has n+1 interpolation rows plus p-1 (odd p) or p (even p)
237
+ // boundary rows against the same number of control points, so it stays
238
+ // square and full rank down to two points.
239
+ const int min_points = 2;
227
240
 
228
241
  if (num_points < min_points) {
229
242
  throw std::invalid_argument(
@@ -74,9 +74,6 @@ LogQuaternionInterpolation::LogQuaternionInterpolation(
74
74
  if (degree < 3 || degree > 5) {
75
75
  throw std::invalid_argument("Degree must be 3, 4, or 5");
76
76
  }
77
- if (static_cast<int>(quaternions.size()) < degree + 1) {
78
- throw std::invalid_argument("Need at least degree+1 quaternions");
79
- }
80
77
  for (size_t i = 1; i < time_points.size(); ++i) {
81
78
  if (time_points[i] <= time_points[i - 1]) {
82
79
  throw std::invalid_argument("Time points must be strictly increasing");
@@ -45,9 +45,6 @@ ModifiedLogQuaternionInterpolation::ModifiedLogQuaternionInterpolation(
45
45
  if (degree < 3 || degree > 5) {
46
46
  throw std::invalid_argument("Degree must be 3, 4, or 5");
47
47
  }
48
- if (static_cast<int>(quaternions.size()) < degree + 1) {
49
- throw std::invalid_argument("Need at least degree+1 quaternions");
50
- }
51
48
  for (size_t i = 1; i < time_points.size(); ++i) {
52
49
  if (time_points[i] <= time_points[i - 1]) {
53
50
  throw std::invalid_argument("Time points must be strictly increasing");
@@ -9,6 +9,7 @@
9
9
  #include "test_data.hpp"
10
10
 
11
11
  #include <cmath>
12
+ #include <string>
12
13
 
13
14
  using namespace interpolatecpp::bspline;
14
15
  using namespace interpolatecpp::test;
@@ -172,10 +173,24 @@ TEST_CASE("BSplineInterpolator degree validation", "[bspline_interpolator]") {
172
173
  REQUIRE_THROWS_AS(BSplineInterpolator(2, pts), std::invalid_argument);
173
174
  }
174
175
 
175
- SECTION("Too few points") {
176
- Eigen::MatrixXd few_pts(3, 2);
177
- few_pts << 0, 0, 1, 1, 2, 4;
178
- REQUIRE_THROWS_AS(BSplineInterpolator(5, few_pts), std::invalid_argument);
176
+ SECTION("Two points suffice for every degree") {
177
+ Eigen::MatrixXd two_pts(2, 2);
178
+ two_pts << 0, 0, 1, 2;
179
+ for (int degree : {3, 4, 5}) {
180
+ BSplineInterpolator bsi(degree, two_pts);
181
+ auto p0 = bsi.evaluate(bsi.u_min());
182
+ auto p1 = bsi.evaluate(bsi.u_max());
183
+ REQUIRE_THAT(p0(0), WithinAbs(0.0, kNumericalAtol));
184
+ REQUIRE_THAT(p0(1), WithinAbs(0.0, kNumericalAtol));
185
+ REQUIRE_THAT(p1(0), WithinAbs(1.0, kNumericalAtol));
186
+ REQUIRE_THAT(p1(1), WithinAbs(2.0, kNumericalAtol));
187
+ }
188
+ }
189
+
190
+ SECTION("Fewer than two points") {
191
+ Eigen::MatrixXd one_pt(1, 2);
192
+ one_pt << 0, 0;
193
+ REQUIRE_THROWS_AS(BSplineInterpolator(3, one_pt), std::invalid_argument);
179
194
  }
180
195
  }
181
196
 
@@ -556,3 +571,51 @@ TEST_CASE("BSpline variant inheritance", "[bspline_variants]") {
556
571
  REQUIRE(base.evaluate(0.5).allFinite());
557
572
  }
558
573
  }
574
+
575
+ // ===== Explicit acceleration constraints =====
576
+
577
+ // The generated natural boundary rows start at the second derivative, so they
578
+ // must skip whichever endpoint an acceleration already pinned. Reusing one
579
+ // duplicates a row and the QR solve silently returns an inconsistent answer.
580
+ TEST_CASE("BSplineInterpolator honours explicit accelerations", "[bspline_variants]") {
581
+ const auto pts = make_quadratic_2d(5);
582
+ Eigen::VectorXd times(5);
583
+ times << 0, 1, 2, 3, 4;
584
+
585
+ Eigen::VectorXd a0(2);
586
+ a0 << 0.5, -0.5;
587
+ Eigen::VectorXd an(2);
588
+ an << -0.5, 0.5;
589
+ Eigen::VectorXd v0(2);
590
+ v0 << 1.0, 0.0;
591
+
592
+ for (int degree : {3, 4, 5}) {
593
+ SECTION("degree " + std::to_string(degree)) {
594
+ SECTION("initial acceleration only") {
595
+ BSplineInterpolator bsi(degree, pts, times, std::nullopt, std::nullopt, a0);
596
+ for (int i = 0; i < 5; ++i) {
597
+ REQUIRE((bsi.evaluate(times[i]) - pts.row(i).transpose()).norm() < 1e-6);
598
+ }
599
+ REQUIRE((bsi.evaluate_derivative(times[0], 2) - a0).norm() < 1e-6);
600
+ }
601
+
602
+ SECTION("final acceleration only") {
603
+ BSplineInterpolator bsi(degree, pts, times, std::nullopt, std::nullopt,
604
+ std::nullopt, an);
605
+ REQUIRE((bsi.evaluate_derivative(times[4], 2) - an).norm() < 1e-6);
606
+ }
607
+
608
+ SECTION("both accelerations") {
609
+ BSplineInterpolator bsi(degree, pts, times, std::nullopt, std::nullopt, a0, an);
610
+ REQUIRE((bsi.evaluate_derivative(times[0], 2) - a0).norm() < 1e-6);
611
+ REQUIRE((bsi.evaluate_derivative(times[4], 2) - an).norm() < 1e-6);
612
+ }
613
+
614
+ SECTION("initial velocity and acceleration") {
615
+ BSplineInterpolator bsi(degree, pts, times, v0, std::nullopt, a0);
616
+ REQUIRE((bsi.evaluate_derivative(times[0], 1) - v0).norm() < 1e-6);
617
+ REQUIRE((bsi.evaluate_derivative(times[0], 2) - a0).norm() < 1e-6);
618
+ }
619
+ }
620
+ }
621
+ }
@@ -209,4 +209,4 @@ Interpolation near 180-degree rotations can be problematic because the interpola
209
209
 
210
210
  - **[API Reference](../api-reference.md#quaternion)** for complete method documentation
211
211
  - **[Algorithms Guide](../algorithms.md)** for mathematical foundations
212
- - **Example scripts**: `examples/squad_c2_ex.py`, `examples/log_quat_ex.py`, `examples/quat_visualization_ex.py`
212
+ - **Example scripts**: `examples/squad_c2_ex.py`, `examples/log_quat_new_ex.py`, `examples/quat_visualization_ex.py`
@@ -5,9 +5,6 @@ This example demonstrates the LogQuaternionInterpolation class (LQI method)
5
5
  for smooth quaternion trajectory generation using logarithmic quaternion
6
6
  representation with B-spline interpolation.
7
7
 
8
- Note: This example has been updated to use LogQuaternionInterpolation instead
9
- of the deprecated LogQuaternionBSpline class.
10
-
11
8
  Key Features Demonstrated:
12
9
  - Logarithmic quaternion space interpolation for smooth trajectories
13
10
  - Algorithm 1 from Parker et al. (2023) for continuous axis-angle recovery
@@ -27,7 +24,6 @@ Mathematical Background:
27
24
  import matplotlib.pyplot as plt
28
25
  import numpy as np
29
26
 
30
- # Using LogQuaternionInterpolation instead of deprecated LogQuaternionBSpline
31
27
  from interpolatepy.log_quat import LogQuaternionInterpolation, ModifiedLogQuaternionInterpolation
32
28
  from interpolatepy.quat_core import Quaternion
33
29
  from interpolatepy.quat_spline import QuaternionSpline
@@ -0,0 +1,342 @@
1
+ """
2
+ Logarithmic Quaternion Interpolation (LQI) along a cylindrical helix oriented
3
+ with the Frenet frame.
4
+
5
+ Pipeline:
6
+ 1. Define a cylindrical helix p(u) = (r*cos(u), r*sin(u), b*u) with analytical
7
+ derivatives so the Frenet frame can be evaluated in closed form. The radius
8
+ is constant, so curvature and torsion are constant along the curve.
9
+ 2. Sample a sparse set of waypoints along the helix and compute the Frenet
10
+ frame [tangent, normal, binormal] at each waypoint.
11
+ 3. Convert each Frenet rotation matrix to a unit quaternion.
12
+ 4. Feed the (time, quaternion) waypoints to LogQuaternionInterpolation to
13
+ obtain a smooth C^2 orientation trajectory. LQI splines the rotation
14
+ vector r(u) = theta(u) * n_hat(u) as a single 3D quantity (in contrast
15
+ with mLQI, which splines theta and (X, Y, Z) on separate channels).
16
+ 5. Evaluate the position helix and the interpolated orientation at a dense
17
+ set of times and visualize the resulting tool frames side by side with the
18
+ ground-truth Frenet frames.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import matplotlib.pyplot as plt
24
+ import numpy as np
25
+ from mpl_toolkits.mplot3d import Axes3D # noqa: F401 (registers 3D projection)
26
+
27
+ from interpolatepy.frenet_frame import compute_trajectory_frames
28
+ from interpolatepy.frenet_frame import plot_frames
29
+ from interpolatepy.log_quat import LogQuaternionInterpolation
30
+ from interpolatepy.quat_core import Quaternion
31
+ from interpolatepy.quat_visualization import QuaternionTrajectoryVisualizer
32
+
33
+
34
+ def cylindrical_helix_with_derivatives(
35
+ u: float, r: float = 1.0, b: float = 0.3
36
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
37
+ """
38
+ Cylindrical helix (constant radius) position and first two derivatives.
39
+
40
+ p(u) = ( r*cos(u), r*sin(u), b*u )
41
+ dp/du = (-r*sin(u), r*cos(u), b )
42
+ d2p/du2 = (-r*cos(u), -r*sin(u), 0 )
43
+ """
44
+ cos_u = np.cos(u)
45
+ sin_u = np.sin(u)
46
+
47
+ p = np.array([r * cos_u, r * sin_u, b * u])
48
+ dp_du = np.array([-r * sin_u, r * cos_u, b])
49
+ d2p_du2 = np.array([-r * cos_u, -r * sin_u, 0.0])
50
+
51
+ return p, dp_du, d2p_du2
52
+
53
+
54
+ def frame_to_quaternion(frame: np.ndarray) -> Quaternion:
55
+ """
56
+ Convert a 3x3 frame whose columns are [tangent, normal, binormal] into a
57
+ unit quaternion. The frame matrix is itself the rotation matrix from the
58
+ local Frenet basis to the world basis, so we can hand it directly to
59
+ Quaternion.from_rotation_matrix.
60
+ """
61
+ return Quaternion.from_rotation_matrix(frame).unit()
62
+
63
+
64
+ def build_waypoints(
65
+ n_waypoints: int = 10,
66
+ u_min: float = 0.5,
67
+ u_max: float = 6.0 * np.pi,
68
+ r: float = 1.0,
69
+ b: float = 0.3,
70
+ ) -> tuple[np.ndarray, list[Quaternion], np.ndarray, np.ndarray]:
71
+ """
72
+ Sample the helix at n_waypoints values of u, compute Frenet frames there,
73
+ and return (times, quaternions, waypoint_positions, waypoint_frames).
74
+
75
+ Times are taken equal to the parameter u so the orientation evolution
76
+ matches the curve parameterization.
77
+ """
78
+ u_waypoints = np.linspace(u_min, u_max, n_waypoints)
79
+
80
+ def helix_func(u: float) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
81
+ return cylindrical_helix_with_derivatives(u, r=r, b=b)
82
+
83
+ positions, frames = compute_trajectory_frames(helix_func, u_waypoints)
84
+
85
+ quaternions = [frame_to_quaternion(frames[i]) for i in range(len(u_waypoints))]
86
+
87
+ return u_waypoints, quaternions, positions, frames
88
+
89
+
90
+ def evaluate_interpolated_trajectory(
91
+ lqi: LogQuaternionInterpolation,
92
+ u_dense: np.ndarray,
93
+ r: float,
94
+ b: float,
95
+ ) -> tuple[np.ndarray, np.ndarray, list[Quaternion]]:
96
+ """
97
+ Evaluate the helix positions, the LQI-interpolated orientation frames,
98
+ and the underlying interpolated quaternions at the dense parameter values.
99
+ """
100
+ dense_positions = np.zeros((len(u_dense), 3))
101
+ dense_frames = np.zeros((len(u_dense), 3, 3))
102
+ dense_quaternions: list[Quaternion] = []
103
+
104
+ for i, u in enumerate(u_dense):
105
+ dense_positions[i], _, _ = cylindrical_helix_with_derivatives(u, r=r, b=b)
106
+ q = lqi.evaluate(u)
107
+ dense_frames[i] = q.to_rotation_matrix()
108
+ dense_quaternions.append(q)
109
+
110
+ return dense_positions, dense_frames, dense_quaternions
111
+
112
+
113
+ def angular_error_deg(frame_truth: np.ndarray, frame_est: np.ndarray) -> float:
114
+ """
115
+ Geodesic angle (in degrees) between two rotation matrices.
116
+ """
117
+ r = frame_truth.T @ frame_est
118
+ cos_angle = np.clip(0.5 * (np.trace(r) - 1.0), -1.0, 1.0)
119
+ return np.degrees(np.arccos(cos_angle))
120
+
121
+
122
+ def main() -> None:
123
+ print("LQI on a Cylindrical Helix with Frenet Frame Waypoints")
124
+ print("=" * 60)
125
+
126
+ # Helix parameters
127
+ r = 1.0 # constant radius
128
+ b = 0.3 # vertical rise per radian
129
+ u_min = 0.5
130
+ u_max = 6.0 * np.pi
131
+ n_waypoints = 1000
132
+
133
+ # Sample waypoints + their Frenet frames.
134
+ times, quaternions, wp_positions, _wp_frames = build_waypoints(
135
+ n_waypoints=n_waypoints,
136
+ u_min=u_min,
137
+ u_max=u_max,
138
+ r=r,
139
+ b=b,
140
+ )
141
+
142
+ print(f"Sampled {n_waypoints} Frenet waypoints in u ∈ [{u_min:.2f}, {u_max:.2f}]")
143
+ print(f"Time/parameter range used for LQI: [{times[0]:.3f}, {times[-1]:.3f}]")
144
+
145
+ # Build the LQI interpolator over the waypoint orientations.
146
+ lqi = LogQuaternionInterpolation(
147
+ time_points=times,
148
+ quaternions=quaternions,
149
+ degree=3,
150
+ )
151
+
152
+ # Dense evaluation of position + interpolated orientation.
153
+ n_dense = 240
154
+ u_dense = np.linspace(u_min, u_max, n_dense)
155
+ dense_positions, dense_frames_lqi, dense_quaternions_lqi = (
156
+ evaluate_interpolated_trajectory(lqi, u_dense, r=r, b=b)
157
+ )
158
+
159
+ # Ground-truth Frenet frames on the same dense grid, for comparison.
160
+ def helix_func(u: float) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
161
+ return cylindrical_helix_with_derivatives(u, r=r, b=b)
162
+
163
+ _, dense_frames_truth = compute_trajectory_frames(helix_func, u_dense)
164
+
165
+ # Report worst-case angular deviation between interpolated orientation and
166
+ # the analytical Frenet frame.
167
+ errors = np.array([
168
+ angular_error_deg(dense_frames_truth[i], dense_frames_lqi[i])
169
+ for i in range(n_dense)
170
+ ])
171
+ print(f"Mean angular error vs Frenet truth: {errors.mean():.3f} deg")
172
+ print(f"Max angular error vs Frenet truth: {errors.max():.3f} deg")
173
+
174
+ # ------------------------------------------------------------------
175
+ # Visualization
176
+ # ------------------------------------------------------------------
177
+ # Cap the number of waypoint markers actually drawn so the plots stay
178
+ # readable when n_waypoints is large (e.g. 1000). The interpolator still
179
+ # uses all of them — this only affects the visual overlay.
180
+ max_shown_waypoints = 30
181
+ if n_waypoints > max_shown_waypoints:
182
+ show_idx = np.linspace(0, n_waypoints - 1, max_shown_waypoints, dtype=int)
183
+ else:
184
+ show_idx = np.arange(n_waypoints)
185
+ wp_positions_shown = wp_positions[show_idx]
186
+ times_shown = times[show_idx]
187
+ quaternions_shown = [quaternions[i] for i in show_idx]
188
+
189
+ fig = plt.figure(figsize=(16, 7))
190
+
191
+ # Left panel: waypoints + interpolated LQI tool frames.
192
+ ax_left = fig.add_subplot(121, projection="3d")
193
+ plot_frames(ax_left, dense_positions, dense_frames_lqi, scale=0.6, skip=12)
194
+ ax_left.scatter(
195
+ wp_positions_shown[:, 0],
196
+ wp_positions_shown[:, 1],
197
+ wp_positions_shown[:, 2],
198
+ color="magenta",
199
+ s=40,
200
+ depthshade=False,
201
+ label=f"LQI waypoints ({len(show_idx)} of {n_waypoints} shown)",
202
+ )
203
+ ax_left.set_title("Helix + LQI-interpolated frames")
204
+ ax_left.set_xlabel("x")
205
+ ax_left.set_ylabel("y")
206
+ ax_left.set_zlabel("z")
207
+ ax_left.legend(loc="upper left")
208
+
209
+ # Right panel: ground-truth analytical Frenet frames on the dense grid.
210
+ ax_right = fig.add_subplot(122, projection="3d")
211
+ plot_frames(ax_right, dense_positions, dense_frames_truth, scale=0.6, skip=12)
212
+ ax_right.set_title("Analytical Frenet frames (reference)")
213
+ ax_right.set_xlabel("x")
214
+ ax_right.set_ylabel("y")
215
+ ax_right.set_zlabel("z")
216
+
217
+ for ax in (ax_left, ax_right):
218
+ ax.set_box_aspect([1, 1, 1])
219
+
220
+ plt.tight_layout()
221
+
222
+ # Quaternion trajectory in stereographic (MRP) projection space.
223
+ # This shows the orientation evolution in the *rotation* space rather than
224
+ # in the cartesian position space, with the input waypoints highlighted.
225
+ visualizer = QuaternionTrajectoryVisualizer()
226
+ visualizer.plot_3d_trajectory(
227
+ dense_quaternions_lqi,
228
+ waypoints=quaternions_shown,
229
+ waypoint_times=list(times_shown),
230
+ title="LQI quaternion trajectory (stereographic MRP projection)",
231
+ color="purple",
232
+ line_width=2.5,
233
+ point_size=15,
234
+ waypoint_color="magenta",
235
+ show_waypoint_labels=False,
236
+ figsize=(10, 8),
237
+ )
238
+
239
+ # Bottom: angular error along the curve.
240
+ _fig2, ax_err = plt.subplots(figsize=(10, 3.5))
241
+ ax_err.plot(u_dense, errors, color="purple", linewidth=2.0)
242
+ ax_err.fill_between(u_dense, errors, alpha=0.25, color="purple")
243
+ ax_err.set_xlabel("Parameter u")
244
+ ax_err.set_ylabel("Angular error [deg]")
245
+ ax_err.set_title("Geodesic deviation: LQI orientation vs Frenet truth")
246
+ ax_err.grid(True, alpha=0.3)
247
+ plt.tight_layout()
248
+
249
+ # ------------------------------------------------------------------
250
+ # Rotation-vector decomposition produced internally by LQI.
251
+ # LQI splines a single 3D vector r(u) = theta(u) * n_hat(u). We plot
252
+ # both the raw components r_x, r_y, r_z and the (theta, n_hat)
253
+ # decomposition recovered from |r| and r/|r|.
254
+ # ------------------------------------------------------------------
255
+ r_dense = np.array([lqi.bspline_interpolator.evaluate(u) for u in u_dense])
256
+ r_wp = np.array([lqi.bspline_interpolator.evaluate(u) for u in times_shown])
257
+
258
+ theta_dense = np.linalg.norm(r_dense, axis=1)
259
+ theta_wp = np.linalg.norm(r_wp, axis=1)
260
+
261
+ # Unit axis from r / |r|, with a safe fallback near theta ~ 0.
262
+ eps_axis = 1e-12
263
+ safe_theta_dense = np.where(theta_dense > eps_axis, theta_dense, 1.0)
264
+ nhat_dense = r_dense / safe_theta_dense[:, None]
265
+ nhat_dense[theta_dense <= eps_axis] = np.array([1.0, 0.0, 0.0])
266
+
267
+ safe_theta_wp = np.where(theta_wp > eps_axis, theta_wp, 1.0)
268
+ nhat_wp = r_wp / safe_theta_wp[:, None]
269
+ nhat_wp[theta_wp <= eps_axis] = np.array([1.0, 0.0, 0.0])
270
+
271
+ _fig3, axes3 = plt.subplots(4, 1, figsize=(11, 9), sharex=True)
272
+ component_labels = [r"$\theta = \|r\|$ [rad]", r"$\hat n_x$", r"$\hat n_y$", r"$\hat n_z$"]
273
+ component_data = [theta_dense, nhat_dense[:, 0], nhat_dense[:, 1], nhat_dense[:, 2]]
274
+ waypoint_data = [theta_wp, nhat_wp[:, 0], nhat_wp[:, 1], nhat_wp[:, 2]]
275
+ component_colors = ["tab:orange", "tab:red", "tab:green", "tab:blue"]
276
+
277
+ for ax, label, curve, wp_vals, c in zip(
278
+ axes3, component_labels, component_data, waypoint_data, component_colors
279
+ ):
280
+ ax.plot(u_dense, curve, color=c, linewidth=2.0, label="LQI")
281
+ ax.scatter(times_shown, wp_vals, color="magenta", s=25, zorder=5, label="waypoints")
282
+ ax.set_ylabel(label)
283
+ ax.grid(True, alpha=0.3)
284
+ ax.legend(loc="upper right", fontsize=8)
285
+
286
+ axes3[-1].set_xlabel("Parameter u")
287
+ axes3[0].set_title(
288
+ r"LQI internal state recovered from $r(u) = \theta(u)\,\hat n(u)$"
289
+ )
290
+ plt.tight_layout()
291
+
292
+ # Raw rotation-vector components r_x, r_y, r_z (what LQI actually splines).
293
+ _fig3b, axes3b = plt.subplots(3, 1, figsize=(11, 7), sharex=True)
294
+ raw_labels = [r"$r_x$", r"$r_y$", r"$r_z$"]
295
+ raw_colors = ["tab:red", "tab:green", "tab:blue"]
296
+ for ax, label, k, c in zip(axes3b, raw_labels, range(3), raw_colors):
297
+ ax.plot(u_dense, r_dense[:, k], color=c, linewidth=2.0, label="LQI")
298
+ ax.scatter(times_shown, r_wp[:, k], color="magenta", s=25, zorder=5, label="waypoints")
299
+ ax.set_ylabel(label)
300
+ ax.grid(True, alpha=0.3)
301
+ ax.legend(loc="upper right", fontsize=8)
302
+ axes3b[-1].set_xlabel("Parameter u")
303
+ axes3b[0].set_title(r"LQI spline state: rotation-vector components $r(u)$")
304
+ plt.tight_layout()
305
+
306
+ # ------------------------------------------------------------------
307
+ # Physical angular velocity (omega) and acceleration (alpha) in 3D.
308
+ # ------------------------------------------------------------------
309
+ omega = np.zeros((n_dense, 3))
310
+ alpha = np.zeros((n_dense, 3))
311
+ for i, u in enumerate(u_dense):
312
+ omega[i], alpha[i] = lqi.get_physical_kinematics(u)
313
+
314
+ omega_norm = np.linalg.norm(omega, axis=1)
315
+ alpha_norm = np.linalg.norm(alpha, axis=1)
316
+
317
+ _fig4, (ax_w, ax_a) = plt.subplots(2, 1, figsize=(11, 7), sharex=True)
318
+
319
+ for k, axis_name, c in zip(range(3), ("x", "y", "z"), ("tab:red", "tab:green", "tab:blue")):
320
+ ax_w.plot(u_dense, omega[:, k], color=c, linewidth=1.8, label=rf"$\omega_{axis_name}$")
321
+ ax_a.plot(u_dense, alpha[:, k], color=c, linewidth=1.8, label=rf"$\alpha_{axis_name}$")
322
+
323
+ ax_w.plot(u_dense, omega_norm, color="black", linewidth=1.2, linestyle="--", label=r"$\|\omega\|$")
324
+ ax_a.plot(u_dense, alpha_norm, color="black", linewidth=1.2, linestyle="--", label=r"$\|\alpha\|$")
325
+
326
+ ax_w.set_ylabel(r"Angular velocity $\omega$ [rad/u]")
327
+ ax_w.set_title("Physical angular velocity and acceleration from LQI")
328
+ ax_w.legend(loc="upper right", ncol=4, fontsize=9)
329
+ ax_w.grid(True, alpha=0.3)
330
+
331
+ ax_a.set_ylabel(r"Angular acceleration $\alpha$ [rad/u$^2$]")
332
+ ax_a.set_xlabel("Parameter u")
333
+ ax_a.legend(loc="upper right", ncol=4, fontsize=9)
334
+ ax_a.grid(True, alpha=0.3)
335
+
336
+ plt.tight_layout()
337
+
338
+ plt.show()
339
+
340
+
341
+ if __name__ == "__main__":
342
+ main()