diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1adcb74ceb72d69e5108c5fb639a45c7d2a0e1ee..a47a0acd2c5f81ed46bf9f9f3ec642b750abfe97 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,76 +1,24 @@
-image: labrobotica/ceres:1.14
-
-before_script:
-  - ls
-  - apt-get update
-  - apt-get install -y build-essential cmake 
-
-# SPDLOG
-#  - apt-get install -y libspdlog-dev
-  - if [ -d spdlog ]; then
-  -   echo "directory exists" 
-  -   if [ "$(ls -A ./spdlog)" ]; then 
-  -     echo "directory not empty" 
-  -     cd spdlog
-  -     git pull
-  -   else 
-  -     echo "directory empty" 
-  -     git clone https://github.com/gabime/spdlog.git
-  -     cd spdlog
-  -   fi
-  - else
-  -   echo "directory inexistent" 
-  -   git clone https://github.com/gabime/spdlog.git
-  -   cd spdlog
-  - fi
-  - git fetch
-  - git checkout v0.17.0
+.build_and_test_template: &build_and_test_definition
   - mkdir -pv build
   - cd build
-  - ls
-  - cmake -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -fPIC" -DSPDLOG_BUILD_TESTING=OFF ..
+  - cmake -DCMAKE_BUILD_TYPE=release -DBUILD_DEMOS=ON -DBUILD_TESTS=ON ..
   - make -j$(nproc)
+  - ctest -j$(nproc)
+  # run demos
+  - ../bin/hello_wolf
+  - ../bin/hello_wolf_autoconf
   - make install
-  - cd ../..
 
-# YAML
-#  - apt-get install -y libyaml-cpp-dev
-  - if [ -d yaml-cpp ]; then
-  -   echo "directory exists" 
-  -   if [ "$(ls -A ./yaml-cpp)" ]; then 
-  -     echo "directory not empty" 
-  -     cd yaml-cpp
-  -     git pull
-  -   else 
-  -     echo "directory empty" 
-  -     git clone https://github.com/jbeder/yaml-cpp.git
-  -     cd yaml-cpp
-  -   fi
-  - else
-  -   echo "directory inexistent" 
-  -   git clone https://github.com/jbeder/yaml-cpp.git
-  -   cd yaml-cpp
-  - fi
-  - mkdir -pv build
-  - cd build
-  - ls
-  - cmake -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -fPIC" -DYAML_CPP_BUILD_TESTS=OFF ..
-  - make -j$(nproc)
-  - make install
-  - cd ../..
+build_and_test:xenial:
+  image: labrobotica/wolf_deps:16.04
+  except:
+    - master
+  script:
+    - *build_and_test_definition
 
-wolf_build_and_test:
-  stage: build
+build_and_test:bionic:
+  image: labrobotica/wolf_deps:18.04
   except:
     - master
   script:
-    - mkdir -pv build
-    - cd build
-    - ls # we can check whether the directory was already full
-    - cmake -DCMAKE_BUILD_TYPE=release -DBUILD_DEMOS=ON -DBUILD_TESTS=ON ..
-    - make -j$(nproc)
-    - ctest -j$(nproc)
-    # run demos
-    - ../bin/hello_wolf
-    - ../bin/hello_wolf_autoconf
-    - make install
+    - *build_and_test_definition
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4103c38189e7e0892959e7be9a52789920330c03..7777ac0a4ba15acbdb080c27ca0746fd57a43590 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,6 +7,7 @@ if(COMMAND cmake_policy)
   cmake_policy(SET CMP0005 NEW)
   cmake_policy(SET CMP0003 NEW)
 endif(COMMAND cmake_policy)
+
 # MAC OSX RPATH
 SET(CMAKE_MACOSX_RPATH 1)
 
