diff --git a/CMakeLists.txt b/CMakeLists.txt
index 81393fcdce6e831e6f9d8d0c19588d31c0add694..b113c989e0f693a728f9f12a0d3c1fe1ea94c566 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -86,6 +86,8 @@ set(${PROJECT_NAME}_HEADERS
     include/hpp/manipulation/path-optimization/random-shortcut.hh
     include/hpp/manipulation/path-optimization/spline-gradient-based.hh
     include/hpp/manipulation/path-planner/end-effector-trajectory.hh
+    include/hpp/manipulation/path-planner/in-state-path.hh
+    include/hpp/manipulation/path-planner/states-path-finder.hh
     include/hpp/manipulation/problem-target/state.hh
     include/hpp/manipulation/serialization.hh
     include/hpp/manipulation/steering-method/cross-state-optimization.hh
@@ -120,6 +122,7 @@ set(${PROJECT_NAME}_SOURCES
     src/path-optimization/random-shortcut.cc
     src/path-optimization/enforce-transition-semantic.cc
     src/path-planner/end-effector-trajectory.cc
+    src/path-planner/states-path-finder.cc
     src/problem-target/state.cc
     src/serialization.cc
     src/steering-method/end-effector-trajectory.cc
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 0000000000000000000000000000000000000000..226c753d043d130ecb9c707f19a0f3003bd2ff14
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,25 @@
+- InStatePath::solve
+    BiRRT* does not work properly at all:
+        - discontinuities due to an algorithmic mistake involving qProj_
+        - not using path projectors, while it should
+    DiffusingPlanner does not work properly sometimes
+        - Some collisions were detected on paths solves for romeo-placard
+          both with discrete and continuous (Progressive, 0.2) path validations
+
+
+- InStatePath::mergeLeafRoadmapWith
+    this is inefficient because the roadmap recomputes the connected
+    component at every step. A better merge function should be added to roadmap.cc
+
+
+- path optimizations after solving once :
+    They are done "locally" (for each of the leafs separately) in C++ with a
+    hard-coded optimizer (Graph-RandomShortcut) and then globally with the
+    command ps.addPathOptimizer("Graph-RandomShortcut"). Due to how this works,
+    this is like doing two times the same thing so the first should be removed.
+    However bugs have been observed when the global optimization is used (Core
+    dumped). For unknown reasons, in GraphOptimizer::optimize, the dynamic
+    cast of current->constraints() may fail on *some* 0-length paths. Ignoring
+    empty paths (the "if" I have added) seems to make the problem disappear
+    but the reason why some 0-length paths don't have the right Constraint type
+    is still to be found.
diff --git a/doc/Doxyfile.extra.in b/doc/Doxyfile.extra.in
index 9ca64de3f434a2e125f080fe33b7d42657e5d5f8..ac540fb53ec6b0159185ac5cc65408e4680e731f 100644
--- a/doc/Doxyfile.extra.in
+++ b/doc/Doxyfile.extra.in
@@ -8,3 +8,4 @@ ALIASES   += Link{1}="\ref \1"
 ALIASES   += Link{2}="\ref \1 \"\2\""
 ALIASES   += LHPP{2}="\Link{hpp::\1::\2,\2}"
 ALIASES   += LPinocchio{1}="\LHPP{pinocchio,\1}"
+USE_MATHJAX= YES
diff --git a/include/hpp/manipulation/fwd.hh b/include/hpp/manipulation/fwd.hh
index 6aa19ffe2f2659d04cffced22ae188d8cf58bda8..59829be006f44220b0970a9e5ee6babf4aa3b671 100644
--- a/include/hpp/manipulation/fwd.hh
+++ b/include/hpp/manipulation/fwd.hh
@@ -95,10 +95,24 @@ typedef core::vectorIn_t vectorIn_t;
 typedef core::vectorOut_t vectorOut_t;
 HPP_PREDEF_CLASS(ManipulationPlanner);
 typedef shared_ptr<ManipulationPlanner> ManipulationPlannerPtr_t;
+namespace pathPlanner {
+HPP_PREDEF_CLASS(EndEffectorTrajectory);
+typedef shared_ptr<EndEffectorTrajectory> EndEffectorTrajectoryPtr_t;
+HPP_PREDEF_CLASS(StatesPathFinder);
+typedef shared_ptr<StatesPathFinder> StatesPathFinderPtr_t;
+HPP_PREDEF_CLASS(InStatePath);
+typedef shared_ptr<InStatePath> InStatePathPtr_t;
+HPP_PREDEF_CLASS(StateShooter);
+typedef shared_ptr<StateShooter> StateShooterPtr_t;
+}  // namespace pathPlanner
 HPP_PREDEF_CLASS(GraphPathValidation);
 typedef shared_ptr<GraphPathValidation> GraphPathValidationPtr_t;
 HPP_PREDEF_CLASS(SteeringMethod);
 typedef shared_ptr<SteeringMethod> SteeringMethodPtr_t;
+namespace steeringMethod {
+HPP_PREDEF_CLASS(EndEffectorTrajectory);
+typedef shared_ptr<EndEffectorTrajectory> EndEffectorTrajectoryPtr_t;
+}  // namespace steeringMethod
 typedef core::PathOptimizer PathOptimizer;
 typedef core::PathOptimizerPtr_t PathOptimizerPtr_t;
 HPP_PREDEF_CLASS(GraphOptimizer);
diff --git a/include/hpp/manipulation/path-planner/in-state-path.hh b/include/hpp/manipulation/path-planner/in-state-path.hh
new file mode 100644
index 0000000000000000000000000000000000000000..8e53d2ba87b3f40287bc791b5cd0fcf5ec24fa91
--- /dev/null
+++ b/include/hpp/manipulation/path-planner/in-state-path.hh
@@ -0,0 +1,128 @@
+// Copyright (c) 2021, Joseph Mirabel
+// Authors: Joseph Mirabel (joseph.mirabel@laas.fr),
+//          Florent Lamiraux (florent.lamiraux@laas.fr)
+//          Alexandre Thiault (athiault@laas.fr)
+//
+// This file is part of hpp-manipulation.
+// hpp-manipulation is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation, either version
+// 3 of the License, or (at your option) any later version.
+//
+// hpp-manipulation is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Lesser Public License for more details.  You should have
+// received a copy of the GNU Lesser General Public License along with
+// hpp-manipulation. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef HPP_MANIPULATION_PATH_PLANNER_IN_STATE_PATH_HH
+#define HPP_MANIPULATION_PATH_PLANNER_IN_STATE_PATH_HH
+
+#include <hpp/core/collision-validation.hh>
+#include <hpp/core/config-projector.hh>
+#include <hpp/core/joint-bound-validation.hh>
+#include <hpp/core/path-planner.hh>
+#include <hpp/manipulation/config.hh>
+#include <hpp/manipulation/fwd.hh>
+#include <hpp/manipulation/problem.hh>
+#include <hpp/manipulation/steering-method/graph.hh>
+
+namespace hpp {
+namespace manipulation {
+namespace pathPlanner {
+/// \addtogroup path_planner
+/// \{
+
+///
+/// Path planner designed to build a path between two configurations
+/// on the same leaf of a given state graph edge
+class HPP_MANIPULATION_DLLAPI InStatePath : public core::PathPlanner {
+ public:
+  virtual ~InStatePath() {}
+
+  static InStatePathPtr_t create(const core::ProblemConstPtr_t& problem);
+
+  static InStatePathPtr_t createWithRoadmap(
+      const core::ProblemConstPtr_t& problem,
+      const core::RoadmapPtr_t& roadmap);
+
+  InStatePathPtr_t copy() const;
+
+  void maxIterPlanner(const unsigned long& maxiter);
+  void timeOutPlanner(const double& timeout);
+  void resetRoadmap(const bool& resetroadmap);
+  void plannerType(const std::string& plannertype);
+  void addOptimizerType(const std::string& opttype);
+  void resetOptimizerTypes();
+
+  /// Set the edge along which q_init and q_goal will be linked.
+  /// Use setEdge before setInit and setGoal.
+  void setEdge(const graph::EdgePtr_t& edge);
+  void setInit(const ConfigurationPtr_t& q);
+  void setGoal(const ConfigurationPtr_t& q);
+
+  virtual void oneStep() {}
+  virtual core::PathVectorPtr_t solve();
+
+  const core::RoadmapPtr_t& leafRoadmap() const;
+  void mergeLeafRoadmapWith(const core::RoadmapPtr_t& roadmap) const;
+
+ protected:
+  InStatePath(const core::ProblemConstPtr_t& problem,
+              const core::RoadmapPtr_t& roadmap)
+      : PathPlanner(problem),
+        problem_(HPP_STATIC_PTR_CAST(const manipulation::Problem, problem)),
+        leafRoadmap_(roadmap),
+        constraints_(),
+        weak_() {
+    const core::DevicePtr_t& robot = problem_->robot();
+    leafProblem_ = core::Problem::create(robot);
+    leafProblem_->setParameter("kPRM*/numberOfNodes",
+                               core::Parameter((size_type)2000));
+    leafProblem_->clearConfigValidations();
+    leafProblem_->addConfigValidation(core::CollisionValidation::create(robot));
+    leafProblem_->addConfigValidation(
+        core::JointBoundValidation::create(robot));
+    for (const core::CollisionObjectPtr_t& obs :
+         problem_->collisionObstacles()) {
+      leafProblem_->addObstacle(obs);
+    }
+    leafProblem_->pathProjector(problem->pathProjector());
+  }
+
+  InStatePath(const InStatePath& other)
+      : PathPlanner(other.problem_),
+        problem_(other.problem_),
+        leafProblem_(other.leafProblem_),
+        leafRoadmap_(other.leafRoadmap_),
+        constraints_(other.constraints_),
+        weak_() {}
+
+  void init(InStatePathWkPtr_t weak) { weak_ = weak; }
+
+ private:
+  // a pointer to the problem used to create the InStatePath instance
+  ProblemConstPtr_t problem_;
+  // a new problem created for this InStatePath instance
+  core::ProblemPtr_t leafProblem_;
+  core::RoadmapPtr_t leafRoadmap_;
+  ConstraintSetPtr_t constraints_;
+
+  double timeOutPathPlanning_ = 0;
+  unsigned long maxIterPathPlanning_ = 0;
+  bool resetRoadmap_ = true;
+  std::string plannerType_ = "BiRRT*";
+  std::vector<std::string> optimizerTypes_;
+
+  /// Weak pointer to itself
+  InStatePathWkPtr_t weak_;
+
+};  // class InStatePath
+/// \}
+
+}  // namespace pathPlanner
+}  // namespace manipulation
+}  // namespace hpp
+
+#endif  // HPP_MANIPULATION_STEERING_METHOD_CROSS_STATE_OPTIMIZATION_HH
diff --git a/include/hpp/manipulation/path-planner/states-path-finder.hh b/include/hpp/manipulation/path-planner/states-path-finder.hh
new file mode 100644
index 0000000000000000000000000000000000000000..44c442c51459363d930feac4a78dd4622e57c8ac
--- /dev/null
+++ b/include/hpp/manipulation/path-planner/states-path-finder.hh
@@ -0,0 +1,265 @@
+// Copyright (c) 2017, Joseph Mirabel
+// Authors: Joseph Mirabel (joseph.mirabel@laas.fr),
+//          Florent Lamiraux (florent.lamiraux@laas.fr)
+//          Alexandre Thiault (athiault@laas.fr)
+//          Le Quang Anh (quang-anh.le@laas.fr)
+//
+// This file is part of hpp-manipulation.
+// hpp-manipulation is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation, either version
+// 3 of the License, or (at your option) any later version.
+//
+// hpp-manipulation is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Lesser Public License for more details.  You should have
+// received a copy of the GNU Lesser General Public License along with
+// hpp-manipulation. If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef HPP_MANIPULATION_PATH_PLANNER_STATES_PATH_FINDER_HH
+#define HPP_MANIPULATION_PATH_PLANNER_STATES_PATH_FINDER_HH
+
+#include <hpp/core/config-projector.hh>
+#include <hpp/core/config-validations.hh>
+#include <hpp/core/fwd.hh>
+#include <hpp/core/path-planner.hh>
+#include <hpp/core/path.hh>
+#include <hpp/core/projection-error.hh>
+#include <hpp/core/validation-report.hh>
+#include <hpp/manipulation/config.hh>
+#include <hpp/manipulation/fwd.hh>
+#include <hpp/manipulation/problem.hh>
+
+namespace hpp {
+namespace manipulation {
+namespace pathPlanner {
+
+/// \addtogroup path_planner
+/// \{
+
+/// Optimization-based path planning method.
+///
+/// #### Sketch of the method
+///
+/// Given two configurations \f$ (q_1,q_2) \f$, this class formulates and
+/// solves the problem as follows.
+/// - Compute the corresponding states \f$ (s_1, s_2) \f$.
+/// - For each path \f$ (e_0, ... e_n) \f$ of length not more than
+///   parameter "StatesPathFinder/maxDepth" between
+///   \f$ (s_1, s_2)\f$ in the constraint graph, do:
+///   - define \f$ n-1 \f$ intermediate configuration \f$ p_i \f$,
+///   - initialize the optimization problem, as explained below,
+///   - solve the optimization problem, which gives \f$ p^*_i \f$,
+///   - in case of failure, continue the loop.
+///
+/// #### Problem formulation
+/// Find \f$ (p_i) \f$ such that:
+/// - \f$ p_0 = q_1 \f$,
+/// - \f$ p_{n+1} = q_2 \f$,
+/// - \f$ p_i \f$ is in state between \f$ (e_{i-1}, e_i) \f$, (\ref
+/// StateFunction)
+/// - \f$ (p_i, p_{i+1}) \f$ are reachable with transition \f$ e_i \f$ (\ref
+/// EdgeFunction).
+///
+/// #### Problem resolution
+///
+/// One solver (hpp::constraints::solver::BySubstitution) is created
+/// for each waypoint \f$p_i\f$.
+/// - method buildOptimizationProblem builds a matrix the rows of which
+///   are the parameterizable numerical constraints present in the
+///   graph, and the columns of which are the waypoints. Each value in the
+///   matrix defines the status of each constraint right hand side for
+///   this waypoint, among {absent from the solver,
+///                         equal to value for previous waypoint,
+///                         equal to value for start configuration,
+///                         equal to value for end configuration}.
+/// - method analyseOptimizationProblem loops over the waypoint solvers,
+///   tests what happens when solving each waypoint after initializing
+///   only the right hand sides that are equal to the initial or goal
+///   configuration, and detects if a collision is certain to block any attempts
+///   to solve the problem in the solveOptimizationProblem step.
+/// - method solveOptimizationProblem tries to solve for each waypoint after
+///   initializing the right hand sides with the proper values, backtracking
+///   to the previous waypoint if the solving failed or a collision is
+///   detected a number of times set from the parameter
+///   "StatesPathFinder/nTriesUntilBacktrack". If too much backtracking
+///   occurs, the method can eventually return false.
+/// - eventually method buildPath build paths between waypoints with
+///   the constraints of the transition in which the path lies.
+///
+/// #### Current status
+///
+/// The method has been successfully tested with romeo holding a placard
+/// and the construction set benchmarks. The result is satisfactory
+/// except between pregrasp and grasp waypoints that may be far
+/// away from each other if the transition between those state does
+/// not contain the grasp complement constraint. The same holds
+/// between placement and pre-placement.
+class HPP_MANIPULATION_DLLAPI StatesPathFinder : public core::PathPlanner {
+ public:
+  struct OptimizationData;
+
+  virtual ~StatesPathFinder(){};
+
+  static StatesPathFinderPtr_t create(const core::ProblemConstPtr_t& problem);
+
+  static StatesPathFinderPtr_t createWithRoadmap(
+      const core::ProblemConstPtr_t& problem,
+      const core::RoadmapPtr_t& roadmap);
+
+  StatesPathFinderPtr_t copy() const;
+
+  /// create a vector of configurations between two configurations
+  /// \param q1 initial configuration
+  /// \param q2 pointer to final configuration, NULL if goal is
+  /// defined as a set of constraints
+  /// \return a Configurations_t from q1 to q2 if found. An empty
+  /// vector if a path could not be built.
+  core::Configurations_t computeConfigList(ConfigurationIn_t q1,
+                                           ConfigurationPtr_t q2);
+
+  // access functions for Python interface
+  std::vector<std::string> constraintNamesFromSolverAtWaypoint(std::size_t wp);
+  std::vector<std::string> lastBuiltTransitions() const;
+  std::string displayConfigsSolved() const;
+  bool buildOptimizationProblemFromNames(std::vector<std::string> names);
+
+  // Substeps of method solveOptimizationProblem
+  void initWPRandom(std::size_t wp);
+  void initWPNear(std::size_t wp);
+  void initWP(std::size_t wp, ConfigurationIn_t q);
+  /// Status of the step to solve for a particular waypoint
+  enum SolveStepStatus {
+    /// Valid solution (no collision)
+    VALID_SOLUTION,
+    /// Bad solve status, no solution from the solver
+    NO_SOLUTION,
+    /// Solution has collision in edge leading to the waypoint
+    COLLISION_BEFORE,
+    /// Solution has collision in edge going from the waypoint
+    COLLISION_AFTER,
+  };
+
+  SolveStepStatus solveStep(std::size_t wp);
+
+  /// deletes from memory the latest working states list, which is used to
+  /// resume finding solutions from that list in case of failure at a
+  /// later step.
+  void reset();
+
+  virtual void startSolve();
+  virtual void oneStep();
+  /// when both initial state is one of potential goal states,
+  /// try connecting them directly
+  virtual void tryConnectInitAndGoals();
+
+ protected:
+  StatesPathFinder(const core::ProblemConstPtr_t& problem,
+                   const core::RoadmapPtr_t&);
+  StatesPathFinder(const StatesPathFinder& other);
+
+  void init(StatesPathFinderWkPtr_t weak) { weak_ = weak; }
+
+ private:
+  typedef constraints::solver::BySubstitution Solver_t;
+  struct GraphSearchData;
+
+  /// Gather constraints of all edges
+  void gatherGraphConstraints();
+
+  /// Step 1 of the algorithm
+  /// \return whether the max depth was reached.
+  bool findTransitions(GraphSearchData& data) const;
+
+  /// Step 2 of the algorithm
+  graph::Edges_t getTransitionList(const GraphSearchData& data,
+                                   const std::size_t& i) const;
+
+  /// Step 3 of the algorithm
+
+  // check that the solver either contains exactly same constraint
+  // or a constraint with similar parametrizable form
+  // constraint/both and constraint/complement
+  bool contains(const Solver_t& solver, const ImplicitPtr_t& c) const;
+
+  // check that the solver either contains exactly same constraint
+  // or a stricter version of the constraint
+  // constraint/both stricter than constraint and stricter than
+  // constraint/complement
+  bool containsStricter(const Solver_t& solver, const ImplicitPtr_t& c) const;
+  bool checkConstantRightHandSide(size_type index);
+  bool buildOptimizationProblem(const graph::Edges_t& transitions);
+
+  /// Step 4 of the algorithm
+  void preInitializeRHS(std::size_t j, Configuration_t& q);
+  bool analyseOptimizationProblem(const graph::Edges_t& transitions);
+  bool analyseOptimizationProblem2(const graph::Edges_t& transitions,
+                                   core::ProblemConstPtr_t _problem);
+
+  /// Step 5 of the algorithm
+  void initializeRHS(std::size_t j);
+  bool solveOptimizationProblem();
+
+  /// Step 6 of the algorithm
+  core::Configurations_t getConfigList() const;
+
+  /// Functions used in assert statements
+  bool checkWaypointRightHandSide(std::size_t ictr, std::size_t jslv) const;
+  bool checkSolverRightHandSide(std::size_t ictr, std::size_t jslv) const;
+  bool checkWaypointRightHandSide(std::size_t jslv) const;
+  bool checkSolverRightHandSide(std::size_t jslv) const;
+
+  void displayRhsMatrix();
+  void displayStatusMatrix(const graph::Edges_t& transitions);
+
+  /// A pointer to the manipulation problem
+  ProblemConstPtr_t problem_;
+  /// Path planning problem in each leaf.
+  core::ProblemPtr_t inStateProblem_;
+
+  /// Vector of parameterizable edge numerical constraints
+  NumericalConstraints_t constraints_;
+  /// Map of indices in constraints_
+  std::map<std::string, std::size_t> index_;
+
+  /// associative map that stores pairs of constraints of the form
+  /// (constraint/complement, constraint/hold)
+  std::map<ImplicitPtr_t, ImplicitPtr_t> sameRightHandSide_;
+
+  /// associative map that stores pairs of constraints of either form
+  /// (constraint, constraint/hold)
+  /// or (constraint/complement, constraint/hold)
+  std::map<ImplicitPtr_t, ImplicitPtr_t> stricterConstraints_;
+
+  mutable OptimizationData* optData_;
+  mutable std::shared_ptr<GraphSearchData> graphData_;
+  graph::Edges_t lastBuiltTransitions_;
+
+  /// Constraints defining the goal
+  /// For now:
+  /// - comparison type Equality is initialized to zero
+  /// - if goal constraint is not already present in any graph state,
+  ///   it should not require propagating a complement.
+  ///   invalid eg: specify the full pose of an object placement or
+  ///   object grasp
+  NumericalConstraints_t goalConstraints_;
+  bool goalDefinedByConstraints_;
+  // Variables used across several calls to oneStep
+  ConfigurationPtr_t q1_, q2_;
+  core::Configurations_t configList_;
+  std::size_t idxConfigList_;
+  size_type nTryConfigList_;
+  bool solved_, interrupt_;
+
+  /// Weak pointer to itself
+  StatesPathFinderWkPtr_t weak_;
+
+};  // class StatesPathFinder
+/// \}
+
+}  // namespace pathPlanner
+}  // namespace manipulation
+}  // namespace hpp
+
+#endif  // HPP_MANIPULATION_PATH_PLANNER_STATES_PATH_FINDER_HH
diff --git a/include/hpp/manipulation/steering-method/end-effector-trajectory.hh b/include/hpp/manipulation/steering-method/end-effector-trajectory.hh
index 6454a1b134c08326e33115ceef823fd6ab2f4ae9..5117641a261fb9bb4106c2155c891c14f75228db 100644
--- a/include/hpp/manipulation/steering-method/end-effector-trajectory.hh
+++ b/include/hpp/manipulation/steering-method/end-effector-trajectory.hh
@@ -56,10 +56,35 @@ class HPP_MANIPULATION_DLLAPI EndEffectorTrajectory
     return ptr;
   }
 
-  /// Build a trajectory in SE(3).
-  /// \param points a Nx7 matrix whose rows corresponds to a pose.
-  /// \param weights a 6D vector, weights to be applied when computing
-  ///        the distance between two SE3 points.
+  /** Build a trajectory in SE(3).
+      \param points a Nx7 matrix whose rows corresponds to poses.
+      \param weights a 6D vector, weights to be applied when computing
+             the distance between two SE3 points.
+
+      The trajectory \f$T\f$ is defined as follows. Let \f$N\f$ be the number of
+      lines of matrix \c points, \f$p_i\f$ be the i-th line of \c points and
+      let \f$W\f$ be the
+      diagonal matrix with the coefficients of \c weights:
+      \f[
+      W = \left(\begin{array}{cccccc}
+      w_1 & 0 & 0 & 0 & 0 & 0\\
+      0 & w_2 & 0 & 0 & 0 & 0\\
+      0 & 0 & w_3 & 0 & 0 & 0\\
+      0 & 0 & 0 & w_4 & 0 & 0\\
+      0 & 0 & 0 & 0 & w_5 & 0\\
+      0 & 0 & 0 & 0 & 0 & w_6\\
+      \end{array}\right)
+      \f]
+
+      \f{eqnarray*}{
+        f(t) = \mathbf{p}_i \oplus \frac{t-t_i}{t_{i+1}-t_i}
+     (\mathbf{p}_{i+1}-\mathbf{p}_i) && \mbox{ for } t \in [t_i,t_{i+1}] \f}
+
+      where \f$t_0 = 0\f$ and
+      \f{eqnarray*}{
+       t_{i+1}-t_i = \|W(\mathbf{p}_{i+1}-\mathbf{p}_i)\| && \mbox{for } i
+       \mbox{ such that } 1 \leq i \leq N-1
+       \f} */
   static PathPtr_t makePiecewiseLinearTrajectory(matrixIn_t points,
                                                  vectorIn_t weights);
 
diff --git a/src/path-planner/states-path-finder.cc b/src/path-planner/states-path-finder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b91c9c28e414c31ca09eb3ba995133e27aa5179f
--- /dev/null
+++ b/src/path-planner/states-path-finder.cc
@@ -0,0 +1,1638 @@
+// Copyright (c) 2021, Joseph Mirabel
+// Authors: Joseph Mirabel (joseph.mirabel@laas.fr),
+//          Florent Lamiraux (florent.lamiraux@laas.fr)
+//          Alexandre Thiault (athiault@laas.fr)
+//          Le Quang Anh (quang-anh.le@laas.fr)
+//
+// This file is part of hpp-manipulation.
+// hpp-manipulation is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation, either version
+// 3 of the License, or (at your option) any later version.
+//
+// hpp-manipulation is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Lesser Public License for more details.  You should have
+// received a copy of the GNU Lesser General Public License along with
+// hpp-manipulation. If not, see <http://www.gnu.org/licenses/>.
+
+#include <hpp/constraints/affine-function.hh>
+#include <hpp/constraints/explicit.hh>
+#include <hpp/constraints/locked-joint.hh>
+#include <hpp/constraints/solver/by-substitution.hh>
+#include <hpp/core/collision-validation-report.hh>
+#include <hpp/core/collision-validation.hh>
+#include <hpp/core/configuration-shooter.hh>
+#include <hpp/core/diffusing-planner.hh>
+#include <hpp/core/path-optimization/random-shortcut.hh>
+#include <hpp/core/path-planning-failed.hh>
+#include <hpp/core/path-projector/progressive.hh>
+#include <hpp/core/path-validation.hh>
+#include <hpp/core/path-vector.hh>
+#include <hpp/core/problem-target/goal-configurations.hh>
+#include <hpp/core/problem-target/task-target.hh>
+#include <hpp/manipulation/constraint-set.hh>
+#include <hpp/manipulation/graph/edge.hh>
+#include <hpp/manipulation/graph/state-selector.hh>
+#include <hpp/manipulation/graph/state.hh>
+#include <hpp/manipulation/path-planner/in-state-path.hh>
+#include <hpp/manipulation/path-planner/states-path-finder.hh>
+#include <hpp/manipulation/roadmap.hh>
+#include <hpp/pinocchio/configuration.hh>
+#include <hpp/pinocchio/joint-collection.hh>
+#include <hpp/util/debug.hh>
+#include <hpp/util/exception-factory.hh>
+#include <hpp/util/timer.hh>
+#include <map>
+#include <pinocchio/fwd.hpp>
+#include <pinocchio/multibody/model.hpp>
+#include <queue>
+#include <vector>
+
+namespace hpp {
+namespace manipulation {
+namespace pathPlanner {
+
+using Eigen::ColBlockIndices;
+using Eigen::RowBlockIndices;
+
+using graph::EdgePtr_t;
+using graph::Edges_t;
+using graph::LockedJoints_t;
+using graph::Neighbors_t;
+using graph::NumericalConstraints_t;
+using graph::segments_t;
+using graph::StatePtr_t;
+using graph::States_t;
+
+using core::ProblemTargetPtr_t;
+using core::problemTarget::GoalConfigurations;
+using core::problemTarget::GoalConfigurationsPtr_t;
+using core::problemTarget::TaskTarget;
+using core::problemTarget::TaskTargetPtr_t;
+
+#ifdef HPP_DEBUG
+static void displayRoadmap(const core::RoadmapPtr_t& roadmap) {
+  unsigned i = 0;
+  for (auto cc : roadmap->connectedComponents()) {
+    hppDout(info, " CC " << i++);
+    for (auto n : cc->nodes()) {
+      hppDout(info, pinocchio::displayConfig(*(n->configuration())));
+    }
+  }
+}
+#endif
+
+StatesPathFinder::StatesPathFinder(const core::ProblemConstPtr_t& problem,
+                                   const core::RoadmapPtr_t& roadmap)
+    : PathPlanner(problem, roadmap),
+      problem_(HPP_STATIC_PTR_CAST(const manipulation::Problem, problem)),
+      constraints_(),
+      index_(),
+      sameRightHandSide_(),
+      stricterConstraints_(),
+      optData_(0x0),
+      graphData_(0x0),
+      lastBuiltTransitions_(),
+      goalConstraints_(),
+      goalDefinedByConstraints_(false),
+      q1_(0x0),
+      q2_(0x0),
+      configList_(),
+      idxConfigList_(0),
+      nTryConfigList_(0),
+      solved_(false),
+      interrupt_(false),
+      weak_() {
+  gatherGraphConstraints();
+  inStateProblem_ = core::Problem::create(problem_->robot());
+}
+
+StatesPathFinder::StatesPathFinder(const StatesPathFinder& other)
+    : PathPlanner(other.problem_),
+      problem_(other.problem_),
+      constraints_(),
+      index_(other.index_),
+      sameRightHandSide_(other.sameRightHandSide_),
+      weak_() {}
+
+StatesPathFinderPtr_t StatesPathFinder::create(
+    const core::ProblemConstPtr_t& problem) {
+  StatesPathFinder* ptr;
+  RoadmapPtr_t r = Roadmap::create(problem->distance(), problem->robot());
+  try {
+    ProblemConstPtr_t p(HPP_DYNAMIC_PTR_CAST(const Problem, problem));
+    ptr = new StatesPathFinder(p, r);
+  } catch (std::exception&) {
+    throw std::invalid_argument(
+        "The problem must be of type hpp::manipulation::Problem.");
+  }
+  StatesPathFinderPtr_t shPtr(ptr);
+  ptr->init(shPtr);
+  return shPtr;
+}
+
+StatesPathFinderPtr_t StatesPathFinder::createWithRoadmap(
+    const core::ProblemConstPtr_t& problem, const core::RoadmapPtr_t& roadmap) {
+  StatesPathFinder* ptr;
+  ProblemConstPtr_t p = HPP_DYNAMIC_PTR_CAST(const Problem, problem);
+  RoadmapPtr_t r = HPP_DYNAMIC_PTR_CAST(Roadmap, roadmap);
+  if (!r)
+    throw std::invalid_argument(
+        "The roadmap must be of type hpp::manipulation::Roadmap.");
+  if (!p)
+    throw std::invalid_argument(
+        "The problem must be of type hpp::manipulation::Problem.");
+
+  ptr = new StatesPathFinder(p, r);
+  StatesPathFinderPtr_t shPtr(ptr);
+  ptr->init(shPtr);
+  return shPtr;
+}
+
+StatesPathFinderPtr_t StatesPathFinder::copy() const {
+  StatesPathFinder* ptr = new StatesPathFinder(*this);
+  StatesPathFinderPtr_t shPtr(ptr);
+  ptr->init(shPtr);
+  return shPtr;
+}
+
+struct StatesPathFinder::GraphSearchData {
+  StatePtr_t s1;
+  // list of potential goal states
+  // can be multiple if goal is defined as a set of constraints
+  graph::States_t s2;
+
+  // index of the transition list
+  size_t idxSol;
+
+  // Datas for findNextTransitions
+  struct state_with_depth {
+    StatePtr_t s;
+    EdgePtr_t e;
+    std::size_t l;  // depth to root
+    std::size_t i;  // index in parent state_with_depths
+    // constructor used for root node
+    inline state_with_depth() : s(), e(), l(0), i(0) {}
+    // constructor used for non-root node
+    inline state_with_depth(EdgePtr_t _e, std::size_t _l, std::size_t _i)
+        : s(_e->stateFrom()), e(_e), l(_l), i(_i) {}
+  };
+  typedef std::vector<state_with_depth> state_with_depths;
+  typedef std::map<StatePtr_t, state_with_depths> StateMap_t;
+  /// std::size_t is the index in state_with_depths at StateMap_t::iterator
+  struct state_with_depth_ptr_t {
+    StateMap_t::iterator state;
+    std::size_t parentIdx;
+    state_with_depth_ptr_t(const StateMap_t::iterator& it, std::size_t idx)
+        : state(it), parentIdx(idx) {}
+  };
+  typedef std::deque<state_with_depth_ptr_t> Deque_t;
+  // vector of pointers to state with depth
+  typedef std::vector<state_with_depth_ptr_t> state_with_depths_t;
+  // transition lists exceeding this depth in the constraint graph will not be
+  // considered
+  std::size_t maxDepth;
+  // map each state X to a list of preceding states in transition lists that
+  // visit X state_with_depth struct gives info to trace the entire transition
+  // lists
+  StateMap_t parent1;
+  // store a vector of pointers to the end state of each transition list
+  state_with_depths_t solutions;
+  // the frontier of the graph search
+  // consists states that have not been expanded on
+  Deque_t queue1;
+
+  // track index of first transition list that has not been checked out
+  // only needed when goal is defined as set of constraints
+  size_t queueIt;
+
+  const state_with_depth& getParent(const state_with_depth_ptr_t& _p) const {
+    const state_with_depths& parents = _p.state->second;
+    return parents[_p.parentIdx];
+  }
+
+  // add initial state (root node) to the map parent1
+  state_with_depth_ptr_t addInitState() {
+    StateMap_t::iterator next =
+        parent1.insert(StateMap_t::value_type(s1, state_with_depths(1))).first;
+    return state_with_depth_ptr_t(next, 0);
+  }
+
+  // store a transition to the map parent1
+  state_with_depth_ptr_t addParent(const state_with_depth_ptr_t& _p,
+                                   const EdgePtr_t& transition) {
+    const state_with_depths& parents = _p.state->second;
+    const state_with_depth& from = parents[_p.parentIdx];
+
+    // Insert state to if necessary
+    StateMap_t::iterator next =
+        parent1
+            .insert(StateMap_t::value_type(transition->stateTo(),
+                                           state_with_depths()))
+            .first;
+
+    next->second.push_back(
+        state_with_depth(transition, from.l + 1, _p.parentIdx));
+
+    return state_with_depth_ptr_t(next, next->second.size() - 1);
+  }
+};
+
+static bool containsLevelSet(const graph::EdgePtr_t& e) {
+  graph::WaypointEdgePtr_t we = HPP_DYNAMIC_PTR_CAST(graph::WaypointEdge, e);
+  if (!we) return false;
+  for (std::size_t i = 0; i <= we->nbWaypoints(); i++) {
+    graph::LevelSetEdgePtr_t lse =
+        HPP_DYNAMIC_PTR_CAST(graph::LevelSetEdge, we->waypoint(i));
+    if (lse) return true;
+  }
+  return false;
+}
+
+static bool isLoopTransition(const graph::EdgePtr_t& transition) {
+  return transition->stateTo() == transition->stateFrom();
+}
+
+void StatesPathFinder::gatherGraphConstraints() {
+  typedef graph::Edge Edge;
+  typedef graph::EdgePtr_t EdgePtr_t;
+  typedef graph::GraphPtr_t GraphPtr_t;
+  typedef constraints::solver::BySubstitution Solver_t;
+
+  GraphPtr_t cg(problem_->constraintGraph());
+  const ConstraintsAndComplements_t& cac(cg->constraintsAndComplements());
+  for (std::size_t i = 0; i < cg->nbComponents(); ++i) {
+    EdgePtr_t edge(HPP_DYNAMIC_PTR_CAST(Edge, cg->get(i).lock()));
+
+    // only gather the edge constraints
+    if (!edge) continue;
+
+    // Don't even consider level set edges
+    if (containsLevelSet(edge)) continue;
+
+    const Solver_t& solver(edge->pathConstraint()->configProjector()->solver());
+    const NumericalConstraints_t& constraints(solver.numericalConstraints());
+    for (NumericalConstraints_t::const_iterator it(constraints.begin());
+         it != constraints.end(); ++it) {
+      // only look at parameterized constraint
+      if ((*it)->parameterSize() == 0) continue;
+
+      const std::string& name((*it)->function().name());
+      // if constraint is in map, no need to add it
+      if (index_.find(name) != index_.end()) continue;
+
+      index_[name] = constraints_.size();
+      // Check whether constraint is equivalent to a previous one
+      for (NumericalConstraints_t::const_iterator it1(constraints_.begin());
+           it1 != constraints_.end(); ++it1) {
+        for (ConstraintsAndComplements_t::const_iterator it2(cac.begin());
+             it2 != cac.end(); ++it2) {
+          if (((**it1 == *(it2->complement)) && (**it == *(it2->both))) ||
+              ((**it1 == *(it2->both)) && (**it == *(it2->complement)))) {
+            assert(sameRightHandSide_.count(*it1) == 0);
+            assert(sameRightHandSide_.count(*it) == 0);
+            sameRightHandSide_[*it1] = *it;
+            sameRightHandSide_[*it] = *it1;
+          }
+        }
+      }
+      constraints_.push_back(*it);
+      hppDout(info, "Adding constraint \"" << name << "\"");
+      hppDout(info, "Edge \"" << edge->name() << "\"");
+      hppDout(info, "parameter size: " << (*it)->parameterSize());
+    }
+  }
+  // constraint/both is the intersection of both constraint and
+  // constraint/complement
+  for (ConstraintsAndComplements_t::const_iterator it(cac.begin());
+       it != cac.end(); ++it) {
+    stricterConstraints_[it->constraint] = it->both;
+    stricterConstraints_[it->complement] = it->both;
+  }
+}
+
+bool StatesPathFinder::findTransitions(GraphSearchData& d) const {
+  // all potential solutions should be attempted before finding more
+  if (d.idxSol < d.solutions.size()) return false;
+  bool done = false;
+  while (!d.queue1.empty() && !done) {
+    GraphSearchData::state_with_depth_ptr_t _state = d.queue1.front();
+
+    const GraphSearchData::state_with_depth& parent = d.getParent(_state);
+    if (parent.l >= d.maxDepth) return true;
+    d.queue1.pop_front();
+
+    const Neighbors_t& neighbors = _state.state->first->neighbors();
+    for (Neighbors_t::const_iterator _n = neighbors.begin();
+         _n != neighbors.end(); ++_n) {
+      EdgePtr_t transition = _n->second;
+
+      // Don't even consider level set edges
+      if (containsLevelSet(transition)) continue;
+
+      // Avoid identical consecutive transition
+      if (transition == parent.e) continue;
+
+      // Avoid loop transitions
+      if (isLoopTransition(transition)) continue;
+
+      // Insert the end state of the new path to parent
+      GraphSearchData::state_with_depth_ptr_t endState =
+          d.addParent(_state, transition);
+      d.queue1.push_back(endState);
+
+      // done if last state is one of potential goal states
+      if (std::find(d.s2.begin(), d.s2.end(), transition->stateTo()) !=
+          d.s2.end()) {
+        done = true;
+        d.solutions.push_back(endState);
+      }
+    }
+  }
+  // return true if search is exhausted and goal state not found
+  if (!done) return true;
+  return false;
+}
+
+Edges_t StatesPathFinder::getTransitionList(const GraphSearchData& d,
+                                            const std::size_t& idxSol) const {
+  Edges_t transitions;
+  if (idxSol >= d.solutions.size()) return transitions;
+
+  const GraphSearchData::state_with_depth_ptr_t endState = d.solutions[idxSol];
+  const GraphSearchData::state_with_depth* current = &d.getParent(endState);
+  transitions.reserve(current->l);
+  graph::WaypointEdgePtr_t we;
+  while (current->e) {
+    assert(current->l > 0);
+    we = HPP_DYNAMIC_PTR_CAST(graph::WaypointEdge, current->e);
+    if (we) {
+      for (int i = (int)we->nbWaypoints(); i >= 0; --i)
+        transitions.push_back(we->waypoint(i));
+    } else {
+      transitions.push_back(current->e);
+    }
+    current = &d.parent1.at(current->s)[current->i];
+  }
+  std::reverse(transitions.begin(), transitions.end());
+  return transitions;
+}
+
+struct StatesPathFinder::OptimizationData {
+  typedef constraints::solver::HierarchicalIterative::Saturation_t Saturation_t;
+  enum RightHandSideStatus_t {
+    // Constraint is not in solver for this waypoint
+    ABSENT,
+    // right hand side of constraint for this waypoint is equal to
+    // right hand side for previous waypoint
+    EQUAL_TO_PREVIOUS,
+    // right hand side of constraint for this waypoint is equal to
+    // right hand side for initial configuration
+    EQUAL_TO_INIT,
+    // right hand side of constraint for this waypoint is equal to
+    // right hand side for goal configuration
+    EQUAL_TO_GOAL
+  };  // enum RightHandSideStatus_t
+  const std::size_t N, nq, nv;
+  std::vector<Solver_t> solvers;
+  std::vector<bool> isTargetWaypoint;
+  // Waypoints lying in each intermediate state
+  matrix_t waypoint;
+  const Configuration_t q1;
+  Configuration_t q2;
+  core::DevicePtr_t robot;
+  // Matrix specifying for each constraint and each waypoint how
+  // the right hand side is initialized in the solver.
+  Eigen::Matrix<LiegroupElement, Eigen::Dynamic, Eigen::Dynamic> M_rhs;
+  Eigen::Matrix<RightHandSideStatus_t, Eigen::Dynamic, Eigen::Dynamic> M_status;
+  // Number of trials to generate each waypoint configuration
+  // _solveGoalConfig: whether we need to solve for goal configuration
+  OptimizationData(const core::ProblemConstPtr_t _problem,
+                   const Configuration_t& _q1, const ConfigurationPtr_t& _q2,
+                   const Edges_t& transitions, const bool _solveGoalConfig)
+      : N(_solveGoalConfig ? transitions.size() : transitions.size() - 1),
+        nq(_problem->robot()->configSize()),
+        nv(_problem->robot()->numberDof()),
+        solvers(N, _problem->robot()->configSpace()),
+        waypoint(nq, N),
+        q1(_q1),
+        robot(_problem->robot()),
+        M_rhs(),
+        M_status() {
+    if (!_solveGoalConfig) {
+      assert(_q2);
+      q2 = *_q2;
+    }
+    waypoint.setZero();
+    for (auto solver : solvers) {
+      // Set maximal number of iterations for each solver
+      solver.maxIterations(
+          _problem->getParameter("StatesPathFinder/maxIteration").intValue());
+      // Set error threshold for each solver
+      solver.errorThreshold(
+          _problem->getParameter("StatesPathFinder/errorThreshold")
+              .floatValue());
+    }
+    assert(transitions.size() > 0);
+    isTargetWaypoint.assign(transitions.size(), false);
+    for (std::size_t i = 0; i < transitions.size(); i++)
+      isTargetWaypoint[i] = transitions[i]->stateTo()->isWaypoint();
+  }
+};
+
+bool StatesPathFinder::checkConstantRightHandSide(size_type index) {
+  // a goal configuration is required to check if constraint is satisfied
+  // between initial and final configurations
+  assert(!goalDefinedByConstraints_);
+  OptimizationData& d = *optData_;
+  const ImplicitPtr_t c(constraints_[index]);
+  LiegroupElement rhsInit(c->function().outputSpace());
+  c->rightHandSideFromConfig(d.q1, rhsInit);
+  LiegroupElement rhsGoal(c->function().outputSpace());
+  c->rightHandSideFromConfig(d.q2, rhsGoal);
+  // Check that right hand sides are close to each other
+  value_type eps(problem_->constraintGraph()->errorThreshold());
+  value_type eps2(eps * eps);
+  if ((rhsGoal - rhsInit).squaredNorm() > eps2) {
+    return false;
+  }
+  // Matrix of solver right hand sides
+  for (size_type j = 0; j < d.M_rhs.cols(); ++j) {
+    d.M_rhs(index, j) = rhsInit;
+  }
+  return true;
+}
+
+bool StatesPathFinder::checkWaypointRightHandSide(std::size_t ictr,
+                                                  std::size_t jslv) const {
+  const OptimizationData& d = *optData_;
+  ImplicitPtr_t c = constraints_[ictr]->copy();
+  LiegroupElement rhsNow(c->function().outputSpace());
+  assert(rhsNow.size() == c->rightHandSideSize());
+  c->rightHandSideFromConfig(d.waypoint.col(jslv), rhsNow);
+  c = constraints_[ictr]->copy();
+  LiegroupElement rhsOther(c->function().outputSpace());
+  switch (d.M_status(ictr, jslv)) {
+    case OptimizationData::EQUAL_TO_INIT:
+      c->rightHandSideFromConfig(d.q1, rhsOther);
+      break;
+    case OptimizationData::EQUAL_TO_GOAL:
+      assert(!goalDefinedByConstraints_);
+      c->rightHandSideFromConfig(d.q2, rhsOther);
+      break;
+    case OptimizationData::EQUAL_TO_PREVIOUS:
+      c->rightHandSideFromConfig(d.waypoint.col(jslv - 1), rhsOther);
+      break;
+    case OptimizationData::ABSENT:
+    default:
+      return true;
+  }
+  hpp::pinocchio::vector_t diff = rhsOther - rhsNow;
+  hpp::pinocchio::vector_t diffmask =
+      hpp::pinocchio::vector_t::Zero(diff.size());
+  for (auto k : c->activeRows())  // filter with constraint mask
+    for (size_type kk = k.first; kk < k.first + k.second; kk++)
+      diffmask[kk] = diff[kk];
+  value_type eps(problem_->constraintGraph()->errorThreshold());
+  value_type eps2(eps * eps);
+  return diffmask.squaredNorm() < eps2;
+}
+
+bool StatesPathFinder::checkWaypointRightHandSide(std::size_t jslv) const {
+  for (std::size_t ictr = 0; ictr < constraints_.size(); ictr++)
+    if (!checkWaypointRightHandSide(ictr, jslv)) return false;
+  return true;
+}
+
+void StatesPathFinder::displayRhsMatrix() {
+  OptimizationData& d = *optData_;
+  Eigen::Matrix<LiegroupElement, Eigen::Dynamic, Eigen::Dynamic>& m = d.M_rhs;
+
+  for (std::size_t i = 0; i < constraints_.size(); i++) {
+    const ImplicitPtr_t& constraint = constraints_[i];
+    for (std::size_t j = 0; j < d.solvers.size(); j++) {
+      const vectorIn_t& config = d.waypoint.col(j);
+      LiegroupElement le(constraint->function().outputSpace());
+      constraint->rightHandSideFromConfig(d.waypoint.col(j), le);
+      m(i, j) = le;
+    }
+  }
+
+  std::ostringstream oss;
+  oss.precision(2);
+  oss << "\\documentclass[12pt,landscape]{article}" << std::endl;
+  oss << "\\usepackage[a3paper]{geometry}" << std::endl;
+  oss << "\\begin {document}" << std::endl;
+
+  for (size_type ii = 0; ii < (m.cols() + 7) / 8; ii++) {
+    size_type j0 = ii * 8;
+    size_type j1 = std::min(ii * 8 + 8, m.cols());
+    size_type dj = j1 - j0;
+    oss << "\\begin {tabular}{";
+    for (size_type j = 0; j < dj + 2; ++j) oss << "c";
+    oss << "}" << std::endl;
+    oss << "Constraint & mask";
+    for (size_type j = j0; j < j1; ++j) oss << " & WP" << j;
+    oss << "\\\\" << std::endl;
+    for (size_type i = 0; i < m.rows(); ++i) {
+      std::vector<int> mask(constraints_[i]->parameterSize());
+      for (auto k : constraints_[i]->activeRows())
+        for (size_type kk = k.first; kk < k.first + k.second; kk++)
+          mask[kk] = 1;
+      std::ostringstream oss1;
+      oss1.precision(2);
+      std::ostringstream oss2;
+      oss2.precision(2);
+      oss1 << constraints_[i]->function().name() << " & ";
+
+      oss1 << "$\\left(\\begin{array}{c} ";
+      for (std::size_t k = 0; k < mask.size(); ++k) {
+        oss1 << mask[k] << "\\\\ ";
+      }
+      oss1 << "\\end{array}\\right)$" << std::endl;
+      oss1 << " & " << std::endl;
+
+      for (size_type j = j0; j < j1; ++j) {
+        if (d.M_status(i, j) != OptimizationData::ABSENT ||
+            (j < m.cols() - 1 &&
+             d.M_status(i, j + 1) == OptimizationData::EQUAL_TO_PREVIOUS)) {
+          oss2 << "$\\left(\\begin{array}{c} ";
+          for (size_type k = 0; k < m(i, j).size(); ++k) {
+            oss2 << ((abs(m(i, j).vector()[k]) < 1e-6) ? 0
+                                                       : m(i, j).vector()[k])
+                 << "\\\\ ";
+          }
+          oss2 << "\\end{array}\\right)$" << std::endl;
+        }
+        if (j < j1 - 1) {
+          oss2 << " & " << std::endl;
+        }
+      }
+      std::string str2 = oss2.str();
+      if (str2.size() > 50) {  // don't display constraints used nowhere
+        oss << oss1.str() << str2 << "\\\\" << std::endl;
+      }
+    }
+    oss << "\\end{tabular}" << std::endl << std::endl;
+  }
+  oss << "\\end{document}" << std::endl;
+
+  std::string s = oss.str();
+  std::string s2 = "";
+  for (std::size_t i = 0; i < s.size(); i++) {
+    if (s[i] == '_')
+      s2 += "\\_";
+    else
+      s2.push_back(s[i]);
+  }
+  hppDout(info, s2);
+}
+
+void StatesPathFinder::displayStatusMatrix(const graph::Edges_t& transitions) {
+  const Eigen::Matrix<OptimizationData::RightHandSideStatus_t, Eigen::Dynamic,
+                      Eigen::Dynamic>& m = optData_->M_status;
+  std::ostringstream oss;
+  oss.precision(5);
+  oss << "\\documentclass[12pt,landscape]{article}" << std::endl;
+  oss << "\\usepackage[a3paper]{geometry}" << std::endl;
+  oss << "\\begin {document}" << std::endl;
+  oss << "\\paragraph{Edges}" << std::endl;
+  oss << "\\begin{enumerate}" << std::endl;
+  for (auto edge : transitions) {
+    oss << "\\item \\texttt{" << edge->name() << "}" << std::endl;
+  }
+  oss << "\\end{enumerate}" << std::endl;
+  oss << "\\begin {tabular}{l|";
+  for (size_type j = 0; j < m.cols(); ++j)
+    if (transitions[j]->stateTo()->isWaypoint())
+      oss << "c";
+    else
+      oss << "|c|";
+  oss << "|}" << std::endl;
+  oss << "Constraint";
+  for (size_type j = 0; j < m.cols(); ++j) oss << " & " << j + 1;
+  oss << "\\\\" << std::endl;
+  for (size_type i = 0; i < m.rows(); ++i) {
+    oss << "\\texttt{" << constraints_[i]->function().name() << "} & "
+        << std::endl;
+    for (size_type j = 0; j < m.cols(); ++j) {
+      oss << m(i, j);
+      if (j < m.cols() - 1) oss << " & ";
+    }
+    oss << "\\\\" << std::endl;
+  }
+  oss << "\\end{tabular}" << std::endl;
+  oss << "\\end{document}" << std::endl;
+
+  std::string s = oss.str();
+  std::string s2 = "";
+  for (std::size_t i = 0; i < s.size(); i++) {
+    if (s[i] == '_')
+      s2 += "\\_";
+    else
+      s2.push_back(s[i]);
+  }
+  hppDout(info, s2);
+}
+
+bool StatesPathFinder::contains(const Solver_t& solver,
+                                const ImplicitPtr_t& c) const {
+  if (solver.contains(c)) return true;
+  std::map<ImplicitPtr_t, ImplicitPtr_t>::const_iterator it(
+      sameRightHandSide_.find(c));
+  if (it != sameRightHandSide_.end() && solver.contains(it->second))
+    return true;
+  return false;
+}
+
+bool StatesPathFinder::containsStricter(const Solver_t& solver,
+                                        const ImplicitPtr_t& c) const {
+  if (solver.contains(c)) return true;
+  std::map<ImplicitPtr_t, ImplicitPtr_t>::const_iterator it(
+      stricterConstraints_.find(c));
+  if (it != stricterConstraints_.end() && solver.contains(it->second))
+    return true;
+  return false;
+}
+
+bool StatesPathFinder::buildOptimizationProblem(
+    const graph::Edges_t& transitions) {
+  OptimizationData& d = *optData_;
+  // if no waypoint, check init and goal has same RHS
+  if (d.N == 0) {
+    assert(transitions.size() == 1);
+    assert(!goalDefinedByConstraints_);
+    size_type index = 0;
+    for (NumericalConstraints_t::const_iterator it(constraints_.begin());
+         it != constraints_.end(); ++it, ++index) {
+      const ImplicitPtr_t& c(*it);
+      // Get transition solver
+      const Solver_t& trSolver(
+          transitions[0]->pathConstraint()->configProjector()->solver());
+      if (!contains(trSolver, c)) continue;
+      if (!checkConstantRightHandSide(index)) return false;
+    }
+    return true;
+  }
+  d.M_status.resize(constraints_.size(), d.N);
+  d.M_status.fill(OptimizationData::ABSENT);
+  d.M_rhs.resize(constraints_.size(), d.N);
+  d.M_rhs.fill(LiegroupElement());
+  size_type index = 0;
+  // Loop over constraints
+  for (NumericalConstraints_t::const_iterator it(constraints_.begin());
+       it != constraints_.end(); ++it, ++index) {
+    const ImplicitPtr_t& c(*it);
+    // Loop forward over waypoints to determine right hand sides equal
+    // to initial configuration or previous configuration
+    for (std::size_t j = 0; j < d.N; ++j) {
+      // Get transition solver
+      const Solver_t& trSolver(
+          transitions[j]->pathConstraint()->configProjector()->solver());
+      if (!contains(trSolver, c)) continue;
+
+      if ((j == 0) ||
+          d.M_status(index, j - 1) == OptimizationData::EQUAL_TO_INIT) {
+        d.M_status(index, j) = OptimizationData::EQUAL_TO_INIT;
+      } else {
+        d.M_status(index, j) = OptimizationData::EQUAL_TO_PREVIOUS;
+      }
+    }
+    // If the goal configuration is not given
+    // no need to determine if RHS equal to goal configuration
+    if (goalDefinedByConstraints_) {
+      continue;
+    }
+    // Loop backward over waypoints to determine right hand sides equal
+    // to final configuration
+    for (size_type j = d.N - 1; j > 0; --j) {
+      // Get transition solver
+      const Solver_t& trSolver(transitions[(std::size_t)j + 1]
+                                   ->pathConstraint()
+                                   ->configProjector()
+                                   ->solver());
+      if (!contains(trSolver, c)) break;
+
+      if ((j == (size_type)d.N - 1) ||
+          d.M_status(index, j + 1) == OptimizationData::EQUAL_TO_GOAL) {
+        // if constraint right hand side is already equal to
+        // initial config, check that right hand side is equal
+        // for init and goal configs.
+        if (d.M_status(index, j) == OptimizationData::EQUAL_TO_INIT) {
+          if (checkConstantRightHandSide(index)) {
+            // stop for this constraint
+            break;
+          } else {
+            // Right hand side of constraint should be equal along the
+            // whole path but is different at init and at goal configs.
+            return false;
+          }
+        } else {
+          d.M_status(index, j) = OptimizationData::EQUAL_TO_GOAL;
+        }
+      }
+    }
+  }  // for (NumericalConstraints_t::const_iterator it
+#ifdef HPP_DEBUG
+  displayStatusMatrix(transitions);
+#endif
+  // Fill solvers with target constraints of transition
+  for (std::size_t j = 0; j < d.N; ++j) {
+    d.solvers[j] =
+        transitions[j]->targetConstraint()->configProjector()->solver();
+    if (goalDefinedByConstraints_) {
+      continue;
+    }
+    // when goal configuration is given, and if something
+    // (eg relative pose of two objects grasping) is fixed until goal,
+    // we need to propagate the constraint to an earlier solver,
+    // otherwise the chance it solves for the correct config is very low
+    if (j > 0 && j < d.N - 1) {
+      const Solver_t& otherSolver =
+          transitions[j + 1]->pathConstraint()->configProjector()->solver();
+      for (std::size_t i = 0; i < constraints_.size(); i++) {
+        // transition from j-1 to j does not contain this constraint
+        // transition from j to j+1 (all the way to goal) has constraint
+        // constraint must be added to solve for waypoint at j (WP_j+1)
+        if (d.M_status(i, j - 1) == OptimizationData::ABSENT &&
+            d.M_status(i, j) == OptimizationData::EQUAL_TO_GOAL &&
+            !contains(d.solvers[j], constraints_[i]) &&
+            otherSolver.contains(constraints_[i])) {
+          d.solvers[j].add(constraints_[i]);
+          hppDout(info, "Adding missing constraint "
+                            << constraints_[i]->function().name()
+                            << " to solver for waypoint" << j + 1);
+        }
+      }
+    }
+  }
+
+  // if goal is defined by constraints, some goal constraints may be
+  // missing from the end state. we should add these constraints to solver
+  // for the config in the final state
+  if (goalDefinedByConstraints_) {
+    for (auto goalConstraint : goalConstraints_) {
+      if (!containsStricter(d.solvers[d.N - 1], goalConstraint)) {
+        d.solvers[d.N - 1].add(goalConstraint);
+        hppDout(info, "Adding goal constraint "
+                          << goalConstraint->function().name()
+                          << " to solver for waypoint" << d.N);
+      }
+    }
+  }
+
+  return true;
+}
+
+bool StatesPathFinder::checkSolverRightHandSide(std::size_t ictr,
+                                                std::size_t jslv) const {
+  const OptimizationData& d = *optData_;
+  ImplicitPtr_t c = constraints_[ictr]->copy();
+  const Solver_t& solver = d.solvers[jslv];
+  vector_t rhs(c->rightHandSideSize());
+  solver.getRightHandSide(c, rhs);
+  LiegroupElement rhsNow(c->function().outputSpace());
+  assert(rhsNow.size() == rhs.size());
+  rhsNow.vector() = rhs;
+  LiegroupElement rhsOther(c->function().outputSpace());
+  switch (d.M_status(ictr, jslv)) {
+    case OptimizationData::EQUAL_TO_INIT:
+      c->rightHandSideFromConfig(d.q1, rhsOther);
+      break;
+    case OptimizationData::EQUAL_TO_GOAL:
+      assert(!goalDefinedByConstraints_);
+      c->rightHandSideFromConfig(d.q2, rhsOther);
+      break;
+    case OptimizationData::EQUAL_TO_PREVIOUS:
+      c->rightHandSideFromConfig(d.waypoint.col(jslv - 1), rhsOther);
+      break;
+    case OptimizationData::ABSENT:
+    default:
+      return true;
+  }
+  hpp::pinocchio::vector_t diff = rhsOther - rhsNow;
+  hpp::pinocchio::vector_t diffmask =
+      hpp::pinocchio::vector_t::Zero(diff.size());
+  for (auto k : c->activeRows())  // filter with constraint mask
+    for (size_type kk = k.first; kk < k.first + k.second; kk++)
+      diffmask[kk] = diff[kk];
+  value_type eps(problem_->constraintGraph()->errorThreshold());
+  value_type eps2(eps * eps);
+  if (diffmask.squaredNorm() > eps2)
+    hppDout(warning, diffmask.squaredNorm() << " vs " << eps2);
+  return diffmask.squaredNorm() < eps2;
+}
+
+bool StatesPathFinder::checkSolverRightHandSide(std::size_t jslv) const {
+  for (std::size_t ictr = 0; ictr < constraints_.size(); ictr++)
+    if (!checkSolverRightHandSide(ictr, jslv)) return false;
+  return true;
+}
+
+bool StatesPathFinder::buildOptimizationProblemFromNames(
+    std::vector<std::string> names) {
+  graph::Edges_t transitions;
+  graph::GraphPtr_t cg(problem_->constraintGraph());
+  for (const std::string& name : names) {
+    for (std::size_t i = 0; i < cg->nbComponents(); ++i) {
+      graph::EdgePtr_t edge(
+          HPP_DYNAMIC_PTR_CAST(graph::Edge, cg->get(i).lock()));
+      if (edge && edge->name() == name) transitions.push_back(edge);
+    }
+  }
+  return buildOptimizationProblem(transitions);
+}
+
+void StatesPathFinder::preInitializeRHS(std::size_t j, Configuration_t& q) {
+  OptimizationData& d = *optData_;
+  Solver_t& solver(d.solvers[j]);
+  for (std::size_t i = 0; i < constraints_.size(); ++i) {
+    const ImplicitPtr_t& c(constraints_[i]);
+    bool ok = true;
+    switch (d.M_status((size_type)i, (size_type)j)) {
+      case OptimizationData::EQUAL_TO_INIT:
+        ok = solver.rightHandSideFromConfig(c, d.q1);
+        break;
+      case OptimizationData::EQUAL_TO_GOAL:
+        assert(!goalDefinedByConstraints_);
+        ok = solver.rightHandSideFromConfig(c, d.q2);
+        break;
+      case OptimizationData::EQUAL_TO_PREVIOUS:
+        ok = solver.rightHandSideFromConfig(c, q);
+        break;
+      case OptimizationData::ABSENT:
+      default:;
+    }
+    ok |= contains(solver, constraints_[i]);
+    if (!ok) {
+      std::ostringstream err_msg;
+      err_msg << "\nConstraint " << i << " missing for waypoint " << j + 1
+              << " (" << c->function().name() << ")\n"
+              << "The constraints in this solver are:\n";
+      for (const std::string& name : constraintNamesFromSolverAtWaypoint(j + 1))
+        err_msg << name << "\n";
+      hppDout(warning, err_msg.str());
+    }
+    assert(ok);
+  }
+}
+
+bool StatesPathFinder::analyseOptimizationProblem(
+    const graph::Edges_t& transitions) {
+  OptimizationData& d = *optData_;
+  size_type nTriesMax =
+      problem_->getParameter("StatesPathFinder/maxTriesCollisionAnalysis")
+          .intValue();
+  if (nTriesMax == 0) return true;
+  for (std::size_t wp = 1; wp <= d.solvers.size(); wp++) {
+    std::size_t j = wp - 1;
+    const Solver_t& solver = d.solvers[j];
+    using namespace core;
+    Solver_t::Status status;
+    size_type tries = 0;
+    Configuration_t q;
+    do {
+      q = *(problem()->configurationShooter()->shoot());
+      preInitializeRHS(j, q);
+      status = solver.solve(q, constraints::solver::lineSearch::Backtracking());
+    } while ((status != Solver_t::SUCCESS) && (++tries <= nTriesMax));
+    if (tries > nTriesMax) {
+      hppDout(info, "Collision analysis stopped at WP "
+                        << wp << " because of too many bad solve statuses");
+      return false;
+    }
+    CollisionValidationPtr_t collisionValidations =
+        CollisionValidation::create(problem_->robot());
+    collisionValidations->checkParameterized(true);
+    collisionValidations->computeAllContacts(true);
+    ValidationReportPtr_t validationReport;
+    bool ok = true;
+    if (!collisionValidations->validate(q, validationReport)) {
+      AllCollisionsValidationReportPtr_t allReports =
+          HPP_DYNAMIC_PTR_CAST(AllCollisionsValidationReport, validationReport);
+      assert(allReports);
+      std::size_t nbReports = allReports->collisionReports.size();
+      hppDout(info, wp << " nbReports: " << nbReports);
+      for (std::size_t i = 0; i < nbReports; i++) {
+        CollisionValidationReportPtr_t& report =
+            allReports->collisionReports[i];
+        JointConstPtr_t j1 = report->object1->joint();
+        JointConstPtr_t j2 = report->object2->joint();
+        if (!j1 || !j2) continue;
+        const EdgePtr_t& edge = transitions[wp];
+        RelativeMotion::matrix_type m = edge->relativeMotion();
+        RelativeMotion::RelativeMotionType rmt =
+            m(RelativeMotion::idx(j1), RelativeMotion::idx(j2));
+        hppDout(info, "report " << i << " joints names \n"
+                                << j1->name() << "\n"
+                                << j2->name() << "\n"
+                                << rmt);
+        if (rmt == RelativeMotion::RelativeMotionType::Unconstrained) continue;
+        ok = false;
+        break;
+      }
+    }
+    if (!ok) {
+      hppDout(info, "Analysis found a collision at WP " << wp);
+      return false;
+    }
+    hppDout(info, "Analysis at WP " << wp << " passed after " << tries
+                                    << " solve tries");
+  }
+  return true;
+}
+
+bool StatesPathFinder::analyseOptimizationProblem2(
+    const graph::Edges_t& transitions, core::ProblemConstPtr_t _problem) {
+  typedef constraints::JointConstPtr_t JointConstPtr_t;
+  typedef core::RelativeMotion RelativeMotion;
+
+  OptimizationData& d = *optData_;
+  // map from pair of joint indices to vectors of constraints
+  typedef std::map<std::pair<size_type, size_type>, NumericalConstraints_t>
+      JointConstraintMap;
+  JointConstraintMap jcmap;
+
+  // iterate over all the transitions, propagate only constrained pairs
+  for (std::size_t i = 0; i <= transitions.size() - 1; ++i) {
+    // get index of the transition
+    std::size_t transIdx = transitions.size() - 1 - i;
+    const EdgePtr_t& edge = transitions[transIdx];
+    RelativeMotion::matrix_type m = edge->relativeMotion();
+
+    // check through the pairs already existing in jcmap
+    JointConstraintMap::iterator it = jcmap.begin();
+    while (it != jcmap.end()) {
+      RelativeMotion::RelativeMotionType rmt =
+          m(it->first.first, it->first.second);
+      if (rmt == RelativeMotion::RelativeMotionType::Unconstrained) {
+        JointConstraintMap::iterator toErase = it;
+        ++it;
+        jcmap.erase(toErase);
+      } else {
+        ++it;
+      }
+    }
+    NumericalConstraints_t currentConstraints;
+    if (transIdx == transitions.size() - 1 && !goalDefinedByConstraints_) {
+      // get the constraints from the goal state
+      currentConstraints = transitions[transIdx]
+                               ->targetConstraint()
+                               ->configProjector()
+                               ->solver()
+                               .constraints();
+    } else {
+      currentConstraints = d.solvers[transIdx].constraints();
+    }
+    // loop through all constraints in the target node of the transition
+    for (auto constraint : currentConstraints) {
+      std::pair<JointConstPtr_t, JointConstPtr_t> jointPair =
+          constraint->functionPtr()->dependsOnRelPoseBetween(_problem->robot());
+      JointConstPtr_t joint1 = jointPair.first;
+      size_type index1 = RelativeMotion::idx(joint1);
+      JointConstPtr_t joint2 = jointPair.second;
+      size_type index2 = RelativeMotion::idx(joint2);
+
+      // ignore constraint if it involves the same joint
+      if (index1 == index2) continue;
+
+      // check that the two joints are constrained in the transition
+      RelativeMotion::RelativeMotionType rmt = m(index1, index2);
+      if (rmt == RelativeMotion::RelativeMotionType::Unconstrained) continue;
+
+      // insert if necessary
+      JointConstraintMap::iterator next =
+          jcmap
+              .insert(JointConstraintMap::value_type(
+                  std::make_pair(index1, index2), NumericalConstraints_t()))
+              .first;
+      // if constraint is not in map, insert it
+      if (find_if(next->second.begin(), next->second.end(),
+                  [&constraint](const ImplicitPtr_t& arg) {
+                    return *arg == *constraint;
+                  }) == next->second.end()) {
+        next->second.push_back(constraint);
+      }
+    }
+  }
+  // at this point jcmap contains all the constraints that
+  // - depend on relative pose between 2 joints j1 and j2,
+  // - are present in the solver of any waypoint wp_i
+  // - j1 and j2 are constrained by all the transitions from q_init to wp_i
+  //
+  // in the following we test that q_init satisfies the above constraints
+  // otherwise the list of transitions is discarded
+  if (jcmap.size() == 0) {
+    return true;
+  }
+
+  Solver_t analyseSolver(_problem->robot()->configSpace());
+  analyseSolver.errorThreshold(
+      _problem->getParameter("StatesPathFinder/errorThreshold").floatValue());
+  // iterate through all the pairs that are left,
+  // and check that the initial config satisfies all the constraints
+  for (JointConstraintMap::iterator it(jcmap.begin()); it != jcmap.end();
+       it++) {
+    NumericalConstraints_t constraintList = it->second;
+    hppDout(info, "Constraints involving joints "
+                      << it->first.first << " and " << it->first.second
+                      << " should be satisfied at q init");
+    for (NumericalConstraints_t::iterator ctrIt(constraintList.begin());
+         ctrIt != constraintList.end(); ++ctrIt) {
+      analyseSolver.add((*ctrIt)->copy());
+    }
+  }
+  // initialize the right hand side with the initial config
+  analyseSolver.rightHandSideFromConfig(*q1_);
+  if (analyseSolver.isSatisfied(*q1_)) {
+    return true;
+  }
+  hppDout(info,
+          "Analysis found initial configuration does not satisfy constraint");
+  return false;
+}
+
+void StatesPathFinder::initializeRHS(std::size_t j) {
+  OptimizationData& d = *optData_;
+  Solver_t& solver(d.solvers[j]);
+  for (std::size_t i = 0; i < constraints_.size(); ++i) {
+    const ImplicitPtr_t& c(constraints_[i]);
+    bool ok = true;
+    switch (d.M_status((size_type)i, (size_type)j)) {
+      case OptimizationData::EQUAL_TO_PREVIOUS:
+        assert(j != 0);
+        ok = solver.rightHandSideFromConfig(c, d.waypoint.col(j - 1));
+        break;
+      case OptimizationData::EQUAL_TO_INIT:
+        ok = solver.rightHandSideFromConfig(c, d.q1);
+        break;
+      case OptimizationData::EQUAL_TO_GOAL:
+        assert(!goalDefinedByConstraints_);
+        ok = solver.rightHandSideFromConfig(c, d.q2);
+        break;
+      case OptimizationData::ABSENT:
+      default:;
+    }
+    ok |= contains(solver, constraints_[i]);
+    if (!ok) {
+      std::ostringstream err_msg;
+      err_msg << "\nConstraint " << i << " missing for waypoint " << j + 1
+              << " (" << c->function().name() << ")\n"
+              << "The constraints in this solver are:\n";
+      for (const std::string& name : constraintNamesFromSolverAtWaypoint(j + 1))
+        err_msg << name << "\n";
+      hppDout(warning, err_msg.str());
+    }
+    assert(ok);
+  }
+}
+
+void StatesPathFinder::initWPRandom(std::size_t wp) {
+  assert(wp >= 1 && wp <= (std::size_t)optData_->waypoint.cols());
+  initializeRHS(wp - 1);
+  optData_->waypoint.col(wp - 1) =
+      *(problem()->configurationShooter()->shoot());
+}
+void StatesPathFinder::initWPNear(std::size_t wp) {
+  assert(wp >= 1 && wp <= (std::size_t)optData_->waypoint.cols());
+  initializeRHS(wp - 1);
+  if (wp == 1)
+    optData_->waypoint.col(wp - 1) = optData_->q1;
+  else
+    optData_->waypoint.col(wp - 1) = optData_->waypoint.col(wp - 2);
+}
+void StatesPathFinder::initWP(std::size_t wp, ConfigurationIn_t q) {
+  assert(wp >= 1 && wp <= (std::size_t)optData_->waypoint.cols());
+  initializeRHS(wp - 1);
+  optData_->waypoint.col(wp - 1) = q;
+}
+
+StatesPathFinder::SolveStepStatus StatesPathFinder::solveStep(std::size_t wp) {
+  assert(wp >= 1 && wp <= (std::size_t)optData_->waypoint.cols());
+  std::size_t j = wp - 1;
+  Solver_t& solver(optData_->solvers[j]);
+  Solver_t::Status status =
+      solver.solve(optData_->waypoint.col(j),
+                   constraints::solver::lineSearch::Backtracking());
+  if (status == Solver_t::SUCCESS) {
+    assert(checkWaypointRightHandSide(j));
+    const graph::Edges_t& edges = lastBuiltTransitions_;
+    core::ValidationReportPtr_t report;
+    // check collision based on preceeding edge to the waypoint
+    if (!edges[j]->pathValidation()->validate(optData_->waypoint.col(j),
+                                              report))
+      return SolveStepStatus::COLLISION_BEFORE;
+    // if wp is not the goal node, check collision based on following edge
+    if (j < edges.size() - 1 && !edges[j + 1]->pathValidation()->validate(
+                                    optData_->waypoint.col(j), report)) {
+      return SolveStepStatus::COLLISION_AFTER;
+    }
+    return SolveStepStatus::VALID_SOLUTION;
+  }
+  return SolveStepStatus::NO_SOLUTION;
+}
+
+std::string StatesPathFinder::displayConfigsSolved() const {
+  const OptimizationData& d = *optData_;
+  std::ostringstream oss;
+  oss << "configs = [" << std::endl;
+  oss << "  " << pinocchio::displayConfig(d.q1) << ",  # 0" << std::endl;
+  for (size_type j = 0; j < d.waypoint.cols(); j++)
+    oss << "  " << pinocchio::displayConfig(d.waypoint.col(j)) << ",  # "
+        << j + 1 << std::endl;
+  if (!goalDefinedByConstraints_) {
+    oss << "  " << pinocchio::displayConfig(d.q2) << "  # "
+        << d.waypoint.cols() + 1 << std::endl;
+  }
+  oss << "]" << std::endl;
+  std::string ans = oss.str();
+  hppDout(info, ans);
+  return ans;
+}
+
+bool StatesPathFinder::solveOptimizationProblem() {
+  OptimizationData& d = *optData_;
+  // Try to solve with sets of random configs for each waypoint
+  std::size_t nTriesMax =
+      problem_->getParameter("StatesPathFinder/nTriesUntilBacktrack")
+          .intValue();
+  std::size_t nTriesMax1 = nTriesMax * 10;  // more tries for the first waypoint
+  std::size_t nFailsMax =
+      nTriesMax * 20;  // fails before reseting the whole solution
+  std::size_t nBadSolvesMax =
+      nTriesMax * 50;  // bad solve fails before reseting the whole solution
+  std::vector<std::size_t> nTriesDone(d.solvers.size() + 1, 0);
+  std::size_t nFails = 0;
+  std::size_t nBadSolves = 0;
+  std::size_t wp = 1;      // waypoint index starting at 1 (wp 0 = q1)
+  std::size_t wp_max = 0;  // all waypoints up to this index are valid solved
+  matrix_t longestSolved(d.nq, d.N);
+  longestSolved.setZero();
+  while (wp <= d.solvers.size()) {
+    if (wp == 1) {
+      // stop if number of tries for the first waypoint exceeds
+      if (nTriesDone[1] >= nTriesMax1) {
+        // if cannot solve all the way, return longest VALID sequence
+        d.waypoint = longestSolved;
+        displayConfigsSolved();
+        return false;
+      }
+      // Reset the fail counter while the solution is empty
+      nFails = 0;
+      nBadSolves = 0;
+
+    } else if (nFails >= nFailsMax || nBadSolves >= nBadSolvesMax) {
+      // Completely reset a solution when too many tries have failed
+
+      // update the longest valid sequence of waypoints solved
+      if (wp - 1 > wp_max) {
+        // update the maximum index of valid waypoint
+        wp_max = wp - 1;
+        // save the sequence
+        longestSolved.leftCols(wp_max) = d.waypoint.leftCols(wp_max);
+      }
+
+      std::fill(nTriesDone.begin() + 2, nTriesDone.end(), 0);
+      wp = 1;
+
+#ifdef HPP_DEBUG
+      if (nFails >= nFailsMax) {
+        hppDout(warning, " Solution "
+                             << graphData_->idxSol
+                             << ": too many collisions. Resetting back to WP1");
+      }
+      if (nBadSolves >= nBadSolvesMax) {
+        hppDout(warning,
+                " Solution "
+                    << graphData_->idxSol
+                    << ": too many bad solve statuses. Resetting back to WP1");
+      }
+#endif
+
+      continue;
+
+    } else if (nTriesDone[wp] >= nTriesMax) {
+      // enough tries for a waypoint: backtrack
+
+      // update the longest valid sequence of waypoints solved
+      if (wp - 1 > wp_max) {
+        // update the maximum index of valid waypoint
+        wp_max = wp - 1;
+        // save the sequence
+        longestSolved.leftCols(wp_max) = d.waypoint.leftCols(wp_max);
+      }
+
+      do {
+        nTriesDone[wp] = 0;
+        wp--;  // backtrack: try a new solution for the latest waypoint
+      } while (wp > 1 &&
+               (d.isTargetWaypoint[wp - 1] || nTriesDone[wp] >= nTriesMax));
+
+      continue;
+    }
+
+    // Initialize right hand sides, and
+    // Choose a starting configuration for the solver.solve method:
+    // - from previous waypoint if it's the first time we see this solver
+    //   given current solvers 0 to j-1
+    // - with a random configuration if the other initialization has been
+    //   tried and failed
+    if (nTriesDone[wp] == 0)
+      initWPNear(wp);
+    else
+      initWPRandom(wp);
+
+    nTriesDone[wp]++;  // Backtrack to last state when this gets too big
+
+    SolveStepStatus out = solveStep(wp);
+    hppDout(info, "solveStep exit code at WP" << wp << ": " << out);
+    switch (out) {
+      case SolveStepStatus::VALID_SOLUTION:  // Valid solution, go to next
+                                             // waypoint
+        wp++;
+        break;
+      case SolveStepStatus::NO_SOLUTION:  // Bad solve status, considered usual
+                                          // so has higher threshold before
+                                          // going back to first waypoint
+        nBadSolves++;
+        break;
+      case SolveStepStatus::COLLISION_BEFORE:  // Collision. If that happens too
+                                               // much, go back to first
+                                               // waypoint
+      case SolveStepStatus::COLLISION_AFTER:
+        nFails++;
+        break;
+      default:
+        throw(std::logic_error("Unintended exit code for solveStep"));
+    }
+  }
+
+  displayConfigsSolved();
+  // displayRhsMatrix ();
+
+  return true;
+}
+
+// Get list of configurations from solution of optimization problem
+core::Configurations_t StatesPathFinder::getConfigList() const {
+  OptimizationData& d = *optData_;
+  core::Configurations_t pv;
+  ConfigurationPtr_t q1(new Configuration_t(d.q1));
+  pv.push_back(q1);
+  for (std::size_t i = 0; i < d.N; ++i) {
+    ConfigurationPtr_t q(new Configuration_t(d.waypoint.col(i)));
+    pv.push_back(q);
+  }
+  if (!goalDefinedByConstraints_) {
+    ConfigurationPtr_t q2(new Configuration_t(d.q2));
+    pv.push_back(q2);
+  }
+  return pv;
+}
+
+// Loop over all the possible paths in the constraint graph between
+// the state of the initial configuration
+// and either [the state of the final configurations if given] OR
+// [one of potential goal states if goal defined as set of constraints]
+// and compute waypoint configurations in each state.
+core::Configurations_t StatesPathFinder::computeConfigList(
+    ConfigurationIn_t q1, ConfigurationPtr_t q2) {
+  const graph::GraphPtr_t& graph(problem_->constraintGraph());
+  GraphSearchData& d = *graphData_;
+
+  size_t& idxSol = d.idxSol;
+
+  bool maxDepthReached;
+  while (!(maxDepthReached = findTransitions(d))) {  // mut
+    // if there is a working sequence, try it first before getting another
+    // transition list
+    Edges_t transitions = (nTryConfigList_ > 0)
+                              ? lastBuiltTransitions_
+                              : getTransitionList(d, idxSol);  // const, const
+    while (!transitions.empty()) {
+#ifdef HPP_DEBUG
+      std::ostringstream ss;
+      ss << " Trying solution " << idxSol << ": \n\t";
+      for (std::size_t j = 0; j < transitions.size(); ++j)
+        ss << transitions[j]->name() << ", \n\t";
+      hppDout(info, ss.str());
+#endif  // HPP_DEBUG
+      if (optData_) {
+        delete optData_;
+        optData_ = nullptr;
+      }
+      optData_ = new OptimizationData(problem(), q1, q2, transitions,
+                                      goalDefinedByConstraints_);
+
+      if (buildOptimizationProblem(transitions)) {
+        lastBuiltTransitions_ = transitions;
+        if (nTryConfigList_ > 0 ||
+            analyseOptimizationProblem2(transitions, problem())) {
+          if (solveOptimizationProblem()) {
+            core::Configurations_t path = getConfigList();
+            hppDout(warning,
+                    " Solution " << idxSol << ": solved configurations list");
+            return path;
+          } else {
+            hppDout(info, " Failed solution " << idxSol
+                                              << " at step 5 (solve opt pb)");
+          }
+        } else {
+          hppDout(info, " Failed solution " << idxSol
+                                            << " at step 4 (analyse opt pb)");
+        }
+      } else {
+        hppDout(info,
+                " Failed solution " << idxSol << " at step 3 (build opt pb)");
+      }
+      transitions = getTransitionList(d, ++idxSol);
+      // reset of the number of tries for a sequence
+      nTryConfigList_ = 0;
+    }
+  }
+  core::Configurations_t empty_path;
+  ConfigurationPtr_t q(new Configuration_t(q1));
+  empty_path.push_back(q);
+  return empty_path;
+}
+
+void StatesPathFinder::reset() {
+  // when debugging, if we want to start from a certain transition list,
+  // we can set it here
+  graphData_->idxSol = 0;
+  if (optData_) {
+    delete optData_;
+    optData_ = nullptr;
+  }
+  lastBuiltTransitions_.clear();
+  idxConfigList_ = 0;
+  nTryConfigList_ = 0;
+}
+
+void StatesPathFinder::startSolve() {
+  PathPlanner::startSolve();
+  assert(problem_);
+  q1_ = problem_->initConfig();
+  assert(q1_);
+
+  // core::PathProjectorPtr_t pathProjector
+  //   (core::pathProjector::Progressive::create(inStateProblem_, 0.01));
+  // inStateProblem_->pathProjector(pathProjector);
+  inStateProblem_->pathProjector(problem_->pathProjector());
+  const graph::GraphPtr_t& graph(problem_->constraintGraph());
+  graphData_.reset(new GraphSearchData());
+  GraphSearchData& d = *graphData_;
+  d.s1 = graph->getState(*q1_);
+  d.maxDepth = problem_->getParameter("StatesPathFinder/maxDepth").intValue();
+
+  d.queue1.push_back(d.addInitState());
+  d.queueIt = d.queue1.size();
+
+#ifdef HPP_DEBUG
+  // Print out the names of all the states in graph in debug mode
+  States_t allStates = graph->stateSelector()->getStates();
+  hppDout(info, "Constraint graph has " << allStates.size() << " nodes");
+  for (auto state : allStates) {
+    hppDout(info, "State: id = " << state->id() << " name = \"" << state->name()
+                                 << "\"");
+  }
+  hppDout(info,
+          "Constraint graph has " << graph->nbComponents() << " components");
+#endif
+  // Detect whether the goal is defined by a configuration or by a
+  // set of constraints
+  ProblemTargetPtr_t target(problem()->target());
+  GoalConfigurationsPtr_t goalConfigs(
+      HPP_DYNAMIC_PTR_CAST(GoalConfigurations, target));
+  if (goalConfigs) {
+    goalDefinedByConstraints_ = false;
+    core::Configurations_t q2s = goalConfigs->configurations();
+    if (q2s.size() != 1) {
+      std::ostringstream os;
+      os << "StatesPathFinder accept one and only one goal "
+            "configuration, ";
+      os << q2s.size() << " provided.";
+      throw std::logic_error(os.str().c_str());
+    }
+    q2_ = q2s[0];
+    d.s2.push_back(graph->getState(*q2_));
+  } else {
+    TaskTargetPtr_t taskTarget(HPP_DYNAMIC_PTR_CAST(TaskTarget, target));
+    if (!taskTarget) {
+      std::ostringstream os;
+      os << "StatesPathFinder only accept goal defined as "
+            "either a configuration or a set of constraints.";
+      throw std::logic_error(os.str().c_str());
+    }
+    assert(!q2_);
+    goalDefinedByConstraints_ = true;
+    goalConstraints_ = taskTarget->constraints();
+    hppDout(info, "goal defined as a set of constraints");
+
+    int maxNumConstr = -1;
+    for (StatePtr_t state : graph->stateSelector()->getStates()) {
+      NumericalConstraints_t stateConstr = state->numericalConstraints();
+      int numConstr = 0;
+      for (auto goalConstraint : goalConstraints_) {
+        if (std::find(stateConstr.begin(), stateConstr.end(), goalConstraint) !=
+            stateConstr.end()) {
+          ++numConstr;
+          hppDout(info, "State \"" << state->name() << "\" "
+                                   << "has goal constraint: \""
+                                   << goalConstraint->function().name()
+                                   << "\"");
+        }
+      }
+      if (numConstr > maxNumConstr) {
+        d.s2.clear();
+        d.s2.push_back(state);
+        maxNumConstr = numConstr;
+      } else if (numConstr == maxNumConstr) {
+        d.s2.push_back(state);
+      }
+    }
+    d.idxSol = 0;
+  }
+  reset();
+}
+
+void StatesPathFinder::oneStep() {
+  if (idxConfigList_ == 0) {
+    // TODO: accommodate when goal is a set of constraints
+    assert(q1_);
+    configList_ = computeConfigList(*q1_, q2_);
+    if (configList_.size() <= 1) {  // max depth reached
+      reset();
+      throw core::path_planning_failed("Maximal depth reached.");
+    }
+  }
+  size_t& idxSol = graphData_->idxSol;
+  ConfigurationPtr_t q1, q2;
+  if (idxConfigList_ >= configList_.size() - 1) {
+    reset();
+    throw core::path_planning_failed(
+        "Final config reached but goal is not reached.");
+  }
+  q1 = configList_[idxConfigList_];
+  q2 = configList_[idxConfigList_ + 1];
+  const graph::EdgePtr_t& edge(lastBuiltTransitions_[idxConfigList_]);
+  // Copy edge constraints
+  core::ConstraintSetPtr_t constraints(HPP_DYNAMIC_PTR_CAST(
+      core::ConstraintSet, edge->pathConstraint()->copy()));
+  // Initialize right hand side
+  constraints->configProjector()->rightHandSideFromConfig(*q1);
+  assert(constraints->isSatisfied(*q2));
+  inStateProblem_->constraints(constraints);
+  inStateProblem_->pathValidation(edge->pathValidation());
+  inStateProblem_->steeringMethod(edge->steeringMethod());
+  inStateProblem_->initConfig(q1);
+  inStateProblem_->resetGoalConfigs();
+  inStateProblem_->addGoalConfig(q2);
+
+  /// use inner state planner to plan path between two configurations.
+  /// these configs lie on same leaf (same RHS wrt edge constraints)
+  /// eg consecutive configs in the solved config list
+  core::PathPlannerPtr_t inStatePlanner(
+      core::DiffusingPlanner::create(inStateProblem_));
+  inStatePlanner->maxIterations(
+      problem_->getParameter("StatesPathFinder/innerPlannerMaxIterations")
+          .intValue());
+  value_type innerPlannerTimeout =
+      problem_->getParameter("StatesPathFinder/innerPlannerTimeOut")
+          .floatValue();
+  // only set timeout if it is more than 0. default is infinity
+  if (innerPlannerTimeout > 0.) {
+    inStatePlanner->timeOut(innerPlannerTimeout);
+  }
+  hppDout(info,
+          "calling InStatePlanner_.solve for transition " << idxConfigList_);
+  core::PathVectorPtr_t path;
+  try {
+    path = inStatePlanner->solve();
+    for (std::size_t r = 0; r < path->numberPaths() - 1; r++)
+      assert(path->pathAtRank(r)->end() == path->pathAtRank(r + 1)->initial());
+    idxConfigList_++;
+    if (idxConfigList_ == configList_.size() - 1) {
+      hppDout(
+          warning, "Solution "
+                       << idxSol << ": Success"
+                       << "\n-----------------------------------------------");
+    }
+  } catch (const core::path_planning_failed& error) {
+    std::ostringstream oss;
+    oss << "Error " << error.what() << "\n";
+    oss << "Solution " << idxSol << ": Failed to build path at edge "
+        << idxConfigList_ << ": ";
+    oss << lastBuiltTransitions_[idxConfigList_]->name();
+    hppDout(warning, oss.str());
+
+    idxConfigList_ = 0;
+    // Retry nTryMax times to build another solution for the same states list
+    size_type nTryMax =
+        problem_->getParameter("StatesPathFinder/maxTriesBuildPath").intValue();
+    if (++nTryConfigList_ >= nTryMax) {
+      nTryConfigList_ = 0;
+      idxSol++;
+    }
+  }
+  roadmap()->merge(inStatePlanner->roadmap());
+  // if (path) {
+  //   core::PathOptimizerPtr_t inStateOptimizer
+  //     (core::pathOptimization::RandomShortcut::create(inStateProblem_));
+  //   core::PathVectorPtr_t opt = inStateOptimizer->optimize(path);
+  //   roadmap()->insertPathVector(opt, true);
+  // }
+}
+
+void StatesPathFinder::tryConnectInitAndGoals() {
+  GraphSearchData& d = *graphData_;
+  // if start state is not one of the potential goal states, return
+  if (std::find(d.s2.begin(), d.s2.end(), d.s1) == d.s2.end()) {
+    return;
+  }
+
+  // get the loop edge connecting the initial state to itself
+  const graph::Edges_t& loopEdges(
+      problem_->constraintGraph()->getEdges(d.s1, d.s1));
+  // check that there is 1 loop edge
+  assert(loopEdges.size() == 1);
+  // add the loop transition as transition list
+  GraphSearchData::state_with_depth_ptr_t _state = d.queue1.front();
+  GraphSearchData::state_with_depth_ptr_t _endState =
+      d.addParent(_state, loopEdges[0]);
+  d.solutions.push_back(_endState);
+
+  // try connecting initial and final configurations directly
+  if (!goalDefinedByConstraints_) PathPlanner::tryConnectInitAndGoals();
+}
+
+std::vector<std::string> StatesPathFinder::constraintNamesFromSolverAtWaypoint(
+    std::size_t wp) {
+  assert(wp > 0 && wp <= optData_->solvers.size());
+  constraints::solver::BySubstitution& solver(optData_->solvers[wp - 1]);
+  std::vector<std::string> ans;
+  for (std::size_t i = 0; i < solver.constraints().size(); i++)
+    ans.push_back(solver.constraints()[i]->function().name());
+  return ans;
+}
+
+std::vector<std::string> StatesPathFinder::lastBuiltTransitions() const {
+  std::vector<std::string> ans;
+  for (const EdgePtr_t& edge : lastBuiltTransitions_)
+    ans.push_back(edge->name());
+  return ans;
+}
+
+using core::Parameter;
+using core::ParameterDescription;
+
+HPP_START_PARAMETER_DECLARATION(StatesPathFinder)
+core::Problem::declareParameter(ParameterDescription(
+    Parameter::INT, "StatesPathFinder/maxDepth",
+    "Maximum number of transitions to look for.",
+    Parameter((size_type)std::numeric_limits<int>::max())));
+core::Problem::declareParameter(ParameterDescription(
+    Parameter::INT, "StatesPathFinder/maxIteration",
+    "Maximum number of iterations of the Newton Raphson algorithm.",
+    Parameter((size_type)60)));
+core::Problem::declareParameter(ParameterDescription(
+    Parameter::FLOAT, "StatesPathFinder/errorThreshold",
+    "Error threshold of the Newton Raphson algorithm."
+    "Should be at most the same as error threshold in constraint graph",
+    Parameter(1e-4)));
+core::Problem::declareParameter(ParameterDescription(
+    Parameter::INT, "StatesPathFinder/nTriesUntilBacktrack",
+    "Number of tries when sampling configurations before backtracking"
+    "in function solveOptimizationProblem.",
+    Parameter((size_type)3)));
+core::Problem::declareParameter(ParameterDescription(
+    Parameter::INT, "StatesPathFinder/maxTriesCollisionAnalysis",
+    "Number of solve tries before stopping the collision analysis,"
+    "before the actual solving part."
+    "Set to 0 to skip this part of the algorithm.",
+    Parameter((size_type)100)));
+core::Problem::declareParameter(ParameterDescription(
+    Parameter::INT, "StatesPathFinder/maxTriesBuildPath",
+    "Number of solutions with a given states list to try to build a"
+    "continuous path from, before skipping to the next states list",
+    Parameter((size_type)5)));
+core::Problem::declareParameter(ParameterDescription(
+    Parameter::FLOAT, "StatesPathFinder/innerPlannerTimeOut",
+    "This will set ::timeOut accordingly in the inner"
+    "planner used for building a path after intermediate"
+    "configurations have been found."
+    "If set to 0, no timeout: only maxIterations will be used to stop"
+    "the innerPlanner if it does not find a path.",
+    Parameter(2.0)));
+core::Problem::declareParameter(ParameterDescription(
+    Parameter::INT, "StatesPathFinder/innerPlannerMaxIterations",
+    "This will set ::maxIterations accordingly in the inner"
+    "planner used for building a path after intermediate"
+    "configurations have been found",
+    Parameter((size_type)1000)));
+HPP_END_PARAMETER_DECLARATION(StatesPathFinder)
+}  // namespace pathPlanner
+}  // namespace manipulation
+}  // namespace hpp
diff --git a/src/problem-solver.cc b/src/problem-solver.cc
index 6f9711b0041255c5398743c899febbe5d5caa2bb..ac5d640db353717333004905f07048c88e75c59d 100644
--- a/src/problem-solver.cc
+++ b/src/problem-solver.cc
@@ -62,6 +62,8 @@
 #include "hpp/manipulation/path-optimization/enforce-transition-semantic.hh"
 #include "hpp/manipulation/path-optimization/random-shortcut.hh"
 #include "hpp/manipulation/path-planner/end-effector-trajectory.hh"
+#include "hpp/manipulation/path-planner/in-state-path.hh"
+#include "hpp/manipulation/path-planner/states-path-finder.hh"
 #include "hpp/manipulation/problem-target/state.hh"
 #include "hpp/manipulation/problem.hh"
 #include "hpp/manipulation/roadmap.hh"
@@ -123,7 +125,8 @@ ProblemSolver::ProblemSolver() : core::ProblemSolver(), robot_(), problem_() {
   pathPlanners.add("M-RRT", ManipulationPlanner::create);
   pathPlanners.add("EndEffectorTrajectory",
                    pathPlanner::EndEffectorTrajectory::createWithRoadmap);
-
+  pathPlanners.add("StatesPathFinder",
+                   pathPlanner::StatesPathFinder::createWithRoadmap);
   pathValidations.add("Graph-Discretized",
                       createDiscretizedCollisionGraphPathValidation);
   pathValidations.add("Graph-DiscretizedCollision",