Skip to content

Commit

Permalink
Add and test car model
Browse files Browse the repository at this point in the history
  • Loading branch information
astomodynamics committed Dec 11, 2024
1 parent f743b0d commit 75a3bdf
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 12 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ set(dynamics_model_srcs
src/dynamics_model/dubins_car.cpp
src/dynamics_model/bicycle.cpp
src/dynamics_model/cartpole.cpp
src/dynamics_model/car.cpp
src/dynamics_model/quadrotor.cpp
src/dynamics_model/manipulator.cpp
src/dynamics_model/spacecraft_linear.cpp
Expand Down
8 changes: 4 additions & 4 deletions include/cddp-cpp/dynamics_model/car.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ class Car : public DynamicalSystem {
* @return State derivative vector
*/
Eigen::VectorXd getContinuousDynamics(const Eigen::VectorXd& state,
const Eigen::VectorXd& control) const override;
const Eigen::VectorXd& control) const override {
return DynamicalSystem::getContinuousDynamics(state, control);
}

/**
* @brief Computes the discrete-time dynamics using the specified integration method
Expand All @@ -71,9 +73,7 @@ class Car : public DynamicalSystem {
* @return Next state vector
*/
Eigen::VectorXd getDiscreteDynamics(const Eigen::VectorXd& state,
const Eigen::VectorXd& control) const override {
return DynamicalSystem::getDiscreteDynamics(state, control);
}
const Eigen::VectorXd& control) const override;

/**
* @brief Computes the Jacobian of the dynamics with respect to the state
Expand Down
145 changes: 145 additions & 0 deletions src/dynamics_model/car.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include "dynamics_model/car.hpp"
#include <cmath>

namespace cddp {

Car::Car(double timestep, double wheelbase, std::string integration_type)
: DynamicalSystem(STATE_DIM, CONTROL_DIM, timestep, integration_type),
wheelbase_(wheelbase) {
}

Eigen::VectorXd Car::getDiscreteDynamics(
const Eigen::VectorXd& state, const Eigen::VectorXd& control) const {

// Extract states
const double x = state(STATE_X); // x position
const double y = state(STATE_Y); // y position
const double theta = state(STATE_THETA); // car angle
const double v = state(STATE_V); // velocity

// Extract controls
const double delta = control(CONTROL_DELTA); // steering angle
const double a = control(CONTROL_A); // acceleration

// Constants
const double d = wheelbase_; // distance between back and front axles
const double h = timestep_; // timestep

// Compute unit vector in car direction
const double cos_theta = std::cos(theta);
const double sin_theta = std::sin(theta);
Eigen::Vector2d z(cos_theta, sin_theta);

// Front wheel rolling distance
const double f = h * v;

// Back wheel rolling distance
// b = d + f*cos(w) - sqrt(d^2 - (f*sin(w))^2)
const double b = d + f * std::cos(delta) -
std::sqrt(d*d - std::pow(f * std::sin(delta), 2));

// Change in car angle
// dtheta = asin(sin(w)*f/d)
const double dtheta = std::asin(std::sin(delta) * f / d);

// Compute state change
Eigen::VectorXd dy = Eigen::VectorXd::Zero(STATE_DIM);
dy(STATE_X) = b * cos_theta;
dy(STATE_Y) = b * sin_theta;
dy(STATE_THETA) = dtheta;
dy(STATE_V) = h * a;

// Return next state
return state + dy;
}

Eigen::MatrixXd Car::getStateJacobian(
const Eigen::VectorXd& state, const Eigen::VectorXd& control) const {

const int n = STATE_DIM;
Eigen::MatrixXd J(n, n);
const double h = 2e-17; // Small perturbation value

// Get nominal output
Eigen::VectorXd f0 = getDiscreteDynamics(state, control);

// Compute Jacobian column by column using finite differences
Eigen::VectorXd perturbed_state = state;
for (int i = 0; i < n; ++i) {
// Perturb state element
perturbed_state(i) = state(i) + h;

// Get perturbed output
Eigen::VectorXd f1 = getDiscreteDynamics(perturbed_state, control);

// Compute derivative using central difference
J.col(i) = (f1 - f0) / h;

// Reset perturbation
perturbed_state(i) = state(i);
}

// Expected output of the state Jacobian is continuous dynamics Jacobian so modify this discrete one
// Ref: in cddp_core.cpp
/*
// Convert continuous dynamics to discrete time
A = timestep_ * Fx;
A.diagonal().array() += 1.0; // More efficient way to add identity
B = timestep_ * Fu;
*/
J.diagonal().array() -= 1.0;
J /= timestep_;
return J;
}