@@ -167,6 +168,7 @@ SET(HDRS_UTILS
   include/core/utils/converter.h
   include/core/utils/eigen_assert.h
   include/core/utils/eigen_predicates.h
+  include/core/utils/graph_search.h
   include/core/utils/loader.h
   include/core/utils/logging.h
   include/core/utils/make_unique.h
@@ -228,7 +230,8 @@ SET(HDRS_FACTOR
   include/core/factor/factor_relative_pose_2d_with_extrinsics.h
   include/core/factor/factor_relative_pose_3d.h
   include/core/factor/factor_pose_3d_with_extrinsics.h
-  include/core/factor/factor_kf_lmk_pose_3d_with_extrinsics.h
+  include/core/factor/factor_relative_pose_3d_with_extrinsics.h
+  include/core/factor/factor_velocity_local_direction_3d.h
   )
 SET(HDRS_FEATURE
   include/core/feature/feature_base.h
@@ -246,9 +249,10 @@ SET(HDRS_PROCESSOR
   include/core/processor/motion_buffer.h
   include/core/processor/processor_base.h
   include/core/processor/processor_diff_drive.h
+  include/core/processor/processor_fix_wing_model.h
   include/core/processor/factory_processor.h
   include/core/processor/processor_logging.h
-  include/core/processor/processor_loopclosure.h
+  include/core/processor/processor_loop_closure.h
   include/core/processor/processor_motion.h
   include/core/processor/processor_odom_2d.h
   include/core/processor/processor_odom_3d.h
@@ -262,6 +266,7 @@ SET(HDRS_SENSOR
   include/core/sensor/sensor_base.h
   include/core/sensor/sensor_diff_drive.h
   include/core/sensor/factory_sensor.h
+  include/core/sensor/sensor_model.h
   include/core/sensor/sensor_odom_2d.h
   include/core/sensor/sensor_odom_3d.h
   include/core/sensor/sensor_pose.h
@@ -311,10 +316,11 @@ SET(SRCS_COMMON
 SET(SRCS_MATH
   )
 SET(SRCS_UTILS
+  src/utils/check_log.cpp
   src/utils/converter_utils.cpp
-  src/utils/params_server.cpp
+  src/utils/graph_search.cpp
   src/utils/loader.cpp
-  src/utils/check_log.cpp
+  src/utils/params_server.cpp
   )
 
 SET(SRCS_CAPTURE
@@ -344,7 +350,8 @@ SET(SRCS_PROCESSOR
   src/processor/motion_buffer.cpp
   src/processor/processor_base.cpp
   src/processor/processor_diff_drive.cpp
-  src/processor/processor_loopclosure.cpp
+  src/processor/processor_fix_wing_model.cpp
+  src/processor/processor_loop_closure.cpp
   src/processor/processor_motion.cpp
   src/processor/processor_odom_2d.cpp
   src/processor/processor_odom_3d.cpp
@@ -357,6 +364,7 @@ SET(SRCS_PROCESSOR
 SET(SRCS_SENSOR
   src/sensor/sensor_base.cpp
   src/sensor/sensor_diff_drive.cpp
+  src/sensor/sensor_model.cpp
   src/sensor/sensor_odom_2d.cpp
   src/sensor/sensor_odom_3d.cpp
   src/sensor/sensor_pose.cpp
diff --git a/include/core/ceres_wrapper/solver_ceres.h b/include/core/ceres_wrapper/solver_ceres.h
index 0673207bc47e1873084e3f4d711628a67b322f3c..4bf6f7de661059e7c9f96e7f12bb444f18bed013 100644
--- a/include/core/ceres_wrapper/solver_ceres.h
+++ b/include/core/ceres_wrapper/solver_ceres.h
@@ -119,6 +119,8 @@ class SolverCeres : public SolverManager
 
         double finalCost() override;
 
+        double totalTime() override;
+
         ceres::Solver::Options& getSolverOptions();
 
         const Eigen::SparseMatrixd computeHessian() const;
diff --git a/include/core/factor/factor_relative_pose_3d.h b/include/core/factor/factor_relative_pose_3d.h
index 67d98f7e26d03e360c0a5d846b7d84b54012047b..afcf8daf6884c9c08eb800433fa1f2392a8ecd0c 100644
--- a/include/core/factor/factor_relative_pose_3d.h
+++ b/include/core/factor/factor_relative_pose_3d.h
@@ -93,6 +93,8 @@ inline FactorRelativePose3d::FactorRelativePose3d(const FeatureBasePtr& _ftr_cur
                                                             _frame_past_ptr->getP(), // past frame P
                                                             _frame_past_ptr->getO()) // past frame Q
 {
+    MatrixSizeCheck<7,1>::check(_ftr_current_ptr->getMeasurement());
+    MatrixSizeCheck<6,6>::check(_ftr_current_ptr->getMeasurementSquareRootInformationUpper());
 }
 
 template<typename T>
diff --git a/include/core/factor/factor_velocity_local_direction_3d.h b/include/core/factor/factor_velocity_local_direction_3d.h
new file mode 100644
index 0000000000000000000000000000000000000000..115ea57f1803aa7a907c6e895e04d7731eab1560
--- /dev/null
+++ b/include/core/factor/factor_velocity_local_direction_3d.h
@@ -0,0 +1,149 @@
+
+#ifndef FACTOR_VELOCITY_LOCAL_DIRECTION_3D_H_
+#define FACTOR_VELOCITY_LOCAL_DIRECTION_3D_H_
+
+//Wolf includes
+#include "core/factor/factor_autodiff.h"
+#include "core/frame/frame_base.h"
+#include "core/math/rotations.h"
+
+namespace wolf {
+    
+WOLF_PTR_TYPEDEFS(FactorVelocityLocalDirection3d);
+
+//class
+class FactorVelocityLocalDirection3d: public FactorAutodiff<FactorVelocityLocalDirection3d,2,3,4>
+{
+    protected:
+        double min_vel_sq_norm_; // stored in squared norm for efficiency
+        int orthogonal_axis_; // 0: X, 1: Y, 2: Z
+
+    public:
+        FactorVelocityLocalDirection3d(FeatureBasePtr _ftr_ptr,
+                                       const double& _min_vel_norm,
+                                       const ProcessorBasePtr& _processor_ptr,
+                                       bool _apply_loss_function,
+                                       FactorStatus _status = FAC_ACTIVE);
+
+        ~FactorVelocityLocalDirection3d() override = default;
+
+        template<typename T>
+        bool operator ()(const T* const _v, const T* const _o, T* _residuals) const;
+
+        template<typename T>
+        Eigen::Matrix<T,2,1> computeElevationAzimuth(const Eigen::Matrix<T,3,1> vector) const
+        {
+            Eigen::Matrix<T,2,1> elev_azim;
+
+            // plane YZ
+            if (orthogonal_axis_ == 0)
+            {
+                elev_azim(0) = asin(vector(0) / vector.norm());
+                elev_azim(1) = atan2(vector(2), vector(1));
+            }
+            // plane XZ
+            if (orthogonal_axis_ == 1)
+            {
+                elev_azim(0) = asin(vector(1) / vector.norm());
+                elev_azim(1) = atan2(vector(0), vector(2));
+            }
+            // plane XY
+            if (orthogonal_axis_ == 2)
+            {
+                elev_azim(0) = asin(vector(2) / vector.norm());
+                elev_azim(1) = atan2(vector(1), vector(0));
+            }
+
+            return elev_azim;
+        }
+};
+
+FactorVelocityLocalDirection3d::FactorVelocityLocalDirection3d(FeatureBasePtr _ftr_ptr,
+                                                               const double& _min_vel_norm,
+                                                               const ProcessorBasePtr& _processor_ptr,
+                                                               bool _apply_loss_function,
+                                                               FactorStatus _status) :
+        FactorAutodiff<FactorVelocityLocalDirection3d,2,3,4>("FactorVelocityLocalDirection3d",
+                                                             TOP_ABS,
+                                                             _ftr_ptr,
+                                                             nullptr, nullptr, nullptr, nullptr,
+                                                             _processor_ptr,
+                                                             _apply_loss_function,
+                                                             _status,
+                                                             _ftr_ptr->getFrame()->getV(),
+                                                             _ftr_ptr->getFrame()->getO()),
+        min_vel_sq_norm_(_min_vel_norm*_min_vel_norm)
+{
+    MatrixSizeCheck<3,1>::check(_ftr_ptr->getMeasurement());
+    MatrixSizeCheck<1,1>::check(_ftr_ptr->getMeasurementSquareRootInformationUpper());
+
+    /* Decide the plane (two axis A1, A2 from X, Y, Z in local coordinates) in which compute elevation and azimuth
+     *    - elevation w.r.t. the plane
+     *    - azimuth computed with atan2(A2, A1)
+     *
+     * This is done to avoid the degenerated case: elevation = +/-PI/2
+     * We select the orthogonal axis as the farthest to the desired velocity in local coordinates,
+     * so the component one with lower value.
+     */
+
+    measurement_.minCoeff(&orthogonal_axis_);
+
+    // measurement: elevation & azimuth (w.r.t. selected plane)
+    measurement_ = computeElevationAzimuth(Eigen::Vector3d(measurement_));
+    measurement_sqrt_information_upper_ = Matrix2d::Identity() * measurement_sqrt_information_upper_(0,0);
+}
+
+template<typename T>
+inline bool FactorVelocityLocalDirection3d::operator ()(const T* const _v, const T* const _q, T* _residuals) const
+{
+    Eigen::Map<const Eigen::Matrix<T,3,1> > v(_v);
+    Eigen::Map<const Eigen::Quaternion<T> > q(_q);
+    Eigen::Map<Eigen::Matrix<T,2,1> > residuals(_residuals);
+
+    if (v.squaredNorm() < min_vel_sq_norm_)
+    {
+        _residuals[0] = T(0);
+        _residuals[1] = T(0);
+        return true;
+    }
+
+//    std::cout << "----------\n";
+//    std::cout << "desired elevation: " << getMeasurement()(0) << "\n";
+//    std::cout << "desired azimuth:   " << getMeasurement()(1) << "\n";
+
+//    std::cout << "v: \n\t" << v(0) << "\n\t"
+//                           << v(1) << "\n\t"
+//                           << v(2) << "\n";
+//
+//    std::cout << "q: \n\t" << q.coeffs()(0) << "\n\t"
+//                           << q.coeffs()(1) << "\n\t"
+//                           << q.coeffs()(2) << "\n\t"
+//                           << q.coeffs()(3) << "\n";
+
+    Eigen::Matrix<T,3,1> v_local = q.conjugate() * v;
+//    std::cout << "v (local): \n\t" << v_local(0) << "\n\t"
+//                                   << v_local(1) << "\n\t"
+//                                   << v_local(2) << "\n";
+
+    // expectation
+    Eigen::Matrix<T,2,1> exp_elev_azim = computeElevationAzimuth(v_local);
+//    std::cout << "expected elevation: " << exp_elev_azim(0) << "\n";
+//    std::cout << "expected azimuth:   " << exp_elev_azim(1) << "\n";
+
+    // error
+    Eigen::Matrix<T,2,1> error = getMeasurement() - exp_elev_azim;
+    pi2pi(error(1));
+//    std::cout << "error (corrected): \n\t" << error(0) << "\n\t"
+//                                           << error(1) << "\n;
+
+    // residuals
+    residuals = getMeasurementSquareRootInformationUpper() * error;
+//    std::cout << "residuals: \n\t" << residuals(0) << "\n\t"
+//                                   << residuals(1) << "\n;
+
+    return true;
+}
+
+} // namespace wolf
+
+#endif
diff --git a/include/core/frame/frame_base.h b/include/core/frame/frame_base.h
index 848fa3f12f21e709286a61cd37edb4122fe361bc..7234284a5d00ebcb66f6f9a2efee2c38c9745bbd 100644
--- a/include/core/frame/frame_base.h
+++ b/include/core/frame/frame_base.h
@@ -102,6 +102,12 @@ class FrameBase : public NodeBase, public HasStateBlocks, public std::enable_sha
         FrameBasePtr getNextFrame() const;
 
         const CaptureBasePtrList& getCaptureList() const;
+        template <class C>
+        CaptureBasePtr getCaptureOfType() const;
+        CaptureBasePtr getCaptureOfType(const std::string& type) const;
+        template <class C>
+        CaptureBasePtrList getCapturesOfType() const;
+        CaptureBasePtrList getCapturesOfType(const std::string& type) const;
         CaptureBasePtr getCaptureOf(const SensorBaseConstPtr _sensor_ptr) const;
         CaptureBasePtr getCaptureOf(const SensorBasePtr _sensor_ptr, const std::string& type) const;
         CaptureBasePtrList getCapturesOf(const SensorBasePtr _sensor_ptr) const;
@@ -208,6 +214,25 @@ inline void FrameBase::setTrajectory(TrajectoryBasePtr _trj_ptr)
     trajectory_ptr_ = _trj_ptr;
 }
 
+template <class C>
+inline CaptureBasePtr FrameBase::getCaptureOfType() const
+{
+    for (CaptureBasePtr capture_ptr : getCaptureList())
+        if (std::dynamic_pointer_cast<C>(capture_ptr) != nullptr)
+            return capture_ptr;
+    return nullptr;
+}
+
+template <class C>
+inline CaptureBasePtrList FrameBase::getCapturesOfType() const
+{
+    CaptureBasePtrList captures;
+    for (CaptureBasePtr capture_ptr : getCaptureList())
+        if (std::dynamic_pointer_cast<C>(capture_ptr) != nullptr)
+            captures.push_back(capture_ptr);
+    return captures;
+}
+
 } // namespace wolf
 
 #endif
diff --git a/include/core/map/map_base.h b/include/core/map/map_base.h
index caac2f94eed78b033c153373f5d25f101fc2d833..93663bdf7fed2b4a8af6867c8e6226df6bd76571 100644
--- a/include/core/map/map_base.h
+++ b/include/core/map/map_base.h
@@ -28,7 +28,7 @@ class MapBase : public NodeBase, public std::enable_shared_from_this<MapBase>
         MapBase();
         ~MapBase() override;
         
-    private:
+    protected:
         virtual LandmarkBasePtr addLandmark(LandmarkBasePtr _landmark_ptr);
         virtual void removeLandmark(LandmarkBasePtr _landmark_ptr);
 
diff --git a/include/core/processor/processor_fix_wing_model.h b/include/core/processor/processor_fix_wing_model.h
new file mode 100644
index 0000000000000000000000000000000000000000..8c23917354ca1d0e0c3723045ded8d118e3c3abc
--- /dev/null
+++ b/include/core/processor/processor_fix_wing_model.h
@@ -0,0 +1,92 @@
+/*
+ * processor_fix_wing_model.h
+ *
+ *  Created on: Sep 6, 2021
+ *      Author: joanvallve
+ */
+
+#ifndef INCLUDE_CORE_PROCESSOR_PROCESSOR_FIX_WING_MODEL_H_
+#define INCLUDE_CORE_PROCESSOR_PROCESSOR_FIX_WING_MODEL_H_
+
+#include "core/processor/processor_base.h"
+
+namespace wolf
+{
+
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorFixWingModel);
+
+struct ParamsProcessorFixWingModel : public ParamsProcessorBase
+{
+        Eigen::Vector3d velocity_local;
+        double angle_stdev;
+        double min_vel_norm;
+
+        ParamsProcessorFixWingModel() = default;
+        ParamsProcessorFixWingModel(std::string _unique_name, const wolf::ParamsServer & _server) :
+            ParamsProcessorBase(_unique_name, _server)
+        {
+            velocity_local   = _server.getParam<Eigen::Vector3d> (prefix + _unique_name + "/velocity_local");
+            angle_stdev      = _server.getParam<double>          (prefix + _unique_name + "/angle_stdev");
+            min_vel_norm     = _server.getParam<double>          (prefix + _unique_name + "/min_vel_norm");
+
+            assert(std::abs(velocity_local.norm() - 1.0) < wolf::Constants::EPS && "ParamsProcessorFixWingModel: 'velocity_local' must be normalized");
+        }
+        std::string print() const override
+        {
+            return ParamsProcessorBase::print()  + "\n"
+                + "velocity_local: print not implemented\n"
+                + "angle_stdev: " + std::to_string(angle_stdev) + "\n"
+                + "min_vel_norm: " + std::to_string(min_vel_norm) + "\n";
+        }
+};
+
+WOLF_PTR_TYPEDEFS(ProcessorFixWingModel);
+
+class ProcessorFixWingModel : public ProcessorBase
+{
+    public:
+        ProcessorFixWingModel(ParamsProcessorFixWingModelPtr _params);
+
+        // Factory method for high level API
+        WOLF_PROCESSOR_CREATE(ProcessorFixWingModel, ParamsProcessorFixWingModel);
+
+        virtual ~ProcessorFixWingModel() override;
+        void configure(SensorBasePtr _sensor) override;
+
+    protected:
+
+        /** \brief process an incoming capture NEVER CALLED
+         */
+        virtual void processCapture(CaptureBasePtr) override {};
+
+        /** \brief process an incoming key-frame: applies the motion model between consecutive keyframes
+         */
+        virtual void processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) override;
+
+        /** \brief trigger in capture
+         */
+        virtual bool triggerInCapture(CaptureBasePtr) const override {return false;};
+
+        /** \brief trigger in key-frame
+         */
+        virtual bool triggerInKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) const override {return true;};
+
+        /** \brief store key frame
+        */
+        virtual bool storeKeyFrame(FrameBasePtr) override {return false;};
+
+        /** \brief store capture
+        */
+        virtual bool storeCapture(CaptureBasePtr) override {return false;};
+
+        /** \brief Vote for KeyFrame generation
+         */
+        virtual bool voteForKeyFrame() const override {return false;};
+
+        // ATTRIBUTES
+        ParamsProcessorFixWingModelPtr params_processor_;
+};
+
+} /* namespace wolf */
+
+#endif /* INCLUDE_CORE_PROCESSOR_PROCESSOR_FIX_WING_MODEL_H_ */
diff --git a/include/core/processor/processor_loop_closure.h b/include/core/processor/processor_loop_closure.h
new file mode 100644
index 0000000000000000000000000000000000000000..94af3fa1692397e533db0379a342a39edebb51e0
--- /dev/null
+++ b/include/core/processor/processor_loop_closure.h
@@ -0,0 +1,110 @@
+#ifndef _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H
+#define _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H
+
+// Wolf related headers
+#include "core/processor/processor_base.h"
+
+namespace wolf{
+
+WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorLoopClosure);
+
+struct ParamsProcessorLoopClosure : public ParamsProcessorBase
+{
+    int max_loops=-1;
+
+    ParamsProcessorLoopClosure() = default;
+    ParamsProcessorLoopClosure(std::string _unique_name, const ParamsServer& _server):
+        ParamsProcessorBase(_unique_name, _server)
+    {
+        max_loops = _server.getParam<int>(prefix + _unique_name + "/max_loops");
+    }
+
+    std::string print() const override
+    {
+        return "\n" + ParamsProcessorBase::print()
+        + "max_loops: " + std::to_string(max_loops) + "\n";
+    }
+};
+
+WOLF_STRUCT_PTR_TYPEDEFS(MatchLoopClosure);
+
+/** \brief Match between a capture and a capture
+ *
+ * Match between a capture and a capture (capture-capture correspondence)
+ *
+ */
+struct MatchLoopClosure
+{
+    CaptureBasePtr capture_reference;   ///< Capture reference
+    CaptureBasePtr capture_target;      ///< Capture target
+    double normalized_score;            ///< normalized similarity score (0 is bad, 1 is good)
+};
+
+/** \brief General loop closure processor
+ *
+ * This is an abstract class.
+ * + You must define the following classes :
+ *   - voteFindLoopClosures(CaptureBasePtr)
+ *   - emplaceFeatures(CaptureBasePtr)
+ *   - findLoopClosures(CaptureBasePtr)
+ *   - validateLoop(CaptureBasePtr, CaptureBasePtr)
+ *   - emplaceFactors(CaptureBasePtr, CaptureBasePtr)
+ * + You can override the following classes :
+ *   - process(CaptureBasePtr)
+ */
+class ProcessorLoopClosure : public ProcessorBase
+{
+    protected:
+
+        ParamsProcessorLoopClosurePtr params_loop_closure_;
+
+    public:
+
+        ProcessorLoopClosure(const std::string& _type, int _dim, ParamsProcessorLoopClosurePtr _params_loop_closure);
+
+        ~ProcessorLoopClosure() override = default;
+        void configure(SensorBasePtr _sensor) override { };
+
+    protected:
+
+        /** \brief Process a capture (linked to a frame)
+         * If voteFindLoopClosures() returns true, findLoopClosures() is called.
+         * emplaceFactors() is called for pairs of current capture and each capture returned by findLoopClosures()
+         */
+        virtual void process(CaptureBasePtr);
+
+        /** \brief Returns if findLoopClosures() has to be called for the given capture
+         */
+        virtual bool voteFindLoopClosures(CaptureBasePtr cap) = 0;
+
+        /** \brief detects and emplaces all features of the given capture
+         */
+        virtual void emplaceFeatures(CaptureBasePtr cap) = 0;
+
+        /** \brief Find captures that correspond to loop closures with the given capture
+         */
+        virtual std::map<double,MatchLoopClosurePtr> findLoopClosures(CaptureBasePtr _capture) = 0;
+
+        /** \brief validates a loop closure
+         */
+        virtual bool validateLoopClosure(MatchLoopClosurePtr) = 0;
+
+        /** \brief emplaces the factor(s) corresponding to a Loop Closure between two captures
+         */
+        virtual void emplaceFactors(MatchLoopClosurePtr) = 0;
+
+        void processCapture(CaptureBasePtr) override;
+        void processKeyFrame(FrameBasePtr, const double&) override;
+
+        bool triggerInCapture(CaptureBasePtr _cap) const override { return true;};
+        bool triggerInKeyFrame(FrameBasePtr _frm, const double& _time_tol) const override { return true;};
+
+        bool storeKeyFrame(FrameBasePtr _frm) override { return false;};
+        bool storeCapture(CaptureBasePtr _cap) override { return false;};
+
+        bool voteForKeyFrame() const override { return false;};
+};
+
+} // namespace wolf
+
+#endif /* _WOLF_PROCESSOR_LOOP_CLOSURE_BASE_H */
diff --git a/include/core/processor/processor_loopclosure.h b/include/core/processor/processor_loopclosure.h
deleted file mode 100644
index 12e1fd8781627154315b4815a86105e1d404e86f..0000000000000000000000000000000000000000
--- a/include/core/processor/processor_loopclosure.h
+++ /dev/null
@@ -1,183 +0,0 @@
-#ifndef _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H
-#define _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H
-
-// Wolf related headers
-#include "core/processor/processor_base.h"
-
-namespace wolf{
-
-WOLF_STRUCT_PTR_TYPEDEFS(ParamsProcessorLoopClosure);
-
-struct ParamsProcessorLoopClosure : public ParamsProcessorBase
-{
-    using ParamsProcessorBase::ParamsProcessorBase;
-    //  virtual ~ParamsProcessorLoopClosure() = default;
-
-    // add neccesery parameters for loop closure initialisation here and initialize
-    // them in constructor
-};
-
-/** \brief General loop closure processor
- *
- * This is an abstract class.
- * + You must define the following classes :
- *   - voteComputeFeatures()
- *   - voteSearchLoopClosure()
- *   - computeFeatures()
- *   - findLoopCandidate()
- *   - createFactors()
- * + You can override the following classes :
- *   - selectPairKC()
- *   - validateLoop()
- *   - processLoopClosure()
- *
- * It establishes factors XXX
- *
- * Should you need extra functionality for your derived types,
- * you can overload these two methods,
- *
- *   -  preProcess() { }
- *   -  postProcess() { }
- *
- * which are called at the beginning and at the end of process() respectively.
- */
-
-class ProcessorLoopClosure : public ProcessorBase
-{
-protected:
-
-    ParamsProcessorLoopClosurePtr params_loop_closure_;
-
-public:
-
-    ProcessorLoopClosure(const std::string& _type, int _dim, ParamsProcessorLoopClosurePtr _params_loop_closure);
-
-    ~ProcessorLoopClosure() override = default;
-    void configure(SensorBasePtr _sensor) override { };
-
-protected:
-    /** \brief process an incoming capture
-     *
-     * The ProcessorLoopClosure is only triggered in KF (see triggerInCapture()) so this function is not called.
-     */
-    void processCapture(CaptureBasePtr) override {};
-
-    /** \brief process an incoming key-frame
-     *
-     * Each derived processor should implement this function. It will be called if:
-     *  - A new KF arrived and triggerInKF() returned true.
-     */
-    void processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance) override;
-
-    /** \brief trigger in capture
-     *
-     * The ProcessorLoopClosure only processes incoming KF, then it returns false.
-     */
-    bool triggerInCapture(CaptureBasePtr) const override {return false;}
-
-    /** \brief trigger in key-frame
-     *
-     * Returns true if processKeyFrame() should be called after the provided KF arrived.
-     */
-    bool triggerInKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tol_other) const override;
-
-    /** \brief store key frame
-    *
-    * Returns true if the key frame should be stored
-    */
-    bool storeKeyFrame(FrameBasePtr) override;
-
-    /** \brief store capture
-    *
-    * Returns true if the capture should be stored
-    */
-    bool storeCapture(CaptureBasePtr) override;
-
-    /** \brief Called by process(). Tells if computeFeatures() will be called
-     */
-    virtual bool voteComputeFeatures() = 0;
-
-    /** \brief Called by process(). Tells if findLoopCandidate() and createFactors() will be called
-     *
-     * WARNING : A LC can be searched only when voteComputeFeatures() return true
-     */
-    virtual bool voteSearchLoopClosure() = 0;
-
-    /** \brief returns a KeyFrame-Capture pair compatible together (selected from the buffers)
-     *
-     * Should clear elements before the ones selected in buffers.
-     * In the default implementation, we select the KF with the most recent TimeStamp
-     * and that is compatible with at least a Capture
-     */
-    virtual std::pair<FrameBasePtr,CaptureBasePtr> selectPairKC(void);
-
-    /** \brief add the Capture and all features needed to the corresponding KF
-     *
-     * If the loop closure process requires features associated to each capture,
-     * the computations to create these features must be done here.
-     *
-     * Important: All detected features should be emplaced to the capture.
-     *
-     * Returns a bool that tells if features were successfully created
-     */
-    virtual bool detectFeatures(CaptureBasePtr cap) = 0;
-
-    /** \brief Find a KF that would be a good candidate to close a loop
-     * if validateLoop is not overwritten, a loop will be closed with the returned candidate
-     * if no good candidate is found, return nullptr
-     */
-    virtual CaptureBasePtr findLoopCandidate(CaptureBasePtr _capture) = 0;
-
-    /** \brief validate/discard a loop closure
-     *
-     * overwrite it if you want an additional test after findLoopCandidate()
-     */
-    virtual bool validateLoop(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) {return true;};
-
-    /** \brief emplace the factor(s)
-     *
-     */
-    virtual void emplaceFactors(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) = 0;
-
-
-    /** Pre-process incoming Capture
-    *
-    * This is called by process() just after assigning incoming_ptr_ to a valid Capture.
-    *
-    * Overload this function to prepare stuff on derived classes.
-    *
-    * Typical uses of prePrecess() are:
-    *   - casting base types to derived types
-    *   - initializing counters, flags, or any derived variables
-    *   - initializing algorithms needed for processing the derived data
-    */
-    virtual void preProcess() { }
-
-    /** Post-process
-    *
-    * This is called by process() after finishing the processing algorithm.
-    *
-    * Overload this function to post-process stuff on derived classes.
-    *
-    * Typical uses of postPrecess() are:
-    *   - resetting and/or clearing variables and/or algorithms at the end of processing
-    *   - drawing / printing / logging the results of the processing
-    */
-    virtual void postProcess() { }
-
-    /** \brief Vote for KeyFrame generation
-    *
-    * If a KeyFrame criterion is validated, this function returns true,
-    * meaning that it wants to create a KeyFrame at the \b last Capture.
-    *
-    * WARNING! This function only votes! It does not create KeyFrames!
-    */
-    bool voteForKeyFrame() const override
-    {
-        return false;
-    };
-};
-
-} // namespace wolf
-
-#endif /* _WOLF_PROCESSOR_LOOPCLOSURE_BASE_H */
diff --git a/include/core/sensor/sensor_base.h b/include/core/sensor/sensor_base.h
index f4cae62c31f2c8389107388bdebedbec72a68b5b..b80ac75c31d27014cb1affc8d9012e60b6476017 100644
--- a/include/core/sensor/sensor_base.h
+++ b/include/core/sensor/sensor_base.h
@@ -244,6 +244,8 @@ class SensorBase : public NodeBase, public HasStateBlocks, public std::enable_sh
                                  bool state_blocks,
                                  std::ostream& stream ,
                                  std::string _tabs = "") const;
