From 389e25880ca0b5659e3cec07f6c010b46429eef9 Mon Sep 17 00:00:00 2001
From: asanjuan <asanjuan@iri.upc.edu>
Date: Thu, 4 Aug 2022 17:30:15 +0200
Subject: [PATCH] processor_ftipd modification (fixing errors) and new gtests

---
 .clang-format                                 |   2 +-
 ..._force_torque_inertial_preint_dynamics.cpp | 130 ++++++---
 src/sensor/sensor_force_torque_inertial.cpp   |   7 +-
 test/CMakeLists.txt                           |   2 +
 ..._force_torque_inertial_preint_dynamics.cpp |   2 +-
 ...problem_force_torque_inertial_dynamics.cpp | 255 ++++++++++++++++++
 ...e_torque_inertial_dynamics_solve_test.yaml |  56 ++++
 7 files changed, 415 insertions(+), 39 deletions(-)
 create mode 100644 test/gtest_solve_problem_force_torque_inertial_dynamics.cpp
 create mode 100644 test/yaml/problem_force_torque_inertial_dynamics_solve_test.yaml

diff --git a/.clang-format b/.clang-format
index d7ec3ad..8f284dd 100644
--- a/.clang-format
+++ b/.clang-format
@@ -19,7 +19,7 @@ AlwaysBreakAfterDefinitionReturnType: None
 AlwaysBreakAfterReturnType: None
 AlwaysBreakBeforeMultilineStrings: true
 AlwaysBreakTemplateDeclarations: true
-BinPackArguments: true
+BinPackArguments: false
 BinPackParameters: false
 BreakBeforeBraces: Custom
 BraceWrapping:
