Skip to content

Commit 8d58252

Browse files
authored
Merge pull request #272 from abussy-aldebaran/topic/mpc-example
Add MPC test/example.
2 parents ffd71a4 + 08a99ef commit 8d58252

File tree

3 files changed

+213
-1
lines changed

3 files changed

+213
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222

2323
### Added
2424

25+
- Add MPC test/example ([#272](https://github.com/Simple-Robotics/aligator/pull/272))
2526
- Add a collision distance residual for collision pair
2627
- Add a relaxed log-barrier cost function
2728
- Add Nix support ([#268](https://github.com/Simple-Robotics/aligator/pull/268))

tests/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ set(
5454
)
5555

5656
if(PROXSUITE_NLP_WITH_PINOCCHIO_SUPPORT)
57-
list(APPEND TEST_NAMES cycling)
57+
list(APPEND TEST_NAMES cycling mpc-cycle)
5858
endif()
5959

6060
if(BUILD_WITH_PINOCCHIO_SUPPORT AND PINOCCHIO_V3)

tests/mpc-cycle.cpp

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#include "aligator/core/traj-opt-problem.hpp"
2+
#include "aligator/core/traj-opt-data.hpp"
3+
#include "aligator/solvers/proxddp/solver-proxddp.hpp"
4+
#include "aligator/modelling/dynamics/multibody-constraint-fwd.hpp"
5+
#include "aligator/modelling/dynamics/integrator-semi-euler.hpp"
6+
#include "aligator/modelling/multibody/frame-placement.hpp"
7+
#include "aligator/modelling/costs/sum-of-costs.hpp"
8+
#include "aligator/modelling/costs/quad-state-cost.hpp"
9+
10+
#include <pinocchio/multibody/model.hpp>
11+
#include <pinocchio/algorithm/joint-configuration.hpp>
12+
#include <pinocchio/algorithm/frames.hpp>
13+
#include <pinocchio/algorithm/compute-all-terms.hpp>
14+
#include <boost/test/unit_test.hpp>
15+
16+
using namespace aligator;
17+
using context::SolverProxDDP;
18+
19+
namespace {
20+
pinocchio::Model SimpleModel() {
21+
pinocchio::Model model;
22+
23+
const auto joint_id = model.addJoint(0, pinocchio::JointModelPZ(),
24+
pinocchio::SE3::Identity(), "joint");
25+
const auto frame_id = model.addJointFrame(joint_id, -1);
26+
model.addBodyFrame("link", joint_id, pinocchio::SE3::Identity(),
27+
static_cast<int>(frame_id));
28+
29+
Eigen::Vector3d const CoM = Eigen::Vector3d::Zero();
30+
double constexpr mass = 0.001;
31+
Eigen::Matrix3d const I = 0.001 * Eigen::Matrix3d::Identity();
32+
model.appendBodyToJoint(joint_id, pinocchio::Inertia(mass, CoM, I),
33+
pinocchio::SE3::Identity());
34+
35+
return model;
36+
}
37+
38+
using StageSpace = proxsuite::nlp::MultibodyPhaseSpace<double>;
39+
40+
std::tuple<pinocchio::SE3, bool> height(double t) {
41+
double const t0 = std::floor(t);
42+
bool const support = static_cast<int>(t0) % 2 == 0;
43+
44+
pinocchio::SE3 placement = pinocchio::SE3::Identity();
45+
46+
if (!support) {
47+
double constexpr h = 0.02;
48+
double const dt = t - t0;
49+
placement.translation()[2] = h * 4. * dt * (1. - dt);
50+
}
51+
52+
return {placement, support};
53+
}
54+
55+
auto makePositionResidual(pinocchio::Model const &model,
56+
pinocchio::SE3 const &placement) {
57+
return FramePlacementResidualTpl<double>(StageSpace(model).ndx(), model.nv,
58+
model, placement,
59+
model.getFrameId("link"));
60+
}
61+
62+
auto makeCost(double t, pinocchio::Model const &model) {
63+
int const nq = model.nq;
64+
int const nv = model.nv;
65+
int const nx = nq + nv;
66+
int const nu = nv;
67+
const auto stage_space = StageSpace(model);
68+
auto rcost = CostStackTpl<double>(stage_space, nu);
69+
70+
Eigen::VectorXd const x0 = Eigen::VectorXd::Zero(nx);
71+
Eigen::VectorXd const u0 = Eigen::VectorXd::Zero(nu);
72+
73+
Eigen::MatrixXd w_x = Eigen::MatrixXd::Zero(nx, nx);
74+
w_x.diagonal().tail(nv).array() = 0.1;
75+
Eigen::MatrixXd const w_u = 0.01 * Eigen::MatrixXd::Identity(nu, nu);
76+
77+
rcost.addCost("quad_state",
78+
QuadraticStateCostTpl<double>(stage_space, nu, x0, w_x));
79+
rcost.addCost("quad_control",
80+
QuadraticControlCostTpl<double>(stage_space, u0, w_u));
81+
82+
const auto [placement, support] = height(t);
83+
84+
if (!support) {
85+
Eigen::MatrixXd const w_height = 5000. * Eigen::MatrixXd::Identity(6, 6);
86+
const auto frame_fn = makePositionResidual(model, placement);
87+
rcost.addCost("frame_fn", QuadraticResidualCostTpl<double>(
88+
stage_space, frame_fn, w_height));
89+
}
90+
91+
return rcost;
92+
}
93+
94+
auto makeConstraint(pinocchio::Model const &model) {
95+
const auto &frame_id = model.getFrameId("link");
96+
const auto &frame = model.frames[frame_id];
97+
auto constraint = pinocchio::RigidConstraintModel(
98+
pinocchio::ContactType::CONTACT_6D, model, frame.parentJoint,
99+
frame.placement, pinocchio::LOCAL_WORLD_ALIGNED);
100+
constraint.corrector.Kp << 0, 0, 100, 0, 0, 0;
101+
constraint.corrector.Kd << 50, 50, 50, 50, 50, 50;
102+
constraint.name = "contact";
103+
104+
return constraint;
105+
}
106+
107+
auto makeDynamicsModel(double t, double dt, pinocchio::Model const &model) {
108+
auto constraint_models = typename dynamics::MultibodyConstraintFwdDynamicsTpl<
109+
double>::RigidConstraintModelVector();
110+
const auto [placement, support] = height(t);
111+
112+
if (support)
113+
constraint_models.emplace_back(makeConstraint(model)).joint2_placement =
114+
placement;
115+
116+
const StageSpace stage_space(model);
117+
const pinocchio::ProximalSettings proximal_settings(1e-9, 1e-10, 10);
118+
Eigen::MatrixXd actuation_matrix =
119+
Eigen::MatrixXd::Zero(model.nv, model.nv).eval();
120+
actuation_matrix.bottomRows(model.nv).setIdentity();
121+
122+
const dynamics::MultibodyConstraintFwdDynamicsTpl<double> ode(
123+
stage_space, actuation_matrix, constraint_models, proximal_settings);
124+
return dynamics::IntegratorSemiImplEulerTpl<double>(ode, dt);
125+
}
126+
127+
auto makeStage(double t, double dt, pinocchio::Model const &model) {
128+
const auto rcost = makeCost(t, model);
129+
const auto dynModel = makeDynamicsModel(t, dt, model);
130+
131+
return StageModelTpl<double>(rcost, dynModel);
132+
}
133+
134+
} // namespace
135+
136+
BOOST_AUTO_TEST_CASE(test_simple_mpc) {
137+
138+
const auto model = SimpleModel();
139+
auto data = pinocchio::Data(model);
140+
141+
const int nq = model.nq;
142+
const int nv = model.nv;
143+
const int nx = nq + nv;
144+
const int nu = model.nv;
145+
constexpr int nsteps = 10;
146+
constexpr double dt = 0.02;
147+
148+
fmt::print("nq: {:d}, nv: {:d}, nx: {:d}, nu: {:d}\n", nq, nv, nx, nu);
149+
150+
Eigen::VectorXd q = pinocchio::neutral(model);
151+
Eigen::VectorXd vq = Eigen::VectorXd::Zero(nv);
152+
Eigen::VectorXd x = Eigen::VectorXd::Zero(nx);
153+
x << q, vq;
154+
155+
pinocchio::computeAllTerms(model, data, q, vq);
156+
pinocchio::updateFramePlacements(model, data);
157+
158+
const auto term_cost = makeCost(0., model);
159+
auto problem = TrajOptProblemTpl<double>(x, nu, StageSpace(model), term_cost);
160+
161+
for (auto i = 0; i < nsteps; ++i)
162+
problem.addStage(makeStage(0., dt, model));
163+
164+
double constexpr TOL = 1e-7;
165+
unsigned int constexpr max_iters = 100u;
166+
double constexpr mu_init = 1e-8;
167+
constexpr VerboseLevel verbosity = QUIET;
168+
169+
auto ddp = SolverProxDDPTpl<double>(TOL, mu_init, max_iters, verbosity);
170+
ddp.rollout_type_ = RolloutType::LINEAR;
171+
ddp.sa_strategy_ = StepAcceptanceStrategy::LINESEARCH_NONMONOTONE;
172+
ddp.filter_.beta_ = 1e-5;
173+
ddp.force_initial_condition_ = true;
174+
ddp.reg_min = 1e-6;
175+
ddp.linear_solver_choice = LQSolverChoice::SERIAL;
176+
177+
ddp.setup(problem);
178+
179+
bool converged = ddp.run(problem);
180+
BOOST_CHECK(converged);
181+
182+
for (auto t = 0.; t < 5.; t += dt) {
183+
const auto t0 = std::chrono::steady_clock::now();
184+
const auto x = ddp.results_.xs[1];
185+
q = x.head(nq);
186+
vq = x.tail(nv);
187+
pinocchio::computeAllTerms(model, data, q, vq);
188+
pinocchio::updateFramePlacements(model, data);
189+
190+
const auto stage = makeStage(t, dt, model);
191+
problem.replaceStageCircular(stage);
192+
ddp.cycleProblem(problem, stage.createData());
193+
problem.term_cost_ = makeCost(t + dt, model);
194+
ddp.workspace_.problem_data.term_cost_data =
195+
problem.term_cost_->createData();
196+
problem.setInitState(x);
197+
198+
bool converged = ddp.run(problem);
199+
BOOST_CHECK(converged);
200+
const auto [expected, support] =
201+
height(std::max(0., t - (nsteps - 1) * dt));
202+
const auto &actual = data.oMf[model.getFrameId("link")];
203+
BOOST_CHECK((actual.inverse() * expected).isIdentity(2e-3));
204+
BOOST_CHECK_SMALL(actual.translation()[2] - expected.translation()[2],
205+
2e-3);
206+
207+
const auto tf = std::chrono::steady_clock::now();
208+
const auto time = std::chrono::duration<double, std::milli>(tf - t0);
209+
fmt::print("Elapsed time: {} ms\n", time.count());
210+
}
211+
}

0 commit comments

Comments
 (0)