+        void printState (bool _metric, bool _state_blocks, std::ostream& _stream, std::string _tabs) const;
+
         void print(int depth, //
                    bool constr_by, //
                    bool metric, //
diff --git a/include/core/sensor/sensor_model.h b/include/core/sensor/sensor_model.h
new file mode 100644
index 0000000000000000000000000000000000000000..5e2a37df70243f7d331dadde464fffd1d7f081ef
--- /dev/null
+++ b/include/core/sensor/sensor_model.h
@@ -0,0 +1,38 @@
+#ifndef SRC_SENSOR_MODEL_H_
+#define SRC_SENSOR_MODEL_H_
+
+//wolf includes
+#include "core/sensor/sensor_base.h"
+
+namespace wolf {
+
+WOLF_PTR_TYPEDEFS(SensorModel);
+
+class SensorModel : public SensorBase
+{
+    public:
+        SensorModel();
+        ~SensorModel() override;
+
+        static SensorBasePtr create(const std::string& _unique_name,
+                                    const ParamsServer& _server)
+        {
+            auto sensor = std::make_shared<SensorModel>();
+            sensor      ->setName(_unique_name);
+            return sensor;
+        }
+
+        static SensorBasePtr create(const std::string& _unique_name,
+                                    const Eigen::VectorXd& _extrinsics,
+                                    const ParamsSensorBasePtr _intrinsics)
+        {
+            auto sensor = std::make_shared<SensorModel>();
+            sensor      ->setName(_unique_name);
+            return sensor;
+        }
+};
+
+
+} /* namespace wolf */
+
+#endif /* SRC_SENSOR_POSE_H_ */
diff --git a/include/core/solver/solver_manager.h b/include/core/solver/solver_manager.h
index 58d21a5cf834cc18372a197b3bbbead9894e46f6..f3fbeefb3dce65e58ad82d3c604ba3e554169624 100644
--- a/include/core/solver/solver_manager.h
+++ b/include/core/solver/solver_manager.h
@@ -131,6 +131,8 @@ class SolverManager
 
         virtual double finalCost() = 0;
 
+        virtual double totalTime() = 0;
+
         /**
          * \brief Updates solver's problem according to the wolf_problem
          */
diff --git a/include/core/state_block/state_composite.h b/include/core/state_block/state_composite.h
index 7989fd807eab08187c802e0386af02de56ef4f1b..d0ddf4e7fc453212f345640f746cca212170f3b7 100644
--- a/include/core/state_block/state_composite.h
+++ b/include/core/state_block/state_composite.h
@@ -31,7 +31,6 @@ class VectorComposite : public std::unordered_map < char, Eigen::VectorXd >
         VectorComposite() {};
         VectorComposite(const StateStructure& _s);
         VectorComposite(const StateStructure& _s, const std::list<int>& _sizes);
-        VectorComposite(const VectorComposite & v) : unordered_map<char, VectorXd>(v){};
         /**
          * \brief Construct from Eigen::VectorXd and structure
          *
diff --git a/include/core/utils/graph_search.h b/include/core/utils/graph_search.h
new file mode 100644
index 0000000000000000000000000000000000000000..cf14ff844d981922848645774799f2b18655e06f
--- /dev/null
+++ b/include/core/utils/graph_search.h
@@ -0,0 +1,44 @@
+#ifndef GRAPH_SEARCH_H
+#define GRAPH_SEARCH_H
+
+#include "core/common/wolf.h"
+#include "core/frame/frame_base.h"
+#include "core/factor/factor_base.h"
+
+#include <map>
+
+namespace wolf
+{
+
+class GraphSearch
+{
+    private:
+
+        std::map<FrameBasePtr, std::pair<FactorBasePtr,FrameBasePtr>> parents_;
+
+    public:
+
+        GraphSearch();
+
+        ~GraphSearch();
+
+        FactorBasePtrList computeShortestPath(FrameBasePtr frm1,
+                                              FrameBasePtr frm2,
+                                              const unsigned int max_graph_dist = 0);
+
+        std::set<FrameBasePtr> getNeighborFrames(const std::set<FrameBasePtr>& frms);
+
+        static FactorBasePtrList shortestPath(FrameBasePtr frm1,
+                                              FrameBasePtr frm2,
+                                              const unsigned int max_graph_dist = 0)
+        {
+            GraphSearch graph_search;
+
+            return graph_search.computeShortestPath(frm1, frm2, max_graph_dist);
+        }
+
+};
+
+
+}  // namespace wolf
+#endif
diff --git a/src/capture/capture_motion.cpp b/src/capture/capture_motion.cpp
index 16a535dc987deea66756f4c8468ac3005f2b3573..b87e4fb10c37d79ede036e72983faea5d7d12b7c 100644
--- a/src/capture/capture_motion.cpp
+++ b/src/capture/capture_motion.cpp
@@ -49,7 +49,7 @@ CaptureMotion::~CaptureMotion()
 
 bool CaptureMotion::containsTimeStamp (const TimeStamp& _ts, double _time_tolerance)
 {
-    assert(_ts.ok());
+    assert(_ts.ok() and this->time_stamp_.ok());
 
     // the same capture is within tolerance
     if (this->time_stamp_ - _time_tolerance <= _ts && _ts <= this->time_stamp_ + _time_tolerance)
diff --git a/src/ceres_wrapper/solver_ceres.cpp b/src/ceres_wrapper/solver_ceres.cpp
index a2d7616c58181be35b9aae642e0e8e9f956b4879..5914b40a8bc67e44bbc392b146766331e344ced7 100644
--- a/src/ceres_wrapper/solver_ceres.cpp
+++ b/src/ceres_wrapper/solver_ceres.cpp
@@ -612,6 +612,11 @@ double SolverCeres::finalCost()
     return double(summary_.final_cost);
 }
 
+double SolverCeres::totalTime()
+{
+    return double(summary_.total_time_in_seconds);
+}
+
 ceres::CostFunctionPtr SolverCeres::createCostFunction(const FactorBasePtr& _fac_ptr)
 {
     assert(_fac_ptr != nullptr);
diff --git a/src/factor/factor_base.cpp b/src/factor/factor_base.cpp
index 5e3fef35efa980c310226d3976cf7d3387fbbd9d..df80bf00bc0f38d08d2b9c6806a7d0ef9a01a8c7 100644
--- a/src/factor/factor_base.cpp
+++ b/src/factor/factor_base.cpp
@@ -307,7 +307,7 @@ void FactorBase::setProblem(ProblemPtr _problem)
 
 void FactorBase::printHeader(int _depth, bool _constr_by, bool _metric, bool _state_blocks, std::ostream& _stream, std::string _tabs) const
 {
-    _stream << _tabs << "Fac" << id() << " " << getType() << " -->";
+    _stream << _tabs << "Fac" << id() << " " << getType() << (getStatus() == FAC_ACTIVE ? "" : " INACTIVE") << " -->";
     if (       getFrameOtherList()   .empty()
                && getCaptureOtherList() .empty()
                && getFeatureOtherList() .empty()
diff --git a/src/frame/frame_base.cpp b/src/frame/frame_base.cpp
index 7df23b32b9d6f1bf876f796715ff6903fc6fd4bb..71e344552517ef659bb3ae60f66abbb95c25056f 100644
--- a/src/frame/frame_base.cpp
+++ b/src/frame/frame_base.cpp
@@ -165,6 +165,7 @@ FrameBasePtr FrameBase::getNextFrame() const
 
 CaptureBasePtr FrameBase::addCapture(CaptureBasePtr _capt_ptr)
 {
+    WOLF_WARN_COND(getCaptureOf(_capt_ptr->getSensor()) != nullptr, "FrameBase::addCapture adding new capture ", _capt_ptr->id(), " in a frame with another capture of the same sensor: ", getCaptureOf(_capt_ptr->getSensor())->id());
     capture_list_.push_back(_capt_ptr);
     return _capt_ptr;
 }
@@ -174,6 +175,23 @@ void FrameBase::removeCapture(CaptureBasePtr _capt_ptr)
     capture_list_.remove(_capt_ptr);
 }
 
+CaptureBasePtr FrameBase::getCaptureOfType(const std::string& type) const
+{
+    for (CaptureBasePtr capture_ptr : getCaptureList())
+        if (capture_ptr->getType() == type)
+            return capture_ptr;
+    return nullptr;
+}
+
+CaptureBasePtrList FrameBase::getCapturesOfType(const std::string& type) const
+{
+    CaptureBasePtrList captures;
+    for (CaptureBasePtr capture_ptr : getCaptureList())
+        if (capture_ptr->getType() == type)
+            captures.push_back(capture_ptr);
+    return captures;
+}
+
 CaptureBasePtr FrameBase::getCaptureOf(const SensorBasePtr _sensor_ptr, const std::string& type) const
 {
     for (CaptureBasePtr capture_ptr : getCaptureList())
@@ -205,6 +223,11 @@ FactorBasePtr FrameBase::getFactorOf(const ProcessorBasePtr _processor_ptr, cons
     for (const FactorBasePtr& factor_ptr : getConstrainedByList())
         if (factor_ptr->getProcessor() == _processor_ptr && factor_ptr->getType() == type)
             return factor_ptr;
+
+    for (const FactorBasePtr& factor_ptr : getFactorList())
+        if (factor_ptr->getProcessor() == _processor_ptr && factor_ptr->getType() == type)
+            return factor_ptr;
+
     return nullptr;
 }
 
@@ -213,6 +236,11 @@ FactorBasePtr FrameBase::getFactorOf(const ProcessorBasePtr _processor_ptr) cons
     for (const FactorBasePtr& factor_ptr : getConstrainedByList())
         if (factor_ptr->getProcessor() == _processor_ptr)
             return factor_ptr;
+
+    for (const FactorBasePtr& factor_ptr : getFactorList())
+        if (factor_ptr->getProcessor() == _processor_ptr)
+            return factor_ptr;
+
     return nullptr;
 }
 
diff --git a/src/processor/processor_base.cpp b/src/processor/processor_base.cpp
index 794699f4533a7d2f5e6271e8f31b05b0b36cbd69..28963ed644aacd240d2d01a0bb23e6280dc6619a 100644
--- a/src/processor/processor_base.cpp
+++ b/src/processor/processor_base.cpp
@@ -121,7 +121,7 @@ void ProcessorBase::link(SensorBasePtr _sen_ptr)
 void ProcessorBase::setProblem(ProblemPtr _problem)
 {
     std::string str = "Processor works with " + std::to_string(dim_) + "D but problem is " + std::to_string(_problem->getDim()) + "D";
-    assert((dim_ == 0 or dim_ == _problem->getDim()) && str.c_str());
+    assert(((dim_ == 0) or (dim_ == _problem->getDim())) && str.c_str());
 
     if (_problem == nullptr or _problem == this->getProblem())
         return;
diff --git a/src/processor/processor_fix_wing_model.cpp b/src/processor/processor_fix_wing_model.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7c12e90f8f08005d2f0086d57ad1633534dbe8ea
--- /dev/null
+++ b/src/processor/processor_fix_wing_model.cpp
@@ -0,0 +1,66 @@
+/*
+ * processor_fix_wing_model.cpp
+ *
+ *  Created on: Sep 6, 2021
+ *      Author: joanvallve
+ */
+
+#include "core/processor/processor_fix_wing_model.h"
+
+#include "core/capture/capture_base.h"
+#include "core/feature/feature_base.h"
+#include "core/factor/factor_velocity_local_direction_3d.h"
+
+namespace wolf
+{
+
+ProcessorFixWingModel::ProcessorFixWingModel(ParamsProcessorFixWingModelPtr _params) :
+        ProcessorBase("ProcessorFixWingModel", 3, _params),
+        params_processor_(_params)
+{
+}
+
+ProcessorFixWingModel::~ProcessorFixWingModel()
+{
+}
+
+void ProcessorFixWingModel::configure(SensorBasePtr _sensor)
+{
+    assert(_sensor->getProblem()->getFrameStructure().find('V') != std::string::npos && "Processor only works with problems with V");
+}
+
+void ProcessorFixWingModel::processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance)
+{
+    if (_keyframe_ptr->getV()->isFixed())
+        return;
+
+    if (_keyframe_ptr->getFactorOf(shared_from_this()) != nullptr)
+        return;
+
+    // emplace capture
+    auto cap = CaptureBase::emplace<CaptureBase>(_keyframe_ptr, "CaptureBase",
+                                                 _keyframe_ptr->getTimeStamp(), getSensor());
+    
+    // emplace feature
+    auto fea = FeatureBase::emplace<FeatureBase>(cap, "FeatureBase", 
+                                                 params_processor_->velocity_local,
+                                                 Eigen::Matrix1d(params_processor_->angle_stdev * params_processor_->angle_stdev));
+    
+    // emplace factor
+    auto fac = FactorBase::emplace<FactorVelocityLocalDirection3d>(fea,
+                                                                   fea,
+                                                                   params_processor_->min_vel_norm,
+                                                                   shared_from_this(),
+                                                                   false);
+}
+
+} /* namespace wolf */
+
+
+// Register in the FactoryProcessor
+#include "core/processor/factory_processor.h"
+namespace wolf {
+WOLF_REGISTER_PROCESSOR(ProcessorFixWingModel);
+WOLF_REGISTER_PROCESSOR_AUTO(ProcessorFixWingModel);
+} // namespace wolf
+
diff --git a/src/processor/processor_loop_closure.cpp b/src/processor/processor_loop_closure.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..486be875ac6eb306e6e0a7973742b7f3e52241c7
--- /dev/null
+++ b/src/processor/processor_loop_closure.cpp
@@ -0,0 +1,154 @@
+#include "core/processor/processor_loop_closure.h"
+
+namespace wolf
+{
+
+ProcessorLoopClosure::ProcessorLoopClosure(const std::string& _type,
+                                           int _dim,
+                                           ParamsProcessorLoopClosurePtr _params_loop_closure):
+        ProcessorBase(_type, _dim, _params_loop_closure),
+        params_loop_closure_(_params_loop_closure)
+{
+    //
+}
+
+void ProcessorLoopClosure::processCapture(CaptureBasePtr _capture)
+{
+    /* This function has 3 scenarios:
+     *  1. Capture already linked to a frame (in trajectory) -> process
+     *  2. Capture has a timestamp compatible with any stored frame -> link + process
+     *  3. Otherwise -> store capture (Note that more than one processor can be emplacing frames, so an older frame can arrive later than this one)
+     */
+
+    WOLF_DEBUG("ProcessorLoopClosure::processCapture capture ", _capture->id());
+
+    // CASE 1:
+    if (_capture->getFrame() and _capture->getFrame()->getTrajectory())
+    {
+        WOLF_DEBUG("CASE 1");
+
+        process(_capture);
+
+        // remove the frame and older frames
+        buffer_pack_kf_.removeUpTo(_capture->getFrame()->getTimeStamp());
+
+        return;
+    }
+
+    // Search for any stored frame within time tolerance of capture
+    auto frame_pack = buffer_pack_kf_.select(_capture->getTimeStamp(), params_->time_tolerance);
+
+    // CASE 2:
+    if (_capture->getFrame() == nullptr and frame_pack)
+    {
+        WOLF_DEBUG("CASE 2");
+
+        _capture->link(frame_pack->key_frame);
+
+        process(_capture);
+
+        // remove the frame and older frames
+        buffer_pack_kf_.removeUpTo(frame_pack->key_frame->getTimeStamp());
+
+        return;
+    }
+    // CASE 3:
+    WOLF_DEBUG("CASE 3");
+    buffer_capture_.add(_capture->getTimeStamp(), _capture);
+}
+
+void ProcessorLoopClosure::processKeyFrame(FrameBasePtr _frame, const double& _time_tolerance)
+{
+    /* This function has 4 scenarios:
+     *  1. Frame already have a capture of the sensor -> process
+     *  2. Frame has a timestamp within time tolerances of any stored capture -> link + process
+     *  3. Frame is more recent than any stored capture -> store frame to be processed later in processCapture
+     *  4. Otherwise: The frame is not compatible with any stored capture -> discard frame
+     */
+
+    WOLF_DEBUG("ProcessorLoopClosure::processKeyFrame frame ", _frame->id());
+
+    // CASE 1:
+    auto cap = _frame->getCaptureOf(getSensor());
+    if (cap)
+    {
+        WOLF_DEBUG("CASE 1");
+
+        process(cap);
+
+        // remove the capture (if stored)
+        buffer_capture_.getContainer().erase(cap->getTimeStamp());
+
+        return;
+    }
+
+    // Search for any stored capture within time tolerance of frame
+    auto capture = buffer_capture_.select(_frame->getTimeStamp(), params_->time_tolerance);
+
+    // CASE 2:
+    if (capture and not capture->getFrame())
+    {
+        WOLF_DEBUG("CASE 2");
+
+        capture->link(_frame);
+
+        process(capture);
+
+        // remove the capture (if stored)
+        buffer_capture_.getContainer().erase(capture->getTimeStamp());
+
+        // remove old captures (10s of old captures are kept in case frames arrives unordered)
+        buffer_capture_.removeUpTo(_frame->getTimeStamp() - 10);
+
+        return;
+    }
+    // CASE 3:
+    if (buffer_capture_.selectLastAfter(_frame->getTimeStamp(), params_->time_tolerance) == nullptr)
+    {
+        WOLF_DEBUG("CASE 3");
+
+        // store frame
+        buffer_pack_kf_.add(_frame, _time_tolerance);
+
+        return;
+    }
+    // CASE 4:
+    WOLF_DEBUG("CASE 4");
+    // nothing (discard frame)
+}
+
+void ProcessorLoopClosure::process(CaptureBasePtr _capture)
+{
+    assert(_capture->getFrame() != nullptr && "ProcessorLoopClosure::process _capture not linked to _frame");
+    WOLF_DEBUG("ProcessorLoopClosure::process frame ", _capture->getFrame()->id(), " capture ", _capture->id());
+
+    // Detect and emplace features
+    WOLF_DEBUG("emplacing features...");
+    emplaceFeatures(_capture);
+
+    // Vote for loop closure search
+    if (voteFindLoopClosures(_capture))
+    {
+        WOLF_DEBUG("finding loop closures...");
+
+        // Find loop closures
+        auto match_lc_map = findLoopClosures(_capture);
+
+        WOLF_DEBUG(match_lc_map.size(), " loop closures found");
+
+        // Emplace factors for each LC if validated
+        auto n_loops = 0;
+        for (const auto& match_pair : match_lc_map)
+            if (validateLoopClosure(match_pair.second))
+            {
+                emplaceFactors(match_pair.second);
+                n_loops++;
+
+                if (params_loop_closure_->max_loops > 0 and
+                    n_loops >= params_loop_closure_->max_loops)
+                    break;
+            }
+    }
+}
+
+}// namespace wolf
diff --git a/src/processor/processor_loopclosure.cpp b/src/processor/processor_loopclosure.cpp
deleted file mode 100644
index 16e037b1c7634c4d0d7509fea51e075fbc8d0066..0000000000000000000000000000000000000000
--- a/src/processor/processor_loopclosure.cpp
+++ /dev/null
@@ -1,119 +0,0 @@
-/**
- * \file processor_loop_closure.h
- *
- *  Created on: Mai 31, 2019
- *      \author: Pierre Guetschel
- */
-
-#include "core/processor/processor_loopclosure.h"
-
-
-namespace wolf
-{
-
-ProcessorLoopClosure::ProcessorLoopClosure(const std::string& _type,
-                                           int _dim,
-                                           ParamsProcessorLoopClosurePtr _params_loop_closure):
-        ProcessorBase(_type, _dim, _params_loop_closure),
-        params_loop_closure_(_params_loop_closure)
-{
-    //
-}
-
-//##############################################################################
-void ProcessorLoopClosure::processKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tolerance)
-{
-    // the pre-process, if necessary, is implemented in the derived classes
-    preProcess();
-
-    if (voteComputeFeatures())
-    {
-        std::pair<FrameBasePtr,CaptureBasePtr> pairKC = selectPairKC();
-
-        auto cap_1 = pairKC.second;
-        auto kf_1  = pairKC.first;
-
-        if (kf_1==nullptr || cap_1==nullptr) return;
-        bool success_computeFeatures = detectFeatures(cap_1);
-
-        // if succeded
-        if (success_computeFeatures)
-        {
-            // link the capture to the KF (if not already linked)
-            if (cap_1->getFrame() != kf_1)
-            {
-                assert(cap_1->getFrame() == nullptr && "capture already linked to a different frame"); //FIXME
-                cap_1->link(kf_1);
-            }
-
-            // search loop closure
-            if(voteSearchLoopClosure())
-            {
-                auto cap_2 = findLoopCandidate(cap_1);
-                if (cap_2==nullptr)
-                    return;
-                if (!validateLoop(cap_1, cap_2))
-                    return;
-                if (cap_1->getFrame() == nullptr || cap_2->getFrame() == nullptr)
-                {
-                    WOLF_WARN("ProcessorLoopClosureBase : tried to close a loop with captures linked to no KF");
-                    return;
-                }
-                if (cap_1->getFrame() == cap_2->getFrame())
-                {
-                    WOLF_WARN("ProcessorLoopClosureBase : findLoopCandidate() returned two captures of the same frame");
-                    return;
-                }
-                emplaceFactors(cap_1, cap_2);
-            }
-        }
-    }
-
-    // the post-process, if necessary, is implemented in the derived classes
-    postProcess();
-}
-
-bool ProcessorLoopClosure::triggerInKeyFrame(FrameBasePtr _keyframe_ptr, const double& _time_tol_other) const
-{
-    return true;
-}
-bool ProcessorLoopClosure::storeKeyFrame(FrameBasePtr _frame_ptr)
-{
-  return true;
-}
-bool ProcessorLoopClosure::storeCapture(CaptureBasePtr _cap_ptr)
-{
-  return true;
-}
-
-/**
- * In the default implementation, we select the KF with the most recent TimeStamp
- * and that is compatible with at least a Capture
- */
-std::pair<FrameBasePtr,CaptureBasePtr> ProcessorLoopClosure::selectPairKC()
-{
-    auto kf_container = buffer_pack_kf_.getContainer();
-
-    if (kf_container.empty())
-        return std::make_pair(nullptr, nullptr);
-
-    for (auto kf_it=kf_container.begin(); kf_it!=kf_container.end(); ++kf_it)
-    {
-        CaptureBasePtr cap_ptr = buffer_capture_.select(kf_it->first, kf_it->second->time_tolerance);
-        if (cap_ptr != nullptr)
-        {
-            // clear the buffers :
-            buffer_capture_.removeUpTo(cap_ptr->getTimeStamp());
-            buffer_pack_kf_.removeUpTo(kf_it->first);
-            // return the KF-Cap pair :
-            return std::make_pair(kf_it->second->key_frame, cap_ptr);
-        }
-    }
-    return std::make_pair(nullptr, nullptr);
-}
-
-
-//##############################################################################
-
-
-}// namespace wolf
diff --git a/src/processor/processor_motion.cpp b/src/processor/processor_motion.cpp
index 11dee8587c0e1ba03a77f23902f6a7a18c00e6ec..9b7f8da44e1e58c86277a2cb689401426932c723 100644
--- a/src/processor/processor_motion.cpp
+++ b/src/processor/processor_motion.cpp
@@ -540,7 +540,7 @@ VectorComposite ProcessorMotion::getState(const StateStructure& _structure) cons
     // Get state of origin
     const auto& x_origin = getOrigin()->getFrame()->getState(state_structure_);
 