diff --git a/src/processor/processor_force_torque_inertial_preint_dynamics.cpp b/src/processor/processor_force_torque_inertial_preint_dynamics.cpp
index 81156be..3a73a02 100644
--- a/src/processor/processor_force_torque_inertial_preint_dynamics.cpp
+++ b/src/processor/processor_force_torque_inertial_preint_dynamics.cpp
@@ -68,12 +68,8 @@ CaptureMotionPtr ProcessorForceTorqueInertialPreintDynamics::emplaceCapture(cons
                                                                             const VectorXd&       _calib_preint,
                                                                             const CaptureBasePtr& _capture_origin_ptr)
 {
-    CaptureMotionPtr capture = CaptureBase::emplace<CaptureMotion> (_frame_own, 
-                                                                    "CaptureMotion", _ts,
-                                                                    _sensor, 
-                                                                    _data, 
-                                                                    _data_cov, 
-                                                                    _capture_origin_ptr);
+    CaptureMotionPtr capture = CaptureBase::emplace<CaptureMotion>(
+        _frame_own, "CaptureMotion", _ts, _sensor, _data, _data_cov, _capture_origin_ptr);
     setCalibration(capture, _calib);
     capture->setCalibrationPreint(_calib_preint);
     return capture;
@@ -81,33 +77,32 @@ CaptureMotionPtr ProcessorForceTorqueInertialPreintDynamics::emplaceCapture(cons
 
 FeatureBasePtr ProcessorForceTorqueInertialPreintDynamics::emplaceFeature(CaptureMotionPtr _capture_own)
 {
-    auto motion = _capture_own->getBuffer().back();
-    FeatureBasePtr ftr = FeatureBase::emplace<FeatureMotion>(
-                        _capture_own,
-                        "FeatureMotion",
-                        motion.delta_integr_,
-                        motion.delta_integr_cov_,
-                        _capture_own->getCalibrationPreint(),
-                        motion.jacobian_calib_);
+    auto           motion = _capture_own->getBuffer().back();
+    FeatureBasePtr ftr    = FeatureBase::emplace<FeatureMotion>(_capture_own,
+                                                             "FeatureMotion",
+                                                             motion.delta_integr_,
+                                                             motion.delta_integr_cov_,
+                                                             _capture_own->getCalibrationPreint(),
+                                                             motion.jacobian_calib_);
     return ftr;
 }
 
 FactorBasePtr ProcessorForceTorqueInertialPreintDynamics::emplaceFactor(FeatureBasePtr _feature_motion,
                                                                         CaptureBasePtr _capture_origin)
 {
-    
     FeatureMotionPtr ftr_ftipd = std::static_pointer_cast<FeatureMotion>(_feature_motion);
 
-    auto fac_ftipd = FactorBase::emplace<FactorForceTorqueInertialDynamics>(_feature_motion, ftr_ftipd, _capture_origin, shared_from_this(), params_->apply_loss_function);
+    auto fac_ftipd = FactorBase::emplace<FactorForceTorqueInertialDynamics>(
+        _feature_motion, ftr_ftipd, _capture_origin, shared_from_this(), params_->apply_loss_function);
     return fac_ftipd;
 }
 
 void ProcessorForceTorqueInertialPreintDynamics::setCalibration(const CaptureBasePtr _capture,
                                                                 const VectorXd&      _calibration)
 {
-    _capture->getStateBlock('I')->setState(_calibration.segment<6>(0));  // Set IMU bias
-    _capture->getStateBlock('C')->setState(_calibration.segment<3>(6));  // Set C
-    _capture->getStateBlock('i')->setState(_calibration.segment<3>(9));  // Set i
+    _capture->getStateBlock('I')->setState(_calibration.segment<6>(0));   // Set IMU bias
+    _capture->getStateBlock('C')->setState(_calibration.segment<3>(6));   // Set C
+    _capture->getStateBlock('i')->setState(_calibration.segment<3>(9));   // Set i
     _capture->getStateBlock('m')->setState(_calibration.segment<1>(12));  // Set m
 }
 
@@ -125,23 +120,87 @@ void ProcessorForceTorqueInertialPreintDynamics::computeCurrentDelta(const Eigen
                                                                      Eigen::MatrixXd&       _jacobian_calib) const
 {
     // compute tangent by removing IMU bias
+
+    /**
+     * @brief tangent and data
+     *
+     * tangent = [at,wt,ft,tt] wrt CoM
+     * data = [ad,wd,fd,td] measured
+     * calib = [I,C,i,m]
+     * I = [ab,wb]
+     *
+     * at = ad - ab
+     * wt = wd - wb
+     * ft = fd
+     * tt = td +- C x fd
+     *
+     * cross product and Jacobians
+     *
+     * a, b in R3
+     *
+     * a_x, b_x, skew-symmetric matrices
+     *
+     * a_x = [0 -az ay ; az 0 -ax ; -ay ax 0]
+     *
+     * a x b = a_x b
+     *
+     * a x b = - b x a = - b_x a
+     *
+     * J_(axb)_a = -b_x
+     *
+     * J_(axb)_b = a_x
+     */
+
+    // Matrix<double, 12, 1> tangent = _data;
+    // tangent.head<6>() -= _calib.head<6>();  // J_tangent_data  = Identity(12,12)
+    // Matrix<double, 12, 6> J_tangent_I;      // J_tangent_I = [-Identity(6,6) ; Zero(6,6)]
+    // J_tangent_I.topRows<6>()    = -Matrix6d::Identity();
+    // J_tangent_I.bottomRows<6>() = Matrix6d::Zero();
+
+    // // go from tangent to delta. We need to dynamic model for this
+    // const Matrix<double, 7, 1>& model = sensor_fti_->getModel();
+    // Matrix<double, 18, 12>      J_delta_tangent;
+    // Matrix<double, 18, 7>       J_delta_model;
+    // fti::tangent2delta(tangent, model, _dt, _delta, J_delta_tangent, J_delta_model);
+
+    // // Compute cov and compose jacobians
+    // Matrix<double, 18, 6> J_delta_I = J_delta_tangent * J_tangent_I;
+    // _jacobian_calib << J_delta_I, J_delta_model;  // J_delta_calib = _jacobian_calib = [J_delta_I ; J_delta_model]
+    // const auto& J_delta_data = J_delta_tangent;   //  * J_tangent_data, which is the Identity;
+    // _delta_cov               = J_delta_data * _data_cov * J_delta_data.transpose();
+
+    ////////////////////// NOU CODI QUE HAURE DE REVISAR I CANVIAR PEL QUE HI HA ADALT //////////////////////////
+
+    Vector3d C        = _calib.segment<3>(6);
+    Matrix3d C_cross  = skew(C);
+    Vector3d fd       = _data.segment<3>(6);
+    Matrix3d fd_cross = skew(fd);
+
     Matrix<double, 12, 1> tangent = _data;
-    tangent.head<6>() -= _calib.head<6>();  // J_tangent_data  = Identity(12,12)
-    Matrix<double, 12, 6> J_tangent_I;      // J_tangent_I = [-Identity(6,6) ; Zero(6,6)]
-    J_tangent_I.topRows<6>()    = -Matrix6d::Identity();
-    J_tangent_I.bottomRows<6>() = Matrix6d::Zero();
+    tangent.segment<3>(9) += _calib.segment<3>(6).cross(_data.segment<3>(6));  // C x fd
+    Matrix<double, 12, 12> J_tangent_data         = MatrixXd::Identity(12, 12);
+    J_tangent_data.bottomRows(3).middleCols(6, 3) = C_cross;
+    Matrix<double, 12, 6> J_tangent_I;  // J_tangent_I = [-Identity(6,6) ; Zero(6,6)]
+    J_tangent_I.topRows<6>()                  = -Matrix6d::Identity();
+    J_tangent_I.bottomRows<6>()               = Matrix6d::Zero();
+    Matrix<double, 12, 7> J_tangent_model     = MatrixXd::Zero(12, 7);
+    J_tangent_model.bottomRows(3).leftCols(3) = -fd_cross; // J_tangent_model = [Zero(9,7); -fd_cross, Zero(3,3), 0]
 
     // go from tangent to delta. We need to dynamic model for this
     const Matrix<double, 7, 1>& model = sensor_fti_->getModel();
     Matrix<double, 18, 12>      J_delta_tangent;
     Matrix<double, 18, 7>       J_delta_model;
-    fti::tangent2delta(tangent, model, _dt, _delta, J_delta_tangent, J_delta_model);
+    fti::tangent2delta(
+        tangent, model, _dt, _delta, J_delta_tangent, J_delta_model);  // Aquí ja farà bé ell sol el J_delta_model?
 
     // Compute cov and compose jacobians
     Matrix<double, 18, 6> J_delta_I = J_delta_tangent * J_tangent_I;
+    J_delta_model += J_delta_tangent * J_tangent_model;
     _jacobian_calib << J_delta_I, J_delta_model;  // J_delta_calib = _jacobian_calib = [J_delta_I ; J_delta_model]
-    const auto& J_delta_data = J_delta_tangent;   //  * J_tangent_data, which is the Identity;
-    _delta_cov               = J_delta_data * _data_cov * J_delta_data.transpose();
+    const auto& J_delta_data = J_delta_tangent * J_tangent_data;
+    _delta_cov  = J_delta_data * _data_cov * J_delta_data.transpose();
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////////
 }
 
 void ProcessorForceTorqueInertialPreintDynamics::deltaPlusDelta(const Eigen::VectorXd& _delta1,
@@ -181,8 +240,8 @@ Eigen::VectorXd ProcessorForceTorqueInertialPreintDynamics::correctDelta(const E
 VectorXd ProcessorForceTorqueInertialPreintDynamics::getCalibration(const CaptureBaseConstPtr _capture) const
 {
     if (_capture)
-    {   
-        VectorXd calibration(13);
+    {
+        VectorXd        calibration(13);
         const Vector6d& I_calib = _capture->getStateBlock('I')->getState();
         const Vector3d& C_calib = _capture->getStateBlock('C')->getState();
         const Vector3d& i_calib = _capture->getStateBlock('i')->getState();
@@ -192,7 +251,7 @@ VectorXd ProcessorForceTorqueInertialPreintDynamics::getCalibration(const Captur
     }
     else
     {
-        VectorXd calibration(13);
+        VectorXd        calibration(13);
         const Vector6d& I_calib = getSensor()->getStateBlock('I')->getState();
         const Vector3d& C_calib = getSensor()->getStateBlock('C')->getState();
         const Vector3d& i_calib = getSensor()->getStateBlock('i')->getState();
@@ -205,33 +264,34 @@ VectorXd ProcessorForceTorqueInertialPreintDynamics::getCalibration(const Captur
 bool ProcessorForceTorqueInertialPreintDynamics::voteForKeyFrame() const
 {
     // time span
-    if (getBuffer().back().ts_ - getBuffer().front().ts_ > params_force_torque_inertial_preint_dynamics_->max_time_span)
+    if (getBuffer().back().ts_ - getBuffer().front().ts_ >
+        params_force_torque_inertial_preint_dynamics_->max_time_span)
     {
-        WOLF_DEBUG( "PM: vote: time span" );
+        WOLF_DEBUG("PM: vote: time span");
         return true;
     }
     // buffer length
     if (getBuffer().size() > params_force_torque_inertial_preint_dynamics_->max_buff_length)
     {
-        WOLF_DEBUG( "PM: vote: buffer length" );
+        WOLF_DEBUG("PM: vote: buffer length");
         return true;
     }
     // dist_traveled
     VectorComposite X0 = getOrigin()->getFrame()->getState();
     VectorComposite X1;
-    double dt = getBuffer().back().ts_ - getOrigin()->getFrame()->getTimeStamp();
+    double          dt = getBuffer().back().ts_ - getOrigin()->getFrame()->getTimeStamp();
     statePlusDelta(X0, getBuffer().back().delta_integr_, dt, X1);
     double dist = (X1.at('P') - X0.at('P')).norm();
     if (dist > params_force_torque_inertial_preint_dynamics_->dist_traveled)
     {
-        WOLF_DEBUG( "PM: vote: distance traveled" );
+        WOLF_DEBUG("PM: vote: distance traveled");
         return true;
     }
     // angle turned
     double angle = 2.0 * acos(getMotion().delta_integr_(15));
     if (angle > params_force_torque_inertial_preint_dynamics_->angle_turned)
     {
-        WOLF_DEBUG( "PM: vote: angle turned" );
+        WOLF_DEBUG("PM: vote: angle turned");
         return true;
     }
 
diff --git a/src/sensor/sensor_force_torque_inertial.cpp b/src/sensor/sensor_force_torque_inertial.cpp
index 886ef10..89fac3f 100644
--- a/src/sensor/sensor_force_torque_inertial.cpp
+++ b/src/sensor/sensor_force_torque_inertial.cpp
@@ -32,8 +32,8 @@ namespace wolf
 SensorForceTorqueInertial::SensorForceTorqueInertial(const VectorXd&                    _extrinsics,
                                                      ParamsSensorForceTorqueInertialPtr _params)
     : SensorBase("SensorForceTorqueInertial",
-                 FactoryStateBlock::create("StatePoint3d", _extrinsics.head(3), false),
-                 FactoryStateBlock::create("StateQuaternion", _extrinsics.tail(4), false),
+                 FactoryStateBlock::create("StatePoint3d", _extrinsics.head(3), true),
+                 FactoryStateBlock::create("StateQuaternion", _extrinsics.tail(4), true),
                  FactoryStateBlock::create("StateParams6", Vector6d::Zero(), false),  // IMU bias
                  12,
                  false,
@@ -44,9 +44,11 @@ SensorForceTorqueInertial::SensorForceTorqueInertial(const VectorXd&
     addStateBlock('C', FactoryStateBlock::create("StateParams3", params_fti_->com, true));  // centre of mass
     addStateBlock('i', FactoryStateBlock::create("StateParams3", params_fti_->inertia, true));  // inertial vector
     addStateBlock('m', FactoryStateBlock::create("StateParams1", Vector1d(params_fti_->mass), true));  // total mass
+    setStateBlockDynamic('I', false);
     setStateBlockDynamic('C', false);
     setStateBlockDynamic('i', false);
     setStateBlockDynamic('m', false);
+
 }
 
 // TODO: Adapt this API to that of MR !448
@@ -65,6 +67,7 @@ SensorForceTorqueInertial::SensorForceTorqueInertial(const VectorComposite&
     auto sbc = emplaceStateBlock<StateParams3>('C', getProblem(), _states.at('C'), true);  // centre of mass
     auto sbi = emplaceStateBlock<StateParams3>('i', getProblem(), _states.at('i'), true);  // inertial vector
     auto sbm = emplaceStateBlock<StateParams1>('m', getProblem(), _states.at('m'), true);  // total mass
+    setStateBlockDynamic('I', false);
     setStateBlockDynamic('C', false);
     setStateBlockDynamic('i', false);
     setStateBlockDynamic('m', false);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index e2ad0be..425d6f1 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -39,3 +39,5 @@ wolf_add_gtest(gtest_sensor_force_torque gtest_sensor_force_torque.cpp)
 
 wolf_add_gtest(gtest_sensor_inertial_kinematics gtest_sensor_inertial_kinematics.cpp)
 
+wolf_add_gtest(gtest_solve_problem_force_torque_inertial_dynamics gtest_solve_problem_force_torque_inertial_dynamics.cpp)
+
diff --git a/test/gtest_processor_force_torque_inertial_preint_dynamics.cpp b/test/gtest_processor_force_torque_inertial_preint_dynamics.cpp
index f18a414..d9f1e87 100644
--- a/test/gtest_processor_force_torque_inertial_preint_dynamics.cpp
+++ b/test/gtest_processor_force_torque_inertial_preint_dynamics.cpp
@@ -254,6 +254,6 @@ TEST_F(Test_ProcessorForceTorqueInertialPreintDynamics_yaml, VoteForKeyFrame_buf
 int main(int argc, char **argv)
 {
     testing::InitGoogleTest(&argc, argv);
-    ::testing::GTEST_FLAG(filter) = "Test_ProcessorForceTorqueInertialPreintDynamics_yaml.VoteForKeyFrame_buffer";
+    ::testing::GTEST_FLAG(filter) = "Test_ProcessorForceTorqueInertialPreintDynamics_yaml.not_moving_test";
     return RUN_ALL_TESTS();
 }
\ No newline at end of file
diff --git a/test/gtest_solve_problem_force_torque_inertial_dynamics.cpp b/test/gtest_solve_problem_force_torque_inertial_dynamics.cpp
new file mode 100644
index 0000000..619dd4b
--- /dev/null
+++ b/test/gtest_solve_problem_force_torque_inertial_dynamics.cpp
@@ -0,0 +1,255 @@
+//--------LICENSE_START--------
+//
+// Copyright (C) 2020,2021,2022 Institut de Robòtica i Informàtica Industrial, CSIC-UPC.
+// Authors: Joan Solà Ortega (jsola@iri.upc.edu)
+// All rights reserved.
+//
+// This file is part of WOLF
+// WOLF is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+//--------LICENSE_END--------
+
+#include "bodydynamics/factor/factor_force_torque_inertial_dynamics.h"
+#include "bodydynamics/processor/processor_force_torque_inertial_preint_dynamics.h"
+#include "bodydynamics/sensor/sensor_force_torque_inertial.h"
+#include "bodydynamics/internal/config.h"
+
+#include <core/sensor/factory_sensor.h>
+
+#include <core/utils/utils_gtest.h>
+#include <core/utils/logging.h>
+#include <core/utils/params_server.h>
+#include <core/yaml/parser_yaml.h>
+#include <core/state_block/state_block_derived.h>
+#include <core/state_block/factory_state_block.h>
+#include <core/ceres_wrapper/solver_ceres.h>
+
+#include <Eigen/Dense>
+#include <Eigen/Geometry>
+
+using namespace wolf;
+using namespace bodydynamics::fti;
+
+WOLF_REGISTER_STATEBLOCK_WITH_KEY(L, StateVector3d);
+
+class Test_SolveProblemForceTorqueInertialPreintDynamics_yaml : public testing::Test
+{
+  public:
+    ProblemPtr                                    P;
+    SensorForceTorqueInertialPtr                  S;
+    ProcessorForceTorqueInertialPreintDynamicsPtr p;
+    SolverCeresPtr                                solver_;
+    Vector3d                                      cdm_true;
+    Vector3d                                      inertia_true;
+    double                                        mass_true;
+
+  protected:
+    void SetUp() override
+    {
+        std::string wolf_root = _WOLF_BODYDYNAMICS_ROOT_DIR;
+        ParserYaml  parser =
+            ParserYaml("problem_force_torque_inertial_dynamics_solve_test.yaml", wolf_root + "/test/yaml/");
+        ParamsServer server = ParamsServer(parser.getParams());
+        P                   = Problem::autoSetup(server);
+        // CERES WRAPPER
+        solver_                                    = std::make_shared<SolverCeres>(P);
+        solver_->getSolverOptions().minimizer_type = ceres::TRUST_REGION;  // ceres::TRUST_REGION;ceres::LINE_SEARCH
+        solver_->getSolverOptions().max_line_search_step_contraction = 1e-3;
+        solver_->getSolverOptions().max_num_iterations               = 1e4;
+
+        S = std::static_pointer_cast<SensorForceTorqueInertial>(P->getHardware()->getSensorList().front());
+        p = std::static_pointer_cast<ProcessorForceTorqueInertialPreintDynamics>(S->getProcessorList().front());
+
+        cdm_true = {0,0,0.0341};
+        inertia_true = {0.017598,0.017957,0.029599};
+        mass_true = 1.952;
+    }
+};
+
+TEST_F(Test_SolveProblemForceTorqueInertialPreintDynamics_yaml, force_registraion)
+{
+    FactorySensor::registerCreator("SensorForceTorqueInertial", SensorForceTorqueInertial::create);
+}
+
+// Hover test.
+TEST_F(Test_SolveProblemForceTorqueInertialPreintDynamics_yaml, mass_only_hovering)
+{
+    double mass_guess = S->getStateBlock('m')->getState()(0);
+
+    // Data
+    VectorXd data      = VectorXd::Zero(12);  // [ a, w, f, t ]
+    data.segment<3>(0) = -gravity();
+    data.segment<3>(6) = -mass_true * gravity();
+    MatrixXd data_cov  = MatrixXd::Identity(12, 12);
+
+    // We fix the parameters of the sensor except for the mass
+    S->getStateBlock('P')->fix();
+    S->getStateBlock('O')->fix();
+    S->getStateBlock('I')->fix();
+    S->getStateBlock('C')->fix();
+    S->getStateBlock('i')->fix();
+    S->getStateBlock('m')->unfix();
+
+    CaptureMotionPtr C0_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.0, S, data, data_cov, nullptr);
+    CaptureMotionPtr C1_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.2, S, data, data_cov, nullptr);
+    CaptureMotionPtr C2_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.4, S, data, data_cov, nullptr);
+    CaptureMotionPtr C3_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.6, S, data, data_cov, nullptr);
+    CaptureMotionPtr C4_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.8, S, data, data_cov, nullptr);
+    CaptureMotionPtr C5_1 = std::make_shared<CaptureMotion>("CaptureMotion", 1.0, S, data, data_cov, nullptr);
+
+    C0_0->process();
+    CaptureMotionPtr C_KF0 = std::static_pointer_cast<CaptureMotion>(p->getOrigin());
+    C1_0->process();
+    C2_0->process();
+    C3_0->process();
+    C4_0->process();
+    C5_1->process();
+
+    CaptureMotionPtr C_KF1 = std::static_pointer_cast<CaptureMotion>(p->getOrigin());
+
+    C_KF0->getFrame()->fix();
+    C_KF1->getFrame()->fix();
+
+    P->print(4, 1, 1, 1);
+
+    WOLF_INFO("======== SOLVE PROBLEM =======")
+    std::string report = solver_->solve(wolf::SolverManager::ReportVerbosity::FULL);
+    WOLF_INFO(report);  // should show a very low iteration number (possibly 1)
+
+    P->print(4, 1, 1, 1);
+
+    WOLF_INFO("True mass     : ", mass_true, " Kg.");
+    WOLF_INFO("Guess mass    : ", mass_guess, " Kg.");
+    WOLF_INFO("Estimated mass: ", S->getStateBlock('m')->getState(), " Kg.");
+}
+
+
+TEST_F(Test_SolveProblemForceTorqueInertialPreintDynamics_yaml, cdm_only_hovering)
+{
+    S->getStateBlock('C')->setState(Vector3d(0.005,0.005,0.33));
+    S->getStateBlock('m')->setState(Vector1d(mass_true));
+    Vector3d cdm_guess = S->getStateBlock('C')->getState();
+
+    // Data
+    VectorXd data      = VectorXd::Zero(12);  // [ a, w, f, t ]
+    data.segment<3>(0) = -gravity();
+    data.segment<3>(6) = -mass_true * gravity();
+    MatrixXd data_cov  = MatrixXd::Identity(12, 12);
+
+    // We fix the parameters of the sensor except for the cdm
+    S->getStateBlock('P')->fix();
+    S->getStateBlock('O')->fix();
+    S->getStateBlock('I')->fix();
+    S->getStateBlock('C')->unfix();
+    S->getStateBlock('i')->fix();
+    S->getStateBlock('m')->fix();
+
+    CaptureMotionPtr C0_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.0, S, data, data_cov, nullptr);
+    CaptureMotionPtr C1_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.2, S, data, data_cov, nullptr);
+    CaptureMotionPtr C2_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.4, S, data, data_cov, nullptr);
+    CaptureMotionPtr C3_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.6, S, data, data_cov, nullptr);
+    CaptureMotionPtr C4_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.8, S, data, data_cov, nullptr);
+    CaptureMotionPtr C5_1 = std::make_shared<CaptureMotion>("CaptureMotion", 1.0, S, data, data_cov, nullptr);
+
+    C0_0->process();
+    CaptureMotionPtr C_KF0 = std::static_pointer_cast<CaptureMotion>(p->getOrigin());
+    C1_0->process();
+    C2_0->process();
+    C3_0->process();
+    C4_0->process();
+    C5_1->process();
+
+    CaptureMotionPtr C_KF1 = std::static_pointer_cast<CaptureMotion>(p->getOrigin());
+
+    C_KF0->getFrame()->fix();
+    C_KF1->getFrame()->fix();
+
+    P->print(4, 1, 1, 1);
+
+    WOLF_INFO("======== SOLVE PROBLEM =======")
+    std::string report = solver_->solve(wolf::SolverManager::ReportVerbosity::FULL);
+    WOLF_INFO(report);  // should show a very low iteration number (possibly 1)
+
+    P->print(4, 1, 1, 1);
+
+    WOLF_INFO("True cdm     : ", cdm_true.transpose(), " m.");
+    WOLF_INFO("Guess cdm    : ", cdm_guess.transpose(), " m.");
+    WOLF_INFO("Estimated cdm: ", S->getStateBlock('C')->getState().transpose(), " m.");
+}
+
+
+TEST_F(Test_SolveProblemForceTorqueInertialPreintDynamics_yaml, mass_and_cdm_hovering)
+{
+    S->getStateBlock('C')->setState(Vector3d(0.1,0.1,0.2));
+    S->getStateBlock('m')->setState(Vector1d(1.9));
+    Vector3d cdm_guess = S->getStateBlock('C')->getState();
+    double mass_guess = S->getStateBlock('m')->getState()(0);
+
+    // Data
+    VectorXd data      = VectorXd::Zero(12);  // [ a, w, f, t ]
+    data.segment<3>(0) = -gravity();
+    data.segment<3>(6) = -mass_true * gravity();
+    MatrixXd data_cov  = MatrixXd::Identity(12, 12);
+
+    // We fix the parameters of the sensor except for the cdm and the mass
+    S->getStateBlock('P')->fix();
+    S->getStateBlock('O')->fix();
+    S->getStateBlock('I')->fix();
+    S->getStateBlock('C')->unfix();
+    S->getStateBlock('i')->fix();
+    S->getStateBlock('m')->unfix();
+
+    CaptureMotionPtr C0_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.0, S, data, data_cov, nullptr);
+    CaptureMotionPtr C1_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.2, S, data, data_cov, nullptr);
+    CaptureMotionPtr C2_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.4, S, data, data_cov, nullptr);
+    CaptureMotionPtr C3_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.6, S, data, data_cov, nullptr);
+    CaptureMotionPtr C4_0 = std::make_shared<CaptureMotion>("CaptureMotion", 0.8, S, data, data_cov, nullptr);
+    CaptureMotionPtr C5_1 = std::make_shared<CaptureMotion>("CaptureMotion", 1.0, S, data, data_cov, nullptr);
+
+    C0_0->process();
+    CaptureMotionPtr C_KF0 = std::static_pointer_cast<CaptureMotion>(p->getOrigin());
+    C1_0->process();
+    C2_0->process();
+    C3_0->process();
+    C4_0->process();
+    C5_1->process();
+
+    CaptureMotionPtr C_KF1 = std::static_pointer_cast<CaptureMotion>(p->getOrigin());
+
+    C_KF0->getFrame()->fix();
+    C_KF1->getFrame()->fix();
+
+    P->print(4, 1, 1, 1);
+
+    WOLF_INFO("======== SOLVE PROBLEM =======")
+    std::string report = solver_->solve(wolf::SolverManager::ReportVerbosity::FULL);
+    WOLF_INFO(report);  // should show a very low iteration number (possibly 1)
+
+    P->print(4, 1, 1, 1);
+
+    WOLF_INFO("True mass     : ", mass_true, " Kg.");
+    WOLF_INFO("Guess mass    : ", mass_guess, " Kg.");
+    WOLF_INFO("Estimated mass: ", S->getStateBlock('m')->getState(), " Kg.");
+    WOLF_INFO("True cdm     : ", cdm_true.transpose(), " m.");
+    WOLF_INFO("Guess cdm    : ", cdm_guess.transpose(), " m.");
+    WOLF_INFO("Estimated cdm: ", S->getStateBlock('C')->getState().transpose(), " m.");
+}
+
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+    ::testing::GTEST_FLAG(filter) = "Test_SolveProblemForceTorqueInertialPreintDynamics_yaml.cdm_only_hovering";
+    return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/test/yaml/problem_force_torque_inertial_dynamics_solve_test.yaml b/test/yaml/problem_force_torque_inertial_dynamics_solve_test.yaml
new file mode 100644
index 0000000..ed94232
--- /dev/null
+++ b/test/yaml/problem_force_torque_inertial_dynamics_solve_test.yaml
@@ -0,0 +1,56 @@
+config:
+  problem:
+    frame_structure: "POVL"
+    dimension: 3
+    prior:
+      mode: "factor"
+      $state:
+        P: [0,0,0]
+        O: [0,0,0,1]
+        V: [0,0,0]
+        L: [0,0,0]
+      $sigma:
+        P: [0.31, 0.31, 0.31]
+        O: [0.31, 0.31, 0.31]
+        V: [1,1,1]
+        L: [1,1,1]
+      time_tolerance: 0.1
+    tree_manager: 
+      type: "None"
+  map:
+    type: "MapBase"
+    plugin: "core"
+  sensors:
+   -
+    name: "sensor FTI"
+    type: "SensorForceTorqueInertial"
+    plugin: "bodydynamics"
+    extrinsic:
+      pose: [0,0,0, 0,0,0,1]
+    force_noise_std:              0.1         # std dev of force noise in N 
+    torque_noise_std:             0.1         # std dev of torque noise in N/m
+    acc_noise_std:                0.01        # std dev of acc noise in m/s2
+    gyro_noise_std:               0.01        # std dev of gyro noise in rad/s
+
+    com:                          [0,0,0.0341]                      # center of mass [m]
+    inertia:                      [0.017598,0.017957,0.029599]      # moments of inertia i_xx, i_yy, i_zz [kg m2]
+    mass:                         1.9                               # mass [kg]
+
+  processors:
+   -
+    name: "proc FTID"
+    type: "ProcessorForceTorqueInertialPreintDynamics"
+    sensor_name: "sensor FTI"
+    plugin: "bodydynamics"
+    time_tolerance: 0.1
+    apply_loss_function: false
+    unmeasured_perturbation_std: 0.00000001
+    state_getter: true
+    state_priority: 1
+    n_propellers: 3
+    keyframe_vote:
+      voting_active:    true
+      max_time_span:    0.995
+      max_buff_length:  999   # motion deltas
+      dist_traveled:    999   # meters
+      angle_turned:     999   # radians (1 rad approx 57 deg, approx 60 deg)
-- 
GitLab