Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,13 @@ venv.bak/
.history

CLAUDE.md

################################
########### C++ BUILD ##########
################################
cpp/build/
*.o
*.a
*.so
*.dylib
compile_commands.json
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ repos:
- id: check-toml

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.12.8'
rev: 'v0.15.7'
hooks:
- id: ruff
types_or: [python, pyi, jupyter]
args: [ --fix, --exit-non-zero-on-fix, --preview ]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.17.1
rev: v1.19.1
hooks:
- id: mypy
args: [--ignore-missing-imports]
Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# InterpolatePy

![Python](https://img.shields.io/badge/python-3.10+-blue)
![Python](https://img.shields.io/badge/python-3.11+-blue)
[![PyPI Downloads](https://static.pepy.tech/badge/interpolatepy)](https://pepy.tech/projects/interpolatepy)
[![pre-commit](https://github.com/GiorgioMedico/InterpolatePy/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/GiorgioMedico/InterpolatePy/actions/workflows/pre-commit.yml)
[![ci-test](https://github.com/GiorgioMedico/InterpolatePy/actions/workflows/test.yml/badge.svg)](https://github.com/GiorgioMedico/InterpolatePy/actions/workflows/test.yml)
Expand All @@ -10,9 +10,9 @@

InterpolatePy provides 20+ algorithms for smooth trajectory generation with precise control over position, velocity, acceleration, and jerk. From cubic splines and B-curves to quaternion interpolation and S-curve motion profiles — everything you need for professional motion control.

**⚡ Fast:** Vectorized NumPy operations, ~1ms for 1000-point cubic splines
**🎯 Precise:** Research-grade algorithms with C² continuity and bounded derivatives
**📊 Visual:** Built-in plotting for every algorithm
**⚡ Fast:** Optional C++ backend with pybind11; pure-Python fallback uses vectorized NumPy
**🎯 Precise:** Research-grade algorithms with C² continuity and bounded derivatives
**📊 Visual:** Built-in plotting for every algorithm
**🔧 Complete:** Splines, motion profiles, quaternions, and path planning in one library

---
Expand All @@ -23,7 +23,7 @@ InterpolatePy provides 20+ algorithms for smooth trajectory generation with prec
pip install InterpolatePy
```

**Requirements:** Python ≥3.10, NumPy ≥2.0, SciPy ≥1.15, Matplotlib ≥3.10
**Requirements:** Python ≥3.11, NumPy ≥2.3, SciPy ≥1.16, Matplotlib ≥3.10.5

<details>
<summary><strong>Development Installation</strong></summary>
Expand Down Expand Up @@ -222,12 +222,23 @@ plt.show()

## Performance & Quality

- **Fast:** Vectorized NumPy operations, optimized algorithms
- **Reliable:** 85%+ test coverage, continuous integration
- **Modern:** Python 3.10+, strict typing, dataclass-based APIs
- **Fast:** Optional C++ backend (Eigen + pybind11) for maximum performance; pure-Python fallback uses vectorized NumPy
- **Reliable:** 85%+ test coverage, continuous integration, 142 additional C++ unit tests
- **Modern:** Python 3.11+, strict typing, dataclass-based APIs
- **Research-grade:** Peer-reviewed algorithms from robotics literature

**Typical Performance:**
**C++ Backend:**

InterpolatePy includes an optional compiled C++ extension for performance-critical workloads. The API is identical regardless of backend:

```python
import interpolatepy
print(f"C++ backend: {interpolatepy.HAS_CPP}") # True if extension is available
```

Set `INTERPOLATEPY_NO_CPP=1` to force pure-Python mode.

**Typical Performance (pure-Python):**
- Cubic spline (1000 points): ~1ms
- B-spline evaluation (10k points): ~5ms
- S-curve trajectory planning: ~0.5ms
Expand Down Expand Up @@ -256,6 +267,8 @@ ruff format interpolatepy/
ruff check interpolatepy/
mypy interpolatepy/

# Run all pre-commit hooks
pre-commit run --all-files
```
</details>

Expand Down
169 changes: 169 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
cmake_minimum_required(VERSION 3.21)
project(InterpolateCpp
VERSION 0.1.0
LANGUAGES CXX
DESCRIPTION "C++ port of InterpolatePy trajectory planning library"
)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Options
option(INTERPOLATECPP_BUILD_TESTS "Build tests" ON)
option(INTERPOLATECPP_BUILD_BINDINGS "Build pybind11 bindings" OFF)
option(INTERPOLATECPP_BUILD_EXAMPLES "Build examples" OFF)

# Version macros
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.hpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/include/interpolatecpp/version.hpp"
)

# Dependencies via FetchContent
include(FetchContent)

FetchContent_Declare(
Eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4.0
GIT_SHALLOW TRUE
SYSTEM
)

if(INTERPOLATECPP_BUILD_TESTS)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.7.1
GIT_SHALLOW TRUE
)
endif()

if(INTERPOLATECPP_BUILD_BINDINGS)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.13.6
GIT_SHALLOW TRUE
)
endif()

# Make Eigen available (suppress its tests and docs)
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(EIGEN_BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(EIGEN_BUILD_DOC OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(Eigen)
set(BUILD_TESTING ON CACHE BOOL "" FORCE)

# Library sources
set(INTERPOLATECPP_SOURCES
src/cubic_spline.cpp
src/cubic_smoothing_spline.cpp
src/cubic_spline_with_acc1.cpp
src/cubic_spline_with_acc2.cpp
src/smoothing_search.cpp
# Phase 2: B-spline
src/bspline.cpp
src/cubic_bspline_interpolation.cpp
src/bspline_interpolator.cpp
src/approximation_bspline.cpp
src/smoothing_cubic_bspline.cpp
# Phase 3: Motion
src/polynomial_trajectory.cpp
src/double_s_trajectory.cpp
src/trapezoidal_trajectory.cpp
src/parabolic_blend_trajectory.cpp
# Phase 4: Quaternion
src/quaternion.cpp
src/quaternion_spline.cpp
src/squad_c2.cpp
src/log_quaternion_interpolation.cpp
src/modified_log_quaternion_interpolation.cpp
# Phase 5: Path
src/linear_path.cpp
src/circular_path.cpp
src/frenet_frame.cpp
src/linear_traj.cpp
)

add_library(interpolatecpp ${INTERPOLATECPP_SOURCES})
add_library(interpolatecpp::interpolatecpp ALIAS interpolatecpp)

target_include_directories(interpolatecpp
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)

target_link_libraries(interpolatecpp PUBLIC Eigen3::Eigen)

set_target_properties(interpolatecpp PROPERTIES
POSITION_INDEPENDENT_CODE ON
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)

target_compile_options(interpolatecpp PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:
-Wall -Wextra -Wpedantic -Wconversion -Wshadow
>
$<$<CXX_COMPILER_ID:MSVC>:
/W4
>
)

# Tests
if(INTERPOLATECPP_BUILD_TESTS)
enable_testing()
FetchContent_MakeAvailable(Catch2)
add_subdirectory(tests)
endif()

# Bindings
if(INTERPOLATECPP_BUILD_BINDINGS)
FetchContent_MakeAvailable(pybind11)
add_subdirectory(bindings)
endif()

# Examples
if(INTERPOLATECPP_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

# Install rules
include(GNUInstallDirs)

install(TARGETS interpolatecpp
EXPORT InterpolateCppTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

install(DIRECTORY include/interpolatecpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/interpolatecpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

install(EXPORT InterpolateCppTargets
FILE InterpolateCppTargets.cmake
NAMESPACE interpolatecpp::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/InterpolateCpp
)

include(CMakePackageConfigHelpers)
configure_package_config_file(
cmake/InterpolateCppConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/InterpolateCppConfig.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/InterpolateCpp
)

install(FILES "${CMAKE_CURRENT_BINARY_DIR}/InterpolateCppConfig.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/InterpolateCpp
)
26 changes: 26 additions & 0 deletions cpp/bindings/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
pybind11_add_module(interpolatecpp_py
module.cpp
# Phase 1: Cubic Splines
bind_tridiagonal.cpp
bind_cubic_spline.cpp
bind_smoothing_spline.cpp
bind_acc_splines.cpp
bind_smoothing_search.cpp
# Phase 2: B-Splines
bind_bspline.cpp
# Phase 3: Motion Profiles
bind_motion.cpp
# Phase 4: Quaternion Interpolation
bind_quaternion.cpp
# Phase 5: Geometric Paths
bind_paths.cpp
)

target_link_libraries(interpolatecpp_py
PRIVATE
interpolatecpp::interpolatecpp
)

install(TARGETS interpolatecpp_py
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
96 changes: 96 additions & 0 deletions cpp/bindings/bind_acc_splines.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include <interpolatecpp/spline/cubic_spline_with_acc1.hpp>
#include <interpolatecpp/spline/cubic_spline_with_acc2.hpp>

namespace py = pybind11;
using namespace interpolatecpp::spline;

void bind_acc_splines(py::module_& m) {
// SplineParameters
py::class_<SplineParameters>(m, "SplineParameters")
.def(py::init([](double v0, double vn, std::optional<double> a0, std::optional<double> an,
bool debug) {
return SplineParameters{v0, vn, a0, an, debug};
}),
py::arg("v0") = 0.0, py::arg("vn") = 0.0, py::arg("a0") = std::nullopt,
py::arg("an") = std::nullopt, py::arg("debug") = false)
.def_readwrite("v0", &SplineParameters::v0)
.def_readwrite("vn", &SplineParameters::vn)
.def_readwrite("a0", &SplineParameters::a0)
.def_readwrite("an", &SplineParameters::an)
.def_readwrite("debug", &SplineParameters::debug);

// CubicSplineWithAcceleration1
py::class_<CubicSplineWithAcceleration1>(m, "CubicSplineWithAcceleration1")
.def(py::init([](std::vector<double> t, std::vector<double> q, double v0, double vn,
double a0, double an, bool debug) {
return CubicSplineWithAcceleration1(t, q, v0, vn, a0, an, debug);
}),
py::arg("t_points"), py::arg("q_points"), py::arg("v0") = 0.0,
py::arg("vn") = 0.0, py::arg("a0") = 0.0, py::arg("an") = 0.0,
py::arg("debug") = false)
.def("evaluate",
py::overload_cast<double>(&CubicSplineWithAcceleration1::evaluate, py::const_),
py::arg("t"))
.def("evaluate",
py::overload_cast<const Eigen::VectorXd&>(
&CubicSplineWithAcceleration1::evaluate, py::const_),
py::arg("t"))
.def("evaluate_velocity",
py::overload_cast<double>(&CubicSplineWithAcceleration1::evaluate_velocity,
py::const_),
py::arg("t"))
.def("evaluate_velocity",
py::overload_cast<const Eigen::VectorXd&>(
&CubicSplineWithAcceleration1::evaluate_velocity, py::const_),
py::arg("t"))
.def("evaluate_acceleration",
py::overload_cast<double>(&CubicSplineWithAcceleration1::evaluate_acceleration,
py::const_),
py::arg("t"))
.def("evaluate_acceleration",
py::overload_cast<const Eigen::VectorXd&>(
&CubicSplineWithAcceleration1::evaluate_acceleration, py::const_),
py::arg("t"))
.def_property_readonly("t_points", &CubicSplineWithAcceleration1::t_points)
.def_property_readonly("q_points", &CubicSplineWithAcceleration1::q_points)
.def_property_readonly("omega", &CubicSplineWithAcceleration1::omega)
.def_property_readonly("n_points", &CubicSplineWithAcceleration1::n_points)
.def_property_readonly("n_orig", &CubicSplineWithAcceleration1::n_orig);

// CubicSplineWithAcceleration2
py::class_<CubicSplineWithAcceleration2, CubicSpline>(m, "CubicSplineWithAcceleration2")
.def(py::init([](std::vector<double> t, std::vector<double> q, SplineParameters params) {
return CubicSplineWithAcceleration2(t, q, params);
}),
py::arg("t_points"), py::arg("q_points"),
py::arg("params") = SplineParameters{})
.def("evaluate",
py::overload_cast<double>(&CubicSplineWithAcceleration2::evaluate, py::const_),
py::arg("t"))
.def("evaluate",
py::overload_cast<const Eigen::VectorXd&>(
&CubicSplineWithAcceleration2::evaluate, py::const_),
py::arg("t"))
.def("evaluate_velocity",
py::overload_cast<double>(&CubicSplineWithAcceleration2::evaluate_velocity,
py::const_),
py::arg("t"))
.def("evaluate_velocity",
py::overload_cast<const Eigen::VectorXd&>(
&CubicSplineWithAcceleration2::evaluate_velocity, py::const_),
py::arg("t"))
.def("evaluate_acceleration",
py::overload_cast<double>(&CubicSplineWithAcceleration2::evaluate_acceleration,
py::const_),
py::arg("t"))
.def("evaluate_acceleration",
py::overload_cast<const Eigen::VectorXd&>(
&CubicSplineWithAcceleration2::evaluate_acceleration, py::const_),
py::arg("t"))
.def_property_readonly("has_quintic_first", &CubicSplineWithAcceleration2::has_quintic_first)
.def_property_readonly("has_quintic_last", &CubicSplineWithAcceleration2::has_quintic_last);
}
Loading
Loading