-    // Get most rescent motion
+    // Get most recent motion
     const auto& motion = last_ptr_->getBuffer().back();
 
     // Get delta preintegrated up to now
@@ -550,6 +550,8 @@ VectorComposite ProcessorMotion::getState(const StateStructure& _structure) cons
     const auto& calib_preint = last_ptr_->getCalibrationPreint();
 
     VectorComposite state;
+    //WOLF_INFO("processorMotion last timestamp: ", last_ptr_->getTimeStamp());
+    //WOLF_INFO("processorMotion origin timestamp: ", origin_ptr_->getTimeStamp());
     if ( hasCalibration())
     {
         // Get current calibration -- from origin capture
@@ -894,6 +896,7 @@ CaptureMotionPtr ProcessorMotion::findCaptureContainingTimeStamp(const TimeStamp
         capture = frame->getCaptureOf(sensor);
         if (capture != nullptr)
         {
+            assert(std::dynamic_pointer_cast<CaptureMotion>(capture) != nullptr);
             // Rule 1 satisfied! We found a Capture belonging to this processor's Sensor ==> it is a CaptureMotion
             capture_motion = std::static_pointer_cast<CaptureMotion>(capture);
 
diff --git a/src/sensor/sensor_base.cpp b/src/sensor/sensor_base.cpp
index 0d2d54638d145f333efa0e349990beda54d1b17e..0700d403f00f24fc4effcc8d176cdea9277be1a5 100644
--- a/src/sensor/sensor_base.cpp
+++ b/src/sensor/sensor_base.cpp
@@ -301,7 +301,7 @@ bool SensorBase::process(const CaptureBasePtr capture_ptr)
 {
     capture_ptr->setSensor(shared_from_this());
 
-    for (const auto processor : processor_list_)
+    for (const auto& processor : processor_list_)
     {
 
 #ifdef PROFILING
@@ -429,6 +429,36 @@ void SensorBase::printHeader(int _depth, bool _constr_by, bool _metric, bool _st
     printState(_metric, _state_blocks, _stream, _tabs);
 }
 
+void SensorBase::printState (bool _metric, bool _state_blocks, std::ostream& _stream, std::string _tabs) const
+{
+    if (_metric && _state_blocks)
+    {
+        for (const auto &key : getStructure())
+        {
+            auto sb = getStateBlockDynamic(key);
+            if (sb)
+                _stream << _tabs << "  " << key << "[" << (sb->isFixed() ? "Fix" : "Est") << "] = ( "
+                        << std::setprecision(3) << sb->getState().transpose() << " )" << " @ " << sb << std::endl;
+        }
+    }
+    else if (_metric)
+    {
+        _stream << _tabs << "  " << (isFixed() ? "Fix" : "Est") << ",\t x = ( " << std::setprecision(3)
+                << getStateVector().transpose() << " )" << std::endl;
+    }
+    else if (_state_blocks)
+    {
+        _stream << _tabs << "  " << "sb:";
+        for (const auto &key : getStructure())
+        {
+            const auto &sb = getStateBlockDynamic(key);
+            if (sb)
+                _stream << "    " << key << "[" << (sb->isFixed() ? "Fix" : "Est") << "] @ " << sb;
+        }
+        _stream << std::endl;
+    }
+}
+
 void SensorBase::print(int _depth, bool _constr_by, bool _metric, bool _state_blocks, std::ostream& _stream, std::string _tabs) const
 {
     printHeader(_depth, _constr_by, _metric, _state_blocks, _stream, _tabs);
@@ -509,4 +539,5 @@ bool SensorBase::check(CheckLog& _log, std::shared_ptr<NodeBase> _node_ptr, bool
     return _log.is_consistent_;
 }
 
+
 } // namespace wolf
diff --git a/src/sensor/sensor_model.cpp b/src/sensor/sensor_model.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6eb00f021667436360821f27e5684a512eb425e4
--- /dev/null
+++ b/src/sensor/sensor_model.cpp
@@ -0,0 +1,24 @@
+#include "core/sensor/sensor_model.h"
+
+namespace wolf {
+
+
+SensorModel::SensorModel() :
+    SensorBase("SensorModel", nullptr, nullptr, nullptr, 6)
+{
+    //
+}
+
+SensorModel::~SensorModel()
+{
+    //
+}
+
+} // namespace wolf
+
+// Register in the FactorySensor
+#include "core/sensor/factory_sensor.h"
+namespace wolf {
+WOLF_REGISTER_SENSOR(SensorModel);
+WOLF_REGISTER_SENSOR_AUTO(SensorModel);
+}
diff --git a/src/state_block/state_composite.cpp b/src/state_block/state_composite.cpp
index 169bc82ca8edd1fa751bac1e309aa2ce1eb95379..04658a0fb6ccc9fd20412e650709d0d9320e169c 100644
--- a/src/state_block/state_composite.cpp
+++ b/src/state_block/state_composite.cpp
@@ -112,9 +112,9 @@ wolf::VectorComposite operator +(const wolf::VectorComposite &_x, const wolf::Ve
     return xpy;
 }
 
-wolf::VectorComposite operator -(const wolf::VectorComposite &_x, const wolf::VectorComposite &_y)
+VectorComposite operator -(const VectorComposite &_x, const VectorComposite &_y)
 {
-    wolf::VectorComposite xpy;
+    VectorComposite xpy;
     for (const auto& pair_i_xi : _x)
     {
         const auto& i = pair_i_xi.first;
@@ -124,6 +124,17 @@ wolf::VectorComposite operator -(const wolf::VectorComposite &_x, const wolf::Ve
     return xpy;
 }
 
+VectorComposite operator -(const wolf::VectorComposite &_x)
+{
+    wolf::VectorComposite m;
+    for (const auto& pair_i_xi : _x)
+    {
+        const auto& i = pair_i_xi.first;
+        m.emplace(i, - _x.at(i));
+    }
+    return m;
+}
+
 void VectorComposite::set (const VectorXd& _v, const StateStructure& _structure, const std::list<int>& _sizes)
 {
     int index = 0;
@@ -145,17 +156,6 @@ void VectorComposite::setZero()
         pair_key_vec.second.setZero();
 }
 
-wolf::VectorComposite operator -(const wolf::VectorComposite &_x)
-{
-    wolf::VectorComposite m;
-    for (const auto& pair_i_xi : _x)
-    {
-        const auto& i = pair_i_xi.first;
-        m.emplace(i, - _x.at(i));
-    }
-    return m;
-}
-
 ////// MATRIX COMPOSITE //////////
 
 bool MatrixComposite::emplace(const char &_row, const char &_col, const Eigen::MatrixXd &_mat_blk)
diff --git a/src/utils/graph_search.cpp b/src/utils/graph_search.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d2876701b84d3acabf5e4e9c761a49570bffc2fe
--- /dev/null
+++ b/src/utils/graph_search.cpp
@@ -0,0 +1,119 @@
+#include "core/utils/graph_search.h"
+
+using namespace wolf;
+
+GraphSearch::GraphSearch() :
+                    parents_()
+{
+
+}
+
+GraphSearch::~GraphSearch()
+{
+
+}
+
+FactorBasePtrList GraphSearch::computeShortestPath(FrameBasePtr frm1,
+                                                   FrameBasePtr frm2,
+                                                   const unsigned int max_graph_dist)
+{
+    //WOLF_INFO("GraphSearch::computeShortestPath: from frame ", frm1->id(), " to frame ", frm2->id());
+
+    std::set<FrameBasePtr> frm_neigs({frm1});
+    parents_[frm1] = std::pair<FactorBasePtr,FrameBasePtr>(nullptr, nullptr);
+    unsigned int depth = 0;
+
+    //WOLF_INFO(frm1->id());
+
+    while (not frm_neigs.empty())
+    {
+        frm_neigs = getNeighborFrames(frm_neigs);
+        depth++;
+
+        //if (not frm_neigs.empty())
+        //{
+        //    std::string frm_neigs_str(depth, '.');
+        //    for (auto frm : frm_neigs)
+        //        frm_neigs_str += std::to_string(frm->id()) + std::string(" ");
+        //    WOLF_INFO(frm_neigs_str);
+        //}
+
+        // finish
+        if (frm_neigs.count(frm2) != 0)
+        {
+            //WOLF_INFO("Frame ", frm2->id(), " found!");
+
+            assert(parents_.count(frm2) != 0);
+            FactorBasePtrList factor_path;
+            auto frm_it = frm2;
+
+            while (frm_it != frm1)
+            {
+                factor_path.push_back(parents_.at(frm_it).first);
+                frm_it = parents_.at(frm_it).second;
+            }
+
+            return factor_path;
+        }
+
+        // stop
+        if (max_graph_dist > 0 and depth == max_graph_dist)
+            break;
+    }
+    //WOLF_INFO("Path to frame ", frm2->id(), " NOT found!");
+
+    return FactorBasePtrList();
+}
+
+std::set<FrameBasePtr> GraphSearch::getNeighborFrames(const std::set<FrameBasePtr>& frms)
+{
+    std::set<FrameBasePtr> frm_neigs;
+
+    for (auto frm : frms)
+    {
+        // get constrained by factors
+        FactorBasePtrList facs_by = frm->getConstrainedByList();
+
+        // Iterate over all factors_by
+        for (auto && fac_by : facs_by)
+        {
+            //WOLF_INFO_COND(fac_by, "fac_by: ", fac_by->id());
+            //WOLF_INFO_COND(fac_by->getFrame(), "fac_by->getFrame(): ", fac_by->getFrame()->id());
+            if (fac_by and
+                fac_by->getFrame() and
+                parents_.count(fac_by->getFrame()) == 0)
+            {
+                //WOLF_INFO("registering");
+                frm_neigs.insert(fac_by->getFrame());
+                parents_[fac_by->getFrame()] = std::pair<FactorBasePtr,FrameBasePtr>(fac_by, frm);
+            }
+        }
+
+        // get the factors of this frame
+        FactorBasePtrList facs_own;
+        frm->getFactorList(facs_own);
+
+        // Iterate over all factors_own
+        for (auto && fac_own : facs_own)
+        {
+            //WOLF_INFO_COND(fac_own, "fac_own: ", fac_own->id());
+            //WOLF_INFO_COND(fac_own->getFrameOtherList().empty(), "fac_own->getFrameOtherList() is empty");
+            if (fac_own and not fac_own->getFrameOtherList().empty())
+                for (auto frm_other_w : fac_own->getFrameOtherList())
+                {
+                    auto frm_other = frm_other_w.lock();
+                    //WOLF_INFO_COND(frm_other, "frm_other ", frm_other->id());
+                    if (frm_other and
+                        parents_.count(frm_other) == 0)
+                    {
+                        //WOLF_INFO("registering");
+                        frm_neigs.insert(frm_other);
+                        parents_[frm_other] = std::pair<FactorBasePtr,FrameBasePtr>(fac_own, frm);
+                    }
+                }
+        }
+    }
+
+    return frm_neigs;
+}
+
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 4988d2a44860219f3cbf87d7331a01deaa057a0d..033a2c1222d3ad85f27178383e9bd7d765e58f54 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -80,6 +80,10 @@ target_link_libraries(gtest_feature_base ${PLUGIN_NAME})
 wolf_add_gtest(gtest_frame_base gtest_frame_base.cpp)
 target_link_libraries(gtest_frame_base ${PLUGIN_NAME})
 
+# GraphSearch class test
+wolf_add_gtest(gtest_graph_search gtest_graph_search.cpp)
+target_link_libraries(gtest_graph_search ${PLUGIN_NAME})
+
 # HasStateBlocks classes test
 wolf_add_gtest(gtest_has_state_blocks gtest_has_state_blocks.cpp)
 target_link_libraries(gtest_has_state_blocks ${PLUGIN_NAME})
@@ -217,6 +221,10 @@ target_link_libraries(gtest_factor_relative_pose_3d ${PLUGIN_NAME})
 wolf_add_gtest(gtest_factor_relative_pose_3d_with_extrinsics gtest_factor_relative_pose_3d_with_extrinsics.cpp)
 target_link_libraries(gtest_factor_relative_pose_3d_with_extrinsics ${PLUGIN_NAME})
 
+# FactorVelocityLocalDirection3d class test
+wolf_add_gtest(gtest_factor_velocity_local_direction_3d gtest_factor_velocity_local_direction_3d.cpp)
+target_link_libraries(gtest_factor_velocity_local_direction_3d ${PLUGIN_NAME})
+
 # MakePosDef function test
 wolf_add_gtest(gtest_make_posdef gtest_make_posdef.cpp)
 target_link_libraries(gtest_make_posdef ${PLUGIN_NAME})
@@ -230,12 +238,16 @@ wolf_add_gtest(gtest_param_prior gtest_param_prior.cpp)
 target_link_libraries(gtest_param_prior ${PLUGIN_NAME})
 
 # ProcessorDiffDriveSelfcalib class test
+wolf_add_gtest(gtest_processor_fix_wing_model gtest_processor_fix_wing_model.cpp)
+target_link_libraries(gtest_processor_fix_wing_model ${PLUGIN_NAME})
+
+# ProcessorFixWingModel class test
 wolf_add_gtest(gtest_processor_diff_drive gtest_processor_diff_drive.cpp)
 target_link_libraries(gtest_processor_diff_drive ${PLUGIN_NAME})
 
-# ProcessorLoopClosureBase class test
-wolf_add_gtest(gtest_processor_loopclosure gtest_processor_loopclosure.cpp)
-target_link_libraries(gtest_processor_loopclosure ${PLUGIN_NAME})
+# ProcessorLoopClosure class test
+wolf_add_gtest(gtest_processor_loop_closure gtest_processor_loop_closure.cpp)
+target_link_libraries(gtest_processor_loop_closure ${PLUGIN_NAME})
 
 # ProcessorFrameNearestNeighborFilter class test
 # wolf_add_gtest(gtest_processor_frame_nearest_neighbor_filter_2d gtest_processor_frame_nearest_neighbor_filter_2d.cpp)
diff --git a/test/dummy/processor_loop_closure_dummy.h b/test/dummy/processor_loop_closure_dummy.h
new file mode 100644
index 0000000000000000000000000000000000000000..388f2f0dd5f13624d21115a9351680ceb92ce743
--- /dev/null
+++ b/test/dummy/processor_loop_closure_dummy.h
@@ -0,0 +1,93 @@
+#ifndef TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_
+#define TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_
+
+#include "core/processor/processor_loop_closure.h"
+
+using namespace wolf;
+using namespace Eigen;
+
+WOLF_PTR_TYPEDEFS(ProcessorLoopClosureDummy);
+
+// dummy class:
+class ProcessorLoopClosureDummy : public ProcessorLoopClosure
+{
+    public:
+        ProcessorLoopClosureDummy(ParamsProcessorLoopClosurePtr _params) :
+            ProcessorLoopClosure("ProcessorLoopClosureDummy", 2, _params)
+        {
+        }
+
+    protected:
+        bool voteFindLoopClosures(CaptureBasePtr cap) override { return true;};
+        bool validateLoopClosure(MatchLoopClosurePtr match) override { return true;};
+
+        void emplaceFeatures(CaptureBasePtr cap) override
+        {
+            // feature = frame pose
+            FeatureBase::emplace<FeatureBase>(cap,
+                                              "FeatureLoopClosureDummy",
+                                              cap->getFrame()->getState().vector("PO"),
+                                              MatrixXd::Identity(3,3));
+        }
+
+        std::map<double,MatchLoopClosurePtr> findLoopClosures(CaptureBasePtr _capture) override
+        {
+            std::map<double,MatchLoopClosurePtr> match_lc_map;
+
+            auto old_frame = _capture->getFrame()->getPreviousFrame();
+            while (old_frame)
+            {
+                // match if features (frames psoe) are close enough
+                for (auto cap : old_frame->getCaptureList())
+                    for (auto feat : cap->getFeatureList())
+                        if (feat->getType() == "FeatureLoopClosureDummy" and
+                            (feat->getMeasurement() - _capture->getFeatureList().front()->getMeasurement()).norm() < 1e-3)
+                        {
+                            MatchLoopClosurePtr match = std::make_shared<MatchLoopClosure>();
+                            match->capture_reference = cap;
+                            match->capture_target = _capture;
+                            match->normalized_score = 1;
+
+                            while (match_lc_map.count(match->normalized_score))
+                                match->normalized_score -= 1e-9;
+
+                            match_lc_map.emplace(match->normalized_score, match);
+                        }
+
+                old_frame = old_frame->getPreviousFrame();
+            }
+
+            return match_lc_map;
+        }
+
+        void emplaceFactors(MatchLoopClosurePtr match) override
+        {
+            FeatureBasePtr feat_2;
+            for (auto feat : match->capture_target->getFeatureList())
+                if (feat->getType() == "FeatureLoopClosureDummy")
+                {
+                    feat_2 = feat;
+                    break;
+                }
+
+            FactorBase::emplace<FactorRelativePose2d>(feat_2, feat_2,
+                                                      match->capture_reference->getFrame(),
+                                                      shared_from_this(),
+                                                      false,
+                                                      TOP_LOOP);
+        }
+
+    public:
+        unsigned int getNStoredFrames()
+        {
+            return buffer_pack_kf_.getContainer().size();
+        }
+
+        unsigned int getNStoredCaptures()
+        {
+            return buffer_capture_.getContainer().size();
+        }
+};
+
+
+#endif /* TEST_DUMMY_PROCESSOR_LOOP_CLOSURE_DUMMY_H_ */
diff --git a/test/dummy/solver_manager_dummy.h b/test/dummy/solver_manager_dummy.h
index aab56cd1ab532a6a7d3e4b876f2de6dea0ea1b7b..cf45c71c94167f934504e5f97f91080d4d011ce8 100644
--- a/test/dummy/solver_manager_dummy.h
+++ b/test/dummy/solver_manager_dummy.h
@@ -60,6 +60,7 @@ class SolverManagerDummy : public SolverManager
         SizeStd iterations() override    { return 1;         }
         double  initialCost() override   { return double(1); }
         double  finalCost() override     { return double(0); }
+        double  totalTime() override     { return double(0); }
 
     protected:
 
diff --git a/test/gtest_factor_velocity_local_direction_3d.cpp b/test/gtest_factor_velocity_local_direction_3d.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0834d95071973788df1aa053baba80adf3e95bf4
--- /dev/null
+++ b/test/gtest_factor_velocity_local_direction_3d.cpp
@@ -0,0 +1,287 @@
+#include "core/utils/utils_gtest.h"
+
+#include "core/ceres_wrapper/solver_ceres.h"
+#include "core/factor/factor_velocity_local_direction_3d.h"
+#include "core/capture/capture_odom_2d.h"
+#include "core/math/rotations.h"
+
+
+using namespace Eigen;
+using namespace wolf;
+
+int N_TESTS = 100;
+
+class FactorVelocityLocalDirection3dTest : public testing::Test
+{
+    protected:
+        ProblemPtr problem;
+        SolverManagerPtr solver;
+        FrameBasePtr frm;
+        StateBlockPtr sb_p;
+        StateBlockPtr sb_o;
+        StateBlockPtr sb_v;
+        CaptureBasePtr cap;
+
+        int n_solved;
+        std::vector<double> convergence, iterations, times, error;
+
+        virtual void SetUp()
+        {
+            // create problem
+            problem = Problem::create("POV", 3);
+
+            // create solver
+            auto params_solver = std::make_shared<ParamsCeres>();
+            params_solver->solver_options.max_num_iterations = 1e3;
+            solver = std::make_shared<SolverCeres>(problem, params_solver);
+
+            // Frame
+            frm = problem->emplaceFrame(0.0, (Vector10d() << 0,0,0,0,0,0,1,1,0,0).finished());
+            sb_p = frm->getP();
+            sb_p->fix();
+            sb_o = frm->getO();
+            sb_v = frm->getV();
+
+            // Capture
+            cap = CaptureBase::emplace<CaptureBase>(frm, "CaptureBase",
+                                                    frm->getTimeStamp(), nullptr);
+        }
+
+        FactorBasePtr emplaceFeatureAndFactor(const Vector3d& v_local,
+                                              const double& angle_stdev)
+        {
+            // emplace feature
+            FeatureBasePtr fea = FeatureBase::emplace<FeatureBase>(cap, "FeatureBase",
+                                                                   v_local,
+                                                                   Matrix1d(angle_stdev * angle_stdev));
+
+            // emplace factor
+            return FactorBase::emplace<FactorVelocityLocalDirection3d>(fea,
+                                                                       fea,
+                                                                       0,
+                                                                       nullptr,
+                                                                       false);
+        }
+
+        bool testLocalVelocity(const Quaterniond& o,
+                               const Vector3d& v_local,
+                               bool estimate_o,
+                               bool estimate_v)
+        {
+            assert(cap->getFeatureList().empty());
+
+            // set state
+            sb_o->setState(o.coeffs());
+            sb_v->setState(o * v_local);
+
+            // fix or unfix & perturb
+            if (not estimate_o)
+                sb_o->fix();
+            else
+            {
+                sb_o->unfix();
+                sb_o->perturb();
+            }
+            if (not estimate_v)
+                sb_v->fix();
+            else
+            {
+                sb_v->unfix();
+                sb_v->setState(Vector3d::Random());
+            }
+
+            // emplace feature & factor
+            auto fac = emplaceFeatureAndFactor(v_local, 0.01);
+
+            // solve
+            std::string report = solver->solve(SolverManager::ReportVerbosity::FULL);
+
+            // local coordinates
+            auto v_est_local_normalized = (Quaterniond(Vector4d(sb_o->getState())).conjugate() * sb_v->getState()).normalized();
+            auto cos_angle_local = v_est_local_normalized.dot(v_local);
+            if (cos_angle_local > 1)
+                cos_angle_local = 1;
+            if (cos_angle_local < -1)
+                cos_angle_local = -1;
+
+            // global coordinates
+            auto v_global = Quaterniond(Vector4d(sb_o->getState())) * v_local;
+            auto v_est_normalized = sb_v->getState().normalized();
+            auto cos_angle_global = v_est_normalized.dot(v_global);
+            if (cos_angle_global > 1)
+                cos_angle_global = 1;
+            if (cos_angle_global < -1)
+                cos_angle_global = -1;
+
+            // Check angle error
+            if (std::abs(acos(cos_angle_local)) > M_PI/180 or
+                std::abs(acos(cos_angle_global)) > M_PI/180)
+            {
+                WOLF_ERROR("\n\n!!!!!!!!!!!! ERROR TOO BIG  iteration: ", iterations.size());
+                WOLF_INFO("cos(angle local) = ", cos_angle_local);
+                WOLF_INFO("angle local = ", acos(cos_angle_local));
+                WOLF_INFO("cos(angle global) = ", cos_angle_global);
+                WOLF_INFO("angle global = ", acos(cos_angle_global));
+
+                problem->print(4,1,1,1);
+                WOLF_INFO(report);
+
+                // Reset
+                fac->getFeature()->remove();
+
+                return false;
+            }
+
+            // Reset
+            fac->getFeature()->remove();
+
+            // Update performaces
+            convergence.push_back(solver->hasConverged() ? 1 : 0);
+            iterations.push_back(solver->iterations());
+            times.push_back(solver->totalTime());
+            error.push_back(acos(cos_angle_local));
+
+            return true;
+        }
+
+        void printAverageResults()
+        {
+            WOLF_INFO("/////////////////// Average results: n_solved = ", iterations.size());
+            double conv_avg = Eigen::Map<Eigen::VectorXd>(convergence.data(), convergence.size()).mean();
+            double iter_avg = Eigen::Map<Eigen::VectorXd>(iterations.data(), iterations.size()).mean();
+            double time_avg = Eigen::Map<Eigen::VectorXd>(times.data(), times.size()).mean();
+            double err_avg  = Eigen::Map<Eigen::VectorXd>(error.data(), error.size()).mean();
+
+            double iter_stdev = (Eigen::Map<Eigen::ArrayXd>(iterations.data(), iterations.size()) - iter_avg).matrix().norm();
+            double time_stdev = (Eigen::Map<Eigen::ArrayXd>(times.data(), times.size()) - time_avg).matrix().norm();
+            double err_stdev  = (Eigen::Map<Eigen::ArrayXd>(error.data(), error.size()) - err_avg).matrix().norm();
+
+            WOLF_INFO("\tconvergence: ", 100 * conv_avg, "%");
+            WOLF_INFO("\titerations:  ", iter_avg, " - stdev: ", iter_stdev);
+            WOLF_INFO("\ttime:        ", 1000 * time_avg, " ms", " - stdev: ", time_stdev);
+            WOLF_INFO("\tangle error: ", err_avg, " - stdev: ", err_stdev);
+        }
+};
+
+// Input odometry data and covariance
+Vector3d v_local(1.0, 0.0, 0.0);
+double angle_stdev = 0.1;
+
+TEST_F(FactorVelocityLocalDirection3dTest, check_tree)
+{
+    ASSERT_TRUE(problem->check(0));
+
+    emplaceFeatureAndFactor(Vector3d(1.0, 0.0, 0.0), 0.1);
+
+    ASSERT_TRUE(problem->check(0));
+}
+
+TEST_F(FactorVelocityLocalDirection3dTest, origin_solveV)
+{
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(),
+                                      Vector3d::Random().normalized(),
+                                      false, true));
+    printAverageResults();
+}
+
+TEST_F(FactorVelocityLocalDirection3dTest, random_solveV)
+{
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()),
+                                      Vector3d::Random().normalized(),
+                                      false, true));
+    printAverageResults();
+}
+
+TEST_F(FactorVelocityLocalDirection3dTest, origin_solveV_degenerated)
+{
+    for (auto i = 0; i < N_TESTS; i++)
+            ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(),
+                                          (Vector3d() << 1, 0, 0).finished() + Vector3d::Random() * 1e-4,
+                                          false, true));
+    for (auto i = 0; i < N_TESTS; i++)
+            ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(),
+                                          (Vector3d() << 0, 1, 0).finished() + Vector3d::Random() * 1e-4,
+                                          false, true));
+    for (auto i = 0; i < N_TESTS; i++)
+            ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(),
+                                          (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4,
+                                          false, true));
+    printAverageResults();
+}
+
+TEST_F(FactorVelocityLocalDirection3dTest, random_solveV_degenerated)
+{
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()),
+                                      (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4,
+                                      false, true));
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()),
+                                      (Vector3d() << 0, 1, 0).finished() + Vector3d::Random() * 1e-4,
+                                      false, true));
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()),
+                                      (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4,
+                                      false, true));
+    printAverageResults();
+}
+
+TEST_F(FactorVelocityLocalDirection3dTest, origin_solveO)
+{
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(),
+                                      Vector3d::Random().normalized(),
+                                      true, false));
+    printAverageResults();
+}
+
+TEST_F(FactorVelocityLocalDirection3dTest, random_solveO)
+{
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()),
+                                      Vector3d::Random().normalized(),
+                                      true, false));
+    printAverageResults();
+}
+
+TEST_F(FactorVelocityLocalDirection3dTest, origin_solveO_degenerated)
+{
+    for (auto i = 0; i < N_TESTS; i++)
+            ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(),
+                                          (Vector3d() << 1, 0, 0).finished() + Vector3d::Random() * 1e-4,
+                                          true, false));
+    for (auto i = 0; i < N_TESTS; i++)
+            ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(),
+                                          (Vector3d() << 0, 1, 0).finished() + Vector3d::Random() * 1e-4,
+                                          true, false));
+    for (auto i = 0; i < N_TESTS; i++)
+            ASSERT_TRUE(testLocalVelocity(Quaterniond::Identity(),
+                                          (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4,
+                                          true, false));
+    printAverageResults();
+}
+
+TEST_F(FactorVelocityLocalDirection3dTest, random_solveO_degenerated)
+{
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()),
+                                      (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4,
+                                      true, false));
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()),
+                                      (Vector3d() << 0, 1, 0).finished() + Vector3d::Random() * 1e-4,
+                                      true, false));
+    for (auto i = 0; i < N_TESTS; i++)
+        ASSERT_TRUE(testLocalVelocity(Quaterniond(Vector4d::Random().normalized()),
+                                      (Vector3d() << 0, 0, 1).finished() + Vector3d::Random() * 1e-4,
+                                      true, false));
+    printAverageResults();
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_graph_search.cpp b/test/gtest_graph_search.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9693846e7994bf071a1aed368c99626c2311d43a
--- /dev/null
+++ b/test/gtest_graph_search.cpp
@@ -0,0 +1,225 @@
+/*
+ * gtest_graph_search.cpp
+ *
+ *  Created on: Jul, 2021
+ *      Author: jvallve
+ */
+
+#include "core/utils/utils_gtest.h"
+
+#include "core/problem/problem.h"
+#include "core/capture/capture_void.h"
+#include "core/factor/factor_relative_pose_2d.h"
+#include "core/utils/graph_search.h"
+
+#include <iostream>
+#include <thread>
+
+using namespace wolf;
+using namespace Eigen;
+
+class GraphSearchTest : public testing::Test
+{
+    public:
+        ProblemPtr problem;
+
+        void SetUp() override
+        {
+            problem = Problem::create("PO", 2);
+        }
+
+        FrameBasePtr emplaceFrame(const TimeStamp& ts)
+        {
+            return problem->emplaceFrame(ts, Vector3d::Zero());
+        }
+
+        FactorBasePtr createFactor(FrameBasePtr frm1, FrameBasePtr frm2)
+        {
+            auto C12 = CaptureBase::emplace<CaptureVoid>(frm2, frm2->getTimeStamp(), nullptr);
+            auto f12 = FeatureBase::emplace<FeatureBase>(C12, "odom", Vector3d::Zero(), Matrix3d::Identity());
+            return FactorBase::emplace<FactorRelativePose2d>(f12, f12, frm1, nullptr, false, TOP_MOTION);
+        }
+};
+
+TEST_F(GraphSearchTest, setup)
+{
+    ASSERT_TRUE(problem->check());
+}
+
+TEST_F(GraphSearchTest, nonsense11)
+{
+    auto F1 = emplaceFrame(1);
+
+    auto fac_list = GraphSearch::shortestPath(F1, F1, 10);
+
+    ASSERT_TRUE(fac_list.empty());
+}
+
+TEST_F(GraphSearchTest, single12)
+{
+    auto F1 = emplaceFrame(1);
+    auto F2 = emplaceFrame(2);
+    auto fac12 = createFactor(F1,F2);
+
+    auto fac_list = GraphSearch::shortestPath(F1, F2, 10);
+
+    ASSERT_EQ(fac_list.size(), 1);
+    ASSERT_EQ(fac_list.front(), fac12);
+}
+
+TEST_F(GraphSearchTest, single21)
+{
+    auto F1 = emplaceFrame(1);
+    auto F2 = emplaceFrame(2);
+    auto fac12 = createFactor(F1,F2);
+
+    auto fac_list = GraphSearch::shortestPath(F2, F1, 10);
+
+    ASSERT_EQ(fac_list.size(), 1);
+    ASSERT_EQ(fac_list.front(), fac12);
+}
+
+TEST_F(GraphSearchTest, double12)
+{
+    auto F1 = emplaceFrame(1);
+    auto F2 = emplaceFrame(2);
+    auto fac12 = createFactor(F1,F2);
+    auto fac12b = createFactor(F1,F2);
+
+    auto fac_list = GraphSearch::shortestPath(F1, F2, 10);
+
+    ASSERT_EQ(fac_list.size(), 1);
+    ASSERT_TRUE(fac_list.front() == fac12 or fac_list.front() == fac12b);
+}
+
+TEST_F(GraphSearchTest, no_solution12)
+{
+    auto F1 = emplaceFrame(1);
+    auto F2 = emplaceFrame(2);
+    auto F3 = emplaceFrame(3);
+    auto fac23 = createFactor(F2,F3);
+
+    auto fac_list = GraphSearch::shortestPath(F1, F2, 10);
+
+    ASSERT_TRUE(fac_list.empty());
+}
+
+TEST_F(GraphSearchTest, no_solution21)
+{
+    auto F1 = emplaceFrame(1);
+    auto F2 = emplaceFrame(2);
+    auto F3 = emplaceFrame(3);
+    auto fac23 = createFactor(F2,F3);
+
+    auto fac_list = GraphSearch::shortestPath(F2, F1, 10);
+
+    ASSERT_TRUE(fac_list.empty());
+}
+
+TEST_F(GraphSearchTest, large)
+{
+    auto F1 = emplaceFrame(1);
+    auto F2 = emplaceFrame(2);
+    auto F3 = emplaceFrame(3);
+    auto F4 = emplaceFrame(4);
+    auto F5 = emplaceFrame(5);
+    auto F6 = emplaceFrame(6);
+    auto F7 = emplaceFrame(7);
+    auto F8 = emplaceFrame(8);
+    auto F9 = emplaceFrame(9);
+
+    auto fac12 = createFactor(F1,F2);
+    auto fac23 = createFactor(F2,F3);
+    auto fac34 = createFactor(F3,F4);
+    auto fac45 = createFactor(F4,F5);
+    auto fac56 = createFactor(F5,F6);
+    auto fac67 = createFactor(F6,F7);
+    auto fac78 = createFactor(F7,F8);
+    auto fac89 = createFactor(F8,F9);
+
+    auto fac_list = GraphSearch::shortestPath(F1, F9, 8);
+
+    ASSERT_EQ(fac_list.size(), 8);
+
+    auto fac_list_2 = GraphSearch::shortestPath(F1, F9, 7);
+
+    ASSERT_EQ(fac_list_2.size(), 0);
+}
+
+TEST_F(GraphSearchTest, large_no_solution)
+{
+    auto F1 = emplaceFrame(1);
+    auto F2 = emplaceFrame(2);
+    auto F3 = emplaceFrame(3);
+    auto F4 = emplaceFrame(4);
+    auto F5 = emplaceFrame(5);
+    auto F6 = emplaceFrame(6);
+    auto F7 = emplaceFrame(7);
+    auto F8 = emplaceFrame(8);
+    auto F9 = emplaceFrame(9);
+
+    auto fac12 = createFactor(F1,F2);
+    auto fac23 = createFactor(F2,F3);
+    auto fac34 = createFactor(F3,F4);
+    auto fac45 = createFactor(F4,F5);
+
+    auto fac67 = createFactor(F6,F7);
+    auto fac78 = createFactor(F7,F8);
+    auto fac89 = createFactor(F8,F9);
+
+    auto fac_list = GraphSearch::shortestPath(F1, F9, 10);
+
+    ASSERT_EQ(fac_list.size(), 0);
+
+    auto fac_list_2 = GraphSearch::shortestPath(F9, F1, 10);
+
+    ASSERT_EQ(fac_list_2.size(), 0);
+}
+
+TEST_F(GraphSearchTest, large_dense)
+{
+    /*  -------         ---------------
+     * |       |       |               |
+     * 1---2---3---4---5---6---7---8---9
+     *         |           |
+     *          -----------
+     */
+
+    auto F1 = emplaceFrame(1);
+    auto F2 = emplaceFrame(2);
+    auto F3 = emplaceFrame(3);
+    auto F4 = emplaceFrame(4);
+    auto F5 = emplaceFrame(5);
+    auto F6 = emplaceFrame(6);
+    auto F7 = emplaceFrame(7);
+    auto F8 = emplaceFrame(8);
+    auto F9 = emplaceFrame(9);
+
+    auto fac12 = createFactor(F1,F2);
+    auto fac13 = createFactor(F1,F3);
+    auto fac23 = createFactor(F2,F3);
+    auto fac34 = createFactor(F3,F4);
+    auto fac36 = createFactor(F3,F6);
+    auto fac45 = createFactor(F4,F5);
+    auto fac56 = createFactor(F5,F6);
+    auto fac59 = createFactor(F5,F9);
+    auto fac67 = createFactor(F6,F7);
+    auto fac78 = createFactor(F7,F8);
+    auto fac89 = createFactor(F8,F9);
+
+    auto fac_list = GraphSearch::shortestPath(F1, F9, 10);
+
+    ASSERT_EQ(fac_list.size(), 4);
+
+    auto fac_list_2 = GraphSearch::shortestPath(F9, F1, 10);
+
+    ASSERT_EQ(fac_list_2.size(), 4);
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
+
+
diff --git a/test/gtest_processor_fix_wing_model.cpp b/test/gtest_processor_fix_wing_model.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e817a9c400c53cdd569c72de90c672ac703fdaf4
--- /dev/null
+++ b/test/gtest_processor_fix_wing_model.cpp
@@ -0,0 +1,136 @@
+
+#include "core/utils/utils_gtest.h"
+#include "core/problem/problem.h"
+#include "core/ceres_wrapper/solver_ceres.h"
+#include "core/processor/processor_fix_wing_model.h"
+#include "core/state_block/state_quaternion.h"
+
+// STL
+#include <iterator>
+#include <iostream>
+
+using namespace wolf;
+using namespace Eigen;
+
+class ProcessorFixWingModelTest : public testing::Test
+{
+    protected:
+        ProblemPtr problem;
+        SolverManagerPtr solver;
+        SensorBasePtr sensor;
+        ProcessorBasePtr processor;
+
+        virtual void SetUp()
+        {
+            // create problem
+            problem = Problem::create("POV", 3);
+
+            // create solver
+            solver = std::make_shared<SolverCeres>(problem);
+
+            // Emplace sensor
+            sensor = SensorBase::emplace<SensorBase>(problem->getHardware(),
+                                                     "SensorBase",
+                                                     std::make_shared<StateBlock>(Vector3d::Zero()),
+                                                     std::make_shared<StateQuaternion>((Vector4d() << 0,0,0,1).finished()),
+                                                     nullptr,
+                                                     2);
+
+            // Emplace processor
+            ParamsProcessorFixWingModelPtr params = std::make_shared<ParamsProcessorFixWingModel>();
+            params->velocity_local = (Vector3d() << 1, 0, 0).finished();
+            params->angle_stdev = 0.1;
+            params->min_vel_norm = 0;
+            processor = ProcessorBase::emplace<ProcessorFixWingModel>(sensor, params);
+        }
+
+        FrameBasePtr emplaceFrame(TimeStamp ts, const Vector10d& x)
+        {
+            // new frame
+            return problem->emplaceFrame(ts, x);
+        }
+};
+
+TEST_F(ProcessorFixWingModelTest, setup)
+{
+    EXPECT_TRUE(problem->check());
+}
+
+TEST_F(ProcessorFixWingModelTest, keyFrameCallback)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, (Vector10d() << 0,0,0,0,0,0,1,1,0,0).finished());
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    // check one capture
+    ASSERT_EQ(frm1->getCapturesOf(sensor).size(), 1);
+    auto cap = frm1->getCaptureOf(sensor);
+    ASSERT_TRUE(cap != nullptr);
+
+    // check one feature
+    ASSERT_EQ(cap->getFeatureList().size(), 1);
+
+    // check one factor
+    auto fac = frm1->getFactorOf(processor, "FactorVelocityLocalDirection3d");
+    ASSERT_TRUE(fac != nullptr);
+    ASSERT_TRUE(fac->getFeature() != nullptr);
+    ASSERT_TRUE(fac->getCapture() == cap);
+}
+
+TEST_F(ProcessorFixWingModelTest, keyFrameCallbackRepeated)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, (Vector10d() << 0,0,0,0,0,0,1,1,0,0).finished());
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    // repeated keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    // check one capture
+    ASSERT_EQ(frm1->getCapturesOf(sensor).size(), 1);
+    auto cap = frm1->getCaptureOf(sensor);
+    ASSERT_TRUE(cap != nullptr);
+
+    // check one feature
+    ASSERT_EQ(cap->getFeatureList().size(), 1);
+
+    // check one factor
+    auto fac = frm1->getFactorOf(processor, "FactorVelocityLocalDirection3d");
+    ASSERT_TRUE(fac != nullptr);
+    ASSERT_TRUE(fac->getFeature() != nullptr);
+    ASSERT_TRUE(fac->getCapture() == cap);
+}
+
+TEST_F(ProcessorFixWingModelTest, solve_origin)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, (Vector10d() << 0,0,0,0,0,0,1,1,0,0).finished());
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    // perturb
+    frm1->getP()->fix();
+    frm1->getO()->fix();
+    frm1->getV()->unfix();
+    frm1->getV()->setState(Vector3d::Random());
+
+    // solve
+    std::string report = solver->solve(SolverManager::ReportVerbosity::FULL);
+
+    WOLF_INFO(report);
+
+    ASSERT_GT(frm1->getV()->getState()(0), 0);
+    ASSERT_NEAR(frm1->getV()->getState()(1), 0, Constants::EPS);
+    ASSERT_NEAR(frm1->getV()->getState()(2), 0, Constants::EPS);
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_processor_loop_closure.cpp b/test/gtest_processor_loop_closure.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aec8092de22eaee6a4577b43d8b7a10f8528582b
--- /dev/null
+++ b/test/gtest_processor_loop_closure.cpp
@@ -0,0 +1,327 @@
+
+#include "core/utils/utils_gtest.h"
+#include "core/problem/problem.h"
+#include "core/capture/capture_base.h"
+#include "core/factor/factor_relative_pose_2d.h"
+
+#include "dummy/processor_loop_closure_dummy.h"
+
+// STL
+#include <iterator>
+#include <iostream>
+
+using namespace wolf;
+using namespace Eigen;
+
+class ProcessorLoopClosureTest : public testing::Test
+{
+    protected:
+        // Wolf problem
+        ProblemPtr problem = Problem::create("PO", 2);
+        SensorBasePtr sensor;
+        ProcessorLoopClosureDummyPtr processor;
+
+        virtual void SetUp()
+        {
+            // Emplace sensor
+            sensor = SensorBase::emplace<SensorBase>(problem->getHardware(),
+                                                     "SensorBase",
+                                                     std::make_shared<StateBlock>(Vector2d::Zero()),
+                                                     std::make_shared<StateBlock>(Vector1d::Zero()),
+                                                     nullptr,
+                                                     2);
+
+            // Emplace processor
+            ParamsProcessorLoopClosurePtr params = std::make_shared<ParamsProcessorLoopClosure>();
+            params->time_tolerance = 0.5;
+            processor = ProcessorBase::emplace<ProcessorLoopClosureDummy>(sensor,
+                                                                          params);
+        }
+
+        FrameBasePtr emplaceFrame(TimeStamp ts, const Vector3d& x)
+        {
+            // new frame
+            return problem->emplaceFrame(ts, x);
+        }
+
+        CaptureBasePtr emplaceCapture(FrameBasePtr frame)
+        {
+            // new capture
+            return CaptureBase::emplace<CaptureBase>(frame,
+                                                     "CaptureBase",
+                                                     frame->getTimeStamp(),
+                                                     sensor);
+        }
+
+        CaptureBasePtr createCapture(TimeStamp ts)
+        {
+            // new capture
+            return std::make_shared<CaptureBase>("CaptureBase",
+                                                 ts,
+                                                 sensor);
+        }
+};
+
+TEST_F(ProcessorLoopClosureTest, installProcessor)
+{
+    EXPECT_EQ(processor->getNStoredFrames(), 0);
+    EXPECT_EQ(processor->getNStoredCaptures(), 0);
+}
+
+TEST_F(ProcessorLoopClosureTest, frame_stored)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, Vector3d::Zero());
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    EXPECT_EQ(processor->getNStoredFrames(), 1);
+    EXPECT_EQ(processor->getNStoredCaptures(), 0);
+}
+
+TEST_F(ProcessorLoopClosureTest, capture_stored)
+{
+    // new capture
+    auto cap1 = createCapture(1);
+
+    // captureCallback
+    processor->captureCallback(cap1);
+
+    EXPECT_EQ(processor->getNStoredFrames(), 0);
+    EXPECT_EQ(processor->getNStoredCaptures(), 1);
+}
+
+TEST_F(ProcessorLoopClosureTest, captureCallbackCase1)
+{
+    // emplace frame and capture
+    auto frm1 = emplaceFrame(1, Vector3d::Zero());
+    auto cap1 = emplaceCapture(frm1);
+
+    // captureCallback
+    processor->captureCallback(cap1);
+
+    EXPECT_EQ(cap1->getFeatureList().size(), 1); // capture processed by the processor
+    EXPECT_EQ(processor->getNStoredFrames(), 0);
+    EXPECT_EQ(processor->getNStoredCaptures(), 0);
+}
+
+TEST_F(ProcessorLoopClosureTest, captureCallbackCase2)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, Vector3d::Zero());
+
+    // new capture
+    auto cap1 = createCapture(1);
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    // captureCallback
+    processor->captureCallback(cap1);
+
+    EXPECT_EQ(cap1->getFrame(), frm1); // capture processed by the processor
+    EXPECT_EQ(cap1->getFeatureList().size(), 1); // capture processed by the processor
+    EXPECT_EQ(processor->getNStoredFrames(), 0);
+    EXPECT_EQ(processor->getNStoredCaptures(), 0);
+}
+
+TEST_F(ProcessorLoopClosureTest, captureCallbackCase3)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, Vector3d::Zero());
+    // new capture
+    auto cap1 = createCapture(2);
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    // captureCallback
+    processor->captureCallback(cap1);
+
+    EXPECT_TRUE(cap1->getFrame() == nullptr);
+    EXPECT_EQ(cap1->getFeatureList().size(), 0);
+    EXPECT_EQ(processor->getNStoredFrames(), 1);
+    EXPECT_EQ(processor->getNStoredCaptures(), 1);
+}
+
+TEST_F(ProcessorLoopClosureTest, keyFrameCallbackCase1)
+{
+    // emplace frame and capture
+    auto frm1 = emplaceFrame(1, Vector3d::Zero());
+    auto cap1 = emplaceCapture(frm1);
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    EXPECT_EQ(cap1->getFeatureList().size(), 1); // capture processed by the processor
+    EXPECT_EQ(processor->getNStoredFrames(), 0);
+    EXPECT_EQ(processor->getNStoredCaptures(), 0);
+}
+
+TEST_F(ProcessorLoopClosureTest, keyFrameCallbackCase2)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, Vector3d::Zero());
+    // new capture
+    auto cap1 = createCapture(1);
+
+    // captureCallback
+    processor->captureCallback(cap1);
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    EXPECT_EQ(cap1->getFrame(), frm1); // capture processed by the processor
+    EXPECT_EQ(cap1->getFeatureList().size(), 1); // capture processed by the processor
+    EXPECT_EQ(processor->getNStoredFrames(), 0);
+    EXPECT_EQ(processor->getNStoredCaptures(), 0);
+}
+
+TEST_F(ProcessorLoopClosureTest, keyFrameCallbackCase3)
+{
+    // new frame
+    auto frm1 = emplaceFrame(2, Vector3d::Zero());
+    // new capture
+    auto cap1 = createCapture(1);
+
+    // captureCallback
+    processor->captureCallback(cap1);
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    EXPECT_TRUE(cap1->getFrame() == nullptr);
+    EXPECT_EQ(cap1->getFeatureList().size(), 0);
+    EXPECT_EQ(processor->getNStoredFrames(), 1);
+    EXPECT_EQ(processor->getNStoredCaptures(), 1);
+}
+
+TEST_F(ProcessorLoopClosureTest, keyFrameCallbackCase4)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, Vector3d::Zero());
+    // new capture
+    auto cap1 = createCapture(2);
+
+    // captureCallback
+    processor->captureCallback(cap1);
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+
+    EXPECT_TRUE(cap1->getFrame() == nullptr);
+    EXPECT_EQ(cap1->getFeatureList().size(), 0);
+    EXPECT_EQ(processor->getNStoredFrames(), 0);
+    EXPECT_EQ(processor->getNStoredCaptures(), 1);
+}
+
+TEST_F(ProcessorLoopClosureTest, captureCallbackMatch)
+{
+    // new frame
+    auto frm1 = emplaceFrame(1, Vector3d::Zero());
+    auto frm2 = emplaceFrame(2, Vector3d::Zero());
+    auto frm3 = emplaceFrame(3, Vector3d::Zero());
+    auto frm4 = emplaceFrame(4, Vector3d::Zero());
+    auto frm5 = emplaceFrame(5, Vector3d::Zero());
+    // new captures
+    auto cap4 = createCapture(4);
+
+    // keyframecallback
+    problem->keyFrameCallback(frm1, nullptr, 0.5);
+    problem->keyFrameCallback(frm2, nullptr, 0.5);
+    problem->keyFrameCallback(frm3, nullptr, 0.5);
+    problem->keyFrameCallback(frm4, nullptr, 0.5);
+    problem->keyFrameCallback(frm5, nullptr, 0.5);
+
+    // captureCallback
+    processor->captureCallback(cap4);
+
+    EXPECT_EQ(frm1->getCaptureList().size(), 0);
+    EXPECT_EQ(frm2->getCaptureList().size(), 0);
+    EXPECT_EQ(frm3->getCaptureList().size(), 0);
+    EXPECT_EQ(frm4->getCaptureList().size(), 1);
+    EXPECT_EQ(frm5->getCaptureList().size(), 0);
+
+    EXPECT_TRUE(cap4->getFrame() == frm4);
+    EXPECT_EQ(cap4->getFeatureList().size(), 1);
+
+    EXPECT_EQ(processor->getNStoredFrames(), 1); // all oldest frames are removed from buffer
+    EXPECT_EQ(processor->getNStoredCaptures(), 0);
+}
+
+TEST_F(ProcessorLoopClosureTest, keyFrameCallbackMatch)
+{
+    // new frame
+    auto frm2 = emplaceFrame(2, Vector3d::Zero());
+    // new captures
+    auto cap1 = createCapture(1);
+    auto cap2 = createCapture(2);
+    auto cap3 = createCapture(3);
+    auto cap4 = createCapture(4);
+    auto cap5 = createCapture(5);
+
+    // captureCallback
+    processor->captureCallback(cap1);
+    processor->captureCallback(cap2);
+    processor->captureCallback(cap3);
+    processor->captureCallback(cap4);
+    processor->captureCallback(cap5);
+
+    // keyframecallback
+    problem->keyFrameCallback(frm2, nullptr, 0.5);
+
+    EXPECT_TRUE(cap1->getFrame() == nullptr);
+    EXPECT_TRUE(cap2->getFrame() == frm2);
+    EXPECT_TRUE(cap3->getFrame() == nullptr);
+    EXPECT_TRUE(cap4->getFrame() == nullptr);
+    EXPECT_TRUE(cap5->getFrame() == nullptr);
+
+    EXPECT_EQ(cap1->getFeatureList().size(), 0);
+    EXPECT_EQ(cap2->getFeatureList().size(), 1);
+    EXPECT_EQ(cap3->getFeatureList().size(), 0);
+    EXPECT_EQ(cap4->getFeatureList().size(), 0);
+    EXPECT_EQ(cap5->getFeatureList().size(), 0);
+
+    EXPECT_EQ(processor->getNStoredFrames(), 0);
+    EXPECT_EQ(processor->getNStoredCaptures(), 4);
+}
+
+TEST_F(ProcessorLoopClosureTest, emplaceFactors)
+{
+    // emplace frame and capture
+    auto cap1 = emplaceCapture(emplaceFrame(1, Vector3d::Zero()));
+    processor->captureCallback(cap1);
+
+    auto cap2 = emplaceCapture(emplaceFrame(2, Vector3d::Ones()));
+    processor->captureCallback(cap2);
+
+    auto cap3 = emplaceCapture(emplaceFrame(3, 2*Vector3d::Ones()));
+    processor->captureCallback(cap3);
+
+    auto cap4 = emplaceCapture(emplaceFrame(4, Vector3d::Zero()));
+    processor->captureCallback(cap4);
+
+    EXPECT_EQ(cap1->getFrame()->getConstrainedByList().size(), 1);
+    EXPECT_EQ(cap2->getFrame()->getConstrainedByList().size(), 0);
+    EXPECT_EQ(cap3->getFrame()->getConstrainedByList().size(), 0);
+    EXPECT_EQ(cap4->getFrame()->getConstrainedByList().size(), 0);
+
+    EXPECT_EQ(cap1->getFeatureList().size(), 1);
+    EXPECT_EQ(cap2->getFeatureList().size(), 1);
+    EXPECT_EQ(cap3->getFeatureList().size(), 1);
+    EXPECT_EQ(cap4->getFeatureList().size(), 1);
+
+    EXPECT_EQ(cap1->getFeatureList().front()->getFactorList().size(), 0);
+    EXPECT_EQ(cap2->getFeatureList().front()->getFactorList().size(), 0);
+    EXPECT_EQ(cap3->getFeatureList().front()->getFactorList().size(), 0);
+    EXPECT_EQ(cap4->getFeatureList().front()->getFactorList().size(), 1);
+
+    EXPECT_EQ(cap1->getFrame()->getConstrainedByList().front(), cap4->getFeatureList().front()->getFactorList().front());
+}
+
+int main(int argc, char **argv)
+{
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/test/gtest_processor_loopclosure.cpp b/test/gtest_processor_loopclosure.cpp
deleted file mode 100644
index 4e50c870348da1877b3a599c48158730e2df47ab..0000000000000000000000000000000000000000
--- a/test/gtest_processor_loopclosure.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-
-#include "core/utils/utils_gtest.h"
-#include "core/problem/problem.h"
-#include "core/capture/capture_void.h"
-
-#include "core/processor/processor_loopclosure.h"
-
-
-// STL
-#include <iterator>
-#include <iostream>
-
-using namespace wolf;
-using namespace Eigen;
-
-
-WOLF_PTR_TYPEDEFS(ProcessorLoopClosureDummy);
-
-// dummy class:
-class ProcessorLoopClosureDummy : public ProcessorLoopClosure
-{
-private:
-    bool* factor_created;
-
-public:
-    ProcessorLoopClosureDummy(ParamsProcessorLoopClosurePtr _params_loop_closure, bool& factor_created):
-        ProcessorLoopClosure("LOOP CLOSURE DUMMY", 0, _params_loop_closure),
-        factor_created(&factor_created){};
-    std::pair<FrameBasePtr,CaptureBasePtr> public_selectPairKC(){ return selectPairKC();};
-
-protected:
-    bool voteComputeFeatures() override                  { return true;};
-    bool voteSearchLoopClosure() override                { return true;};
-    bool detectFeatures(CaptureBasePtr cap) override     { return true;};
-    CaptureBasePtr findLoopCandidate(CaptureBasePtr _capture) override
-    {
-        for (FrameBasePtr kf : *getProblem()->getTrajectory())
-            for (CaptureBasePtr cap : kf->getCaptureList())
-                if (cap != _capture)
-                    return cap;
-        return nullptr;
-    };
-    void emplaceFactors(CaptureBasePtr _capture_1, CaptureBasePtr _capture_2) override
-    {
-        std::cout << "factor created\n";
-        *factor_created = true;
-    };
-};
-
-
-
-TEST(ProcessorLoopClosure, installProcessor)
-{
-    using namespace wolf;
-    using std::shared_ptr;
-    using std::make_shared;
-    using std::static_pointer_cast;
-    using Eigen::Vector2d;
-
-    bool factor_created = false;
-
-
-    double dt = 0.01;
-
-    // Wolf problem
-    ProblemPtr problem = Problem::create("PO", 2);
-
-    // Install tracker (sensor and processor)
-    auto sens_lc = SensorBase::emplace<SensorBase>(problem->getHardware(),
-                                                    "SENSOR BASE",
-                                                    std::make_shared<StateBlock>(Eigen::VectorXd::Zero(2)),
-                                                    std::make_shared<StateBlock>(Eigen::VectorXd::Zero(1)),
-                                                    std::make_shared<StateBlock>(Eigen::VectorXd::Zero(2)), 2);
-    ParamsProcessorLoopClosurePtr params = std::make_shared<ParamsProcessorLoopClosure>();
-    auto proc_lc = ProcessorBase::emplace<ProcessorLoopClosureDummy>(sens_lc, params, factor_created);
-    std::cout << "sensor & processor created and added to wolf problem" << std::endl;
-
-    // initialize
-    TimeStamp   t(0.0);
-    // Vector3d x(0,0,0);
-    VectorComposite x(Vector3d(0,0,0), "PO", {2,1});
-    // Matrix3d    P = Matrix3d::Identity() * 0.1;
-    VectorComposite P(Vector3d(sqrt(0.1),sqrt(0.1),sqrt(0.1)), "PO", {2,1});
-    problem->setPriorFactor(x, P, t, dt/2);             // KF1
-
-
-    // new KF
-    t += dt;
-    auto kf = problem->emplaceFrame(t, x); //KF2
-    // emplace a capture in KF
-    auto capt_lc = CaptureBase::emplace<CaptureVoid>(kf, t, sens_lc);
-    proc_lc->captureCallback(capt_lc);
-
-    // callback KF
-    proc_lc->keyFrameCallback(kf, dt/2);
-
-    ASSERT_TRUE(factor_created);
-}
-
-int main(int argc, char **argv)
-{
-  testing::InitGoogleTest(&argc, argv);
-  return RUN_ALL_TESTS();
-}