Eigen::MatrixXd Car::getControlJacobian(
const Eigen::VectorXd& state, const Eigen::VectorXd& control) const {

const int n = STATE_DIM;
const int m = CONTROL_DIM;
Eigen::MatrixXd J(n, m);
const double h = 2e-17; // Small perturbation value

// Get nominal output
Eigen::VectorXd f0 = getDiscreteDynamics(state, control);

// Compute Jacobian column by column using finite differences
Eigen::VectorXd perturbed_control = control;
for (int i = 0; i < m; ++i) {
// Perturb control element
perturbed_control(i) = control(i) + h;

// Get perturbed output
Eigen::VectorXd f1 = getDiscreteDynamics(state, perturbed_control);

// Compute derivative using forward difference
J.col(i) = (f1 - f0) / h;

// Reset perturbation
perturbed_control(i) = control(i);
}

// Expected output of the control Jacobian is continuous dynamics Jacobian so modify this discrete one
// Ref: in cddp_core.cpp
/*
// Convert continuous dynamics to discrete time
A = timestep_ * Fx;
A.diagonal().array() += 1.0; // More efficient way to add identity
B = timestep_ * Fu;
*/
J /= timestep_;

return J;
}

Eigen::MatrixXd Car::getStateHessian(
const Eigen::VectorXd& state, const Eigen::VectorXd& control) const {
return Eigen::MatrixXd::Zero(STATE_DIM, STATE_DIM);
}

Eigen::MatrixXd Car::getControlHessian(
const Eigen::VectorXd& state, const Eigen::VectorXd& control) const {
return Eigen::MatrixXd::Zero(CONTROL_DIM, CONTROL_DIM);
}

} // namespace cddp
20 changes: 12 additions & 8 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@

add_executable(test_pendulum dynamics_model/test_pendulum.cpp)
target_link_libraries(test_pendulum gtest gmock gtest_main cddp Eigen3::Eigen)
target_link_libraries(test_pendulum gtest gmock gtest_main cddp)
gtest_discover_tests(test_pendulum)

add_executable(test_dubins_car dynamics_model/test_dubins_car.cpp)
target_link_libraries(test_dubins_car gtest gmock gtest_main cddp Eigen3::Eigen)
target_link_libraries(test_dubins_car gtest gmock gtest_main cddp)
gtest_discover_tests(test_dubins_car)

add_executable(test_bicycle dynamics_model/test_bicycle.cpp)
target_link_libraries(test_bicycle gtest gmock gtest_main cddp Eigen3::Eigen)
target_link_libraries(test_bicycle gtest gmock gtest_main cddp)
gtest_discover_tests(test_bicycle)

add_executable(test_cartpole dynamics_model/test_cartpole.cpp)
target_link_libraries(test_cartpole gtest gmock gtest_main cddp Eigen3::Eigen)
target_link_libraries(test_cartpole gtest gmock gtest_main cddp)
gtest_discover_tests(test_cartpole)

add_executable(test_car dynamics_model/test_car.cpp)
target_link_libraries(test_car gtest gmock gtest_main cddp)
gtest_discover_tests(test_car)

add_executable(test_quadrotor dynamics_model/test_quadrotor.cpp)
target_link_libraries(test_quadrotor gtest gmock gtest_main cddp Eigen3::Eigen)
target_link_libraries(test_quadrotor gtest gmock gtest_main cddp)
gtest_discover_tests(test_quadrotor)

add_executable(test_manipulator dynamics_model/test_manipulator.cpp)
target_link_libraries(test_manipulator gtest gmock gtest_main cddp Eigen3::Eigen)
target_link_libraries(test_manipulator gtest gmock gtest_main cddp)
gtest_discover_tests(test_manipulator)

add_executable(test_spacecraft_linear dynamics_model/test_spacecraft_linear.cpp)
target_link_libraries(test_spacecraft_linear gtest gmock gtest_main cddp Eigen3::Eigen)
target_link_libraries(test_spacecraft_linear gtest gmock gtest_main cddp)
gtest_discover_tests(test_spacecraft_linear)

add_executable(test_objective cddp_core/test_objective.cpp)
Expand Down Expand Up @@ -57,7 +61,7 @@ gtest_discover_tests(test_finite_difference)

# # Test for eigen vs torch
add_executable(test_eigen test_eigen.cpp)
target_link_libraries(test_eigen gtest gmock gtest_main Eigen3::Eigen)
target_link_libraries(test_eigen gtest gmock gtest_main cddp)
gtest_discover_tests(test_eigen)


Expand Down
96 changes: 96 additions & 0 deletions tests/dynamics_model/test_car.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
Copyright 2024 Tomo Sasaki
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Description: Test the car dynamics model.
#include <iostream>
#include <vector>
#include <filesystem>

#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include "dynamics_model/car.hpp"
#include "matplotlibcpp.hpp"

namespace plt = matplotlibcpp;
namespace fs = std::filesystem;
using namespace cddp;

TEST(CarTest, DiscreteDynamics) {
// Create a car instance
double timestep = 0.03; // From original MATLAB code
double wheelbase = 2.0; // From original MATLAB code
std::string integration_type = "euler";
cddp::Car car(timestep, wheelbase, integration_type);

// Store states for plotting
std::vector<double> time_data, x_data, y_data, theta_data, v_data;

// Initial state and control (from MATLAB demo)
Eigen::VectorXd state(4);
state << 1.0, 1.0, 3*M_PI/2, 0.0; // Initial state from MATLAB demo
Eigen::VectorXd control(2);
control << 0.1, 0.1; // Small steering angle and acceleration

// Simulate for a few steps
int num_steps = 100;
for (int i = 0; i < num_steps; ++i) {
// Store data for plotting
time_data.push_back(i * timestep);
x_data.push_back(state[0]);
y_data.push_back(state[1]);
theta_data.push_back(state[2]);
v_data.push_back(state[3]);

// Compute the next state
state = car.getDiscreteDynamics(state, control);
}

// Basic assertions
ASSERT_EQ(car.getStateDim(), 4);
ASSERT_EQ(car.getControlDim(), 2);
ASSERT_DOUBLE_EQ(car.getTimestep(), 0.03);
ASSERT_EQ(car.getIntegrationType(), "euler");

// // Create directory for saving plot (if it doesn't exist)
// const std::string plotDirectory = "../plots/test";
// if (!fs::exists(plotDirectory)) {
// fs::create_directory(plotDirectory);
// }

// // Plot the results
// // Plot trajectory in X-Y plane
// plt::figure(1);
// plt::plot(x_data, y_data, {{"label", "Trajectory"}});
// plt::xlabel("X Position");
// plt::ylabel("Y Position");
// plt::legend();
// plt::save(plotDirectory + "/car_trajectory.png");

// // Plot states over time
// plt::figure(2);
// plt::plot(time_data, theta_data, {{"label", "Heading"}});
// plt::plot(time_data, v_data, {{"label", "Velocity"}});
// plt::xlabel("Time");
// plt::ylabel("State");
// plt::legend();
// plt::save(plotDirectory + "/car_states.png");
// plt::show();
}

int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

0 comments on commit 75a3bdf

Please sign in to comment.