|
| 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