-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New vector field planner impelementation #268
Conversation
Codecov Report
@@ Coverage Diff @@
## master #268 +/- ##
==========================================
+ Coverage 70.56% 70.88% +0.32%
==========================================
Files 188 195 +7
Lines 5537 5605 +68
Branches 869 875 +6
==========================================
+ Hits 3907 3973 +66
- Misses 1097 1099 +2
Partials 533 533
|
|
||
/// This class defines a vector field in a configuration space | ||
/// | ||
/// Any vector filed should inherit this class to implememnt functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: filed --> field
, mMetaSkeleton(_stateSpace->getMetaSkeleton()) | ||
, mBodyNode(_bodyNode) | ||
{ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move the function definitions to the .cpp
file.
/// | ||
/// \param[out] _qd Joint velocities | ||
/// \return Whether joint velocities are successfully computed | ||
virtual bool getJointVelocities(Eigen::VectorXd& _qd) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function can be a const function.
/// Vectorfield planning status callback function | ||
/// | ||
/// \return Status of planning | ||
virtual VectorFieldPlannerStatus checkPlanningStatus() = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function can be a const function.
@@ -2,58 +2,88 @@ | |||
#define AIKIDO_PLANNER_VECTORFIELD_VECTORFIELDPLANNER_HPP_ | |||
|
|||
#include <functional> | |||
#include <dart/common/Timer.hpp> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you used this for debugging. We could remove this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind, dart::common::Timer
is actually used in this class.
@@ -52,6 +53,7 @@ std::unique_ptr<aikido::trajectory::Spline> convertToSpline( | |||
} | |||
|
|||
//============================================================================== | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Adding empty line here is not our convention.
@@ -62,26 +64,32 @@ DesiredTwistFunction::DesiredTwistFunction( | |||
} | |||
|
|||
//============================================================================== | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Adding empty line here is not our convention.
double DesiredTwistFunction::eval(const Eigen::VectorXd& _qd) | ||
{ | ||
return 0.5 * (mJacobian * _qd - mTwist).squaredNorm(); | ||
} | ||
|
||
//============================================================================== | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Adding empty line here is not our convention.
void DesiredTwistFunction::evalGradient( | ||
const Eigen::VectorXd& _qd, Eigen::Map<Eigen::VectorXd> _grad) | ||
{ | ||
_grad = mJacobian.transpose() * (mJacobian * _qd - mTwist); | ||
} | ||
|
||
//============================================================================== | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Adding empty line here is not our convention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this set of changes in terms of creating a class for planning algorithm and adding more useful vector field classes.
I've made some comments.
/// \return Status of planning | ||
virtual VectorFieldPlannerStatus checkPlanningStatus() = 0; | ||
|
||
/// Meta skeleton spate space |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: spate --> state
virtual VectorFieldPlannerStatus checkPlanningStatus() = 0; | ||
|
||
/// Meta skeleton spate space | ||
virtual aikido::statespace::dart::MetaSkeletonStateSpacePtr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these functions (and below getters) virtual? Any possibility that a subclass overrides this function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. I don't know why I wrote that way. Good catch! Thanks!
getMetaSkeletonStateSpace() | ||
{ | ||
return mStateSpace; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add const functions for
getMetaSkeletonStateSpace()
getMetaSkeleton()
getBodyNode()
that return pointers to const objects accordingly like aikido::statespace::dart::MetaSkeletonStateSpaceConstPtr getMetaSkeletonStateSpace() const
where MetaSkeletonStateSpaceConstPtr
is defined as using MetaSkeletonStateSpaceConstPtr = std::shared_ptr<const MetaSkeletonStateSpace>
.
This is necessary for const-correctness. Without const functions of this getters, a const instance of ConfigurationSpaceVectorField
might be able to modify the MetaSkeletonStateSpace
through this getter for example, which is not an expected behavior from a const instance.
Edit: MetaSkeletonStateSpaceConstPtr
--> ConstMetaSkeletonStateSpacePtr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should ConstPtr
be defined in original header file? Fox example using MetaSkeletonStateSpaceConstPtr = std::shared_ptr<const MetaSkeletonStateSpace>
in MetaSkeletonStateSpace.hpp
?
double _optimizationTolerance = 1e-3, | ||
double _padding = 1e-5); | ||
const Eigen::Vector3d& _direction, | ||
double _distance, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: _minDistance
to be consistent with _maxDistance
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is following the naming in prpy
. This is usually the expected distance to move. The planner will terminate once this distance is reached. It will fail it _maxDistance
is reached.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is not what the planner currently does it PrPy. It integrates as far as possible until it either reaches _maxDistance
or is unable to continue. Then, it returns success if the resulting trajectory has length greater than _distance
. I.e. the planner will return a trajectory of length [_distance, _maxDistance]
upon success.
I agree with @jslee02's naming suggestion - I think _minDistance
is more descriptive.
/// | ||
/// \param[in] _stateSpace MetaSkeleton state space | ||
/// \param[in] _bn Body node of end-effector | ||
/// \param[in] _goalPose Desired en-effector pose |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: en-effector --> end-effector
/// | ||
/// \param[in] _q Position in configuration space | ||
/// \param[in] _t Current time being planned | ||
void check(const Eigen::VectorXd& _q, double _t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think step
and check
functions should be protected because they're just used internally in this class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
double _t, | ||
Eigen::VectorXd& _dq)>; | ||
VectorFieldPlanner( | ||
const aikido::planner::vectorfield::ConfigurationSpaceVectorFieldPtr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: We don't need to specify the whole namespaces. This can be simplified as
const ConfigurationSpaceVectorFieldPtr
Please remove aikido::[planner::[vectorfield]]
in this header.
int mIndex; | ||
|
||
bool mEnableCollisionCheck; | ||
bool mEnableDofLimitCheck; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems mEnableCollisionCheck
and mEnableDofLimitCheck
are not actually used because they are always overwritten in followVectorField()
.
I think we should either (1) remove these member variables and always pass them as parameters of followVectorField()
or (2) add getters and setters for them and remove those parameters from followVectorField()
. I'm inclined to (2) because those parameters should represent state of the planner but not temporary variables.
If we choose (2) then we might want to set those parameters in the constrcutor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two member variables are actually used in check()
and step()
. These two functions are only internally called inside followVectorField()
. These two parameters are about how you shall follow a vector field (like integral time), so I put them as input arguments of followVectorField()
. They are set to member variables so that check()
and step()
could access. Any suggestion?
@@ -63,31 +93,81 @@ std::unique_ptr<aikido::trajectory::Spline> planPathByVectorField( | |||
/// \param[in] _constraint Trajectory-wide constraint that must be satisfied | |||
/// \param[in] _direction Direction of moving the end-effector | |||
/// \param[in] _distance Distance of moving the end-effector | |||
/// \param[in] _linearVelocity Linear velocity of moving the end-effector | |||
/// \param[in] _linearTolerance How a planned trajectory is allowed to | |||
/// \param[in] _maxDistance Max distance of moving the end-effector |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused with what this function wants to do. Does this function plan a trajectory moving the EE for a certain distance (> distance & > maxDistance) along the given direction, which is my interpretation according to the logic of checkPlanningStatus()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the moved distance is larger than distance
, it will terminate and return a trajectory.
But if the moved distance is larger than maxDistance
, it violates and will fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's different from my understanding of your implementation. According to this logic, the integration keeps going even when movedDistance
is greater than mDistance
(that is set by _maxDistance
), and it only terminates when mDistance
is greater than mMaxDistance
. Am I missing something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. You are right. I will update the doc strings accordingly :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the logic that @jslee02 described is correct. See my other comment about what the behavior currently is in PrPy, which is what this function mimics.
I am happy to revisit that behavior 🙃 But I think it's quite useful in practice.
|| status == VectorFieldPlannerStatus::CACHE_AND_TERMINATE) | ||
{ | ||
throw VectorFieldTerminated( | ||
"Planning was terminated by the StatusCallback."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we treat the termination of planning (which I believe it's successfully terminated) as an exception?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. This is the way of stopping integration before it is done. Usually we set 10 sec to integrate but can reach an expected distance before that. Any other suggested way?
double _optimizationTolerance = 1e-3); | ||
|
||
/// Vectorfield callback function | ||
/// | ||
/// \param[out] _qd Joint velocities | ||
/// \return Whether joint velocities are successfully computed | ||
virtual bool getJointVelocities(Eigen::VectorXd& _qd) override; | ||
virtual bool getJointVelocities(Eigen::VectorXd& _qd) const override; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please remove virtual
. virtual
is redundant for an overridden function.
|
||
/// Vectorfield planning status callback function | ||
/// | ||
/// \return Status of planning | ||
virtual VectorFieldPlannerStatus checkPlanningStatus() override; | ||
virtual VectorFieldPlannerStatus checkPlanningStatus() const override; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please remove virtual
. virtual
is redundant for an overridden function.
@@ -44,22 +44,22 @@ class MoveEndEffectorPoseVectorField : public ConfigurationSpaceVectorField | |||
/// \param[in] _t Current time being planned | |||
/// \param[out] _qd Joint velocities | |||
/// \return Whether joint velocities are successfully computed | |||
virtual bool getJointVelocities(Eigen::VectorXd& _qd) override; | |||
virtual bool getJointVelocities(Eigen::VectorXd& _qd) const override; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please remove virtual
. virtual
is redundant for an overridden function.
|
||
/// Vectorfield planning status callback function | ||
/// | ||
/// \param[in] _q Position in configuration space | ||
/// \param[in] _t Current time being planned | ||
/// \return Status of planning | ||
virtual VectorFieldPlannerStatus checkPlanningStatus() override; | ||
virtual VectorFieldPlannerStatus checkPlanningStatus() const override; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please remove virtual
. virtual
is redundant for an overridden function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this refactor @dqyi11!
Some high-level comments:
Integration Time. The time parameter used by the integrator is not meaningful because the planner does not consider dynamics. Therefore, integration time should be treated as an implementation detail of the integrator, i.e. not used as a termination condition or argument to the vector field. This may require calling the Stepper
directly, rather than using the integrate_adaptive()
helper method. Thankfully, that method does not actually do very much.
Non-Rn
State Space Support. To be consistent with the rest of Aikido's planners, a vector field should be evaluated on a State
- not an Eigen::VectorXd
. This means that the integrator needs to use the operations defined on StateSpace
(e.g. compose()
, expMap()
, logMap()
) to interact with the states rather than vector operations. This is important to support configuration spaces with other topologies.
The correct way to implement this using Boost.Numeric.OdeInt is likely to configure the integrator to define custom StateType
, Algebra
, Operations
types that internally call functions on the base Aikido StateSpace
API. This should be straightforward because there is a relatively direct mapping between the Aikido API and the API desired by Boost.
Non-MetaSkeletonStateSpace
Support. The base vector field planner should work on an arbitrary StateSpace
using state space operations (see above). A MetaSkeletonStateSpace
is only required when mapping from workspace to configuration space. See below for one idea about how to implement this.
Joint Limits and Collision Checking. These should be implemented using the Aikido constraint API. The easiest way of implementing this is likely to accept a Testable
constraint. The user can then pass in the appropriate NonColliding
and joint limit constraints for their robot.
Constraint Validation. The resolution at which constraints are checked should not be related to the integration step size. Those are independent quantities: constraints should be checked at a user defined resolution (e.g. chosen with knowledge of the robot's kinematics and the amount of padding on objects in the environment) and the latter is an internal parameter chosen by the integrator (e.g. larger when the vector field is flat and smaller when it's derivatives are large).
Conceptually, you can think about these as two separate operations: the planner first integrates a trajectory, then tests the entire trajectory for constraint satisfaction at a user-defined resolution. In practice, to be more efficient, we can interleave the two operations.
To do this, the planner must maintain two pieces of state: (1) the trajectory integrated so far and (2) the last point in the trajectory that was checked for constraint satisfaction. Then, after taking a step, the planner can check the new portion of the trajectory for validity. I can sketch this out in more detail if my explanation is not clear.
Here is a potential sketch of an API that makes the base VectorField
class for for an arbitrary StateSpace
:
-
A base
VectorField
class that operates on an arbitraryStateSpace
.class VectorField { public: bool evaluateVelocity(const StateSpace::State* _state, Eigen::VectorXd* _velocity) = 0; // Preferable: Status is a function of the entire trajectory so far. VectorFieldPlannerStatus evaluateStatus( const aikido::trajectory::Trajectory& _trajectory) = 0; // Alternative: Status is only a function of the current state. VectorFieldPlannerStatus evaluateStatus( const StateSpace::State* _state) = 0; };
-
A
BodyNodePoseVectorField
defined for aMetaSkeletonStateSpace
that converts a vector field for aBodyNode
's pose (i.e. defined over SE(3)) to the robot's configuration space.class BodyNodePoseVectorField : public VectorField { public: BodyNodePoseVectorField(MetaSkeletonStateSpacePtr _stateSpace, const BodyNode* _bodyNode); bool evaluatePoseVelocity(const SE3::State* _state, Eigen::VectorXd* _velocity) = 0; protected: // Defined in terms of evaluatePoseVelocity(). bool evaluateVelocity(const StateSpace::State* _state, Eigen::VectorXd* _velocity); };
-
MoveEndEffectorOffsetVectorField
andMoveEndEffectorPoseVectorField
are defined usingBodyNodePoseVectorField
.
/// \param[in] _bn Body node of end-effector | ||
ConfigurationSpaceVectorField( | ||
aikido::statespace::dart::MetaSkeletonStateSpacePtr _stateSpace, | ||
dart::dynamics::BodyNodePtr _bodyNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A generic vector field can be defined on any state space, not only MetaSkeletonStateSpace
s. Also, it is not necessarily parameterized by a BodyNode
. Those are implementation details of how some vector fields are implemented, e.g. those that move a BodyNode
along a constraint. I think we should replace MetaSkeletonStateSpace
with StateSpace
and remove all of the reference to BodyNode
s from this base class.
It would also be useful to introduce a subclass that handles the conversion from task space to configuration space. That class could take a BodyNodePtr
and handle the necessary Jacobian calculations to convert a twist in SE(3) to the tangent space of the MetaSkeleton
's configuration space.
|
||
/// This class defines a vector field in a configuration space | ||
/// | ||
/// Any vector field should inherit this class to implememnt functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: "implememnt" -> "implement"
/// | ||
/// Any vector field should inherit this class to implememnt functions | ||
/// for calculating joint velocities giving joint positions and time, | ||
/// and checking planning status. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know what "and time" means here. I think this should just say "joint positions."
To be pedantic, we shouldn't be referring to joints at all in this class. A vector field is defined over an abstract state space - it does not technically even have to be a MetaSkeletonStateSpace
!
/// | ||
/// \param[out] _qd Joint velocities | ||
/// \return Whether joint velocities are successfully computed | ||
virtual bool getJointVelocities(Eigen::VectorXd& _qd) const = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should take a StateSpace::State *
output parameter, not an Eigen::VectorXd
.
/// Vectorfield planning status callback function | ||
/// | ||
/// \return Status of planning | ||
virtual VectorFieldPlannerStatus checkPlanningStatus() const = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this function get it's input from? This function should take a const StateSpace::State *
as an argument at a minimum. Ideally, we would pass the entire trajectory that has already been integrated - but that would be significantly more difficult to implement.
double jointLimitTolerance = 3e-2, | ||
double optimizationTolerance = 1., | ||
double timelimit = 5.0, | ||
double integralTimeInterval = 10.0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helper function doesn't provide too much value over the functions that it wraps. I'd slightly prefer to remove function from Aikido and move it to libherb
, where we can specify good defaults for HERB.
/// in planning. | ||
/// \return A trajectory following the vector field. | ||
std::unique_ptr<aikido::trajectory::Spline> plan( | ||
double integrationTimeInterval, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The integration time parameter is meaningless (see my comment above), so it does not make sense to specifiy a limit here.
/// \return A trajectory following the vector field. | ||
std::unique_ptr<aikido::trajectory::Spline> plan( | ||
double integrationTimeInterval, | ||
double timelimit, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally prefer to use std::chrono
types to represent time durations. Consider using std::duration<T>
or - if you don't want to template this function - std::duration<double>
.
/// Define termination of vectorfield planner. | ||
// When VectorFieldTerminated is throws, it means | ||
// that planning is finished. | ||
class VectorFieldTerminated : public std::exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is an implementation detail - it is only necessary because we use an exception as a workaround to prematurely terminate the integration. This belongs in the .cpp
file or a detail
namespace.
/// \param[in] _cache_index Total cache index number | ||
/// \param[in] _stateSpace MetaSkeleton state space | ||
/// \param[in] knots A sequence of knots. | ||
/// \param[in] cache_index Total cache index number. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know what this means. If it's the length of the trajectory to keep, then I suggest removing the parameter entirely relying on the user to truncate knots
. E.g. they could call knots.resize(cache_index)
before calling this function
@mkoval How about the below design of classes to support vector field planner in arbitrary
Define an abstract vector field for any state space.
Define a
Define a
For example, define a
Correspondingly, we could have classes of
A
It seems that |
Changes include:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making those changes - I think we're converging towards a solid implementation. 😅 I think you are taking the correct approach by filing issues for difficult-to-implement features that we don't need immediately.
I am trying to focus only on issues that affect the public API and inheritance tree. These are the remaining major issues that I see:
-
Missing functions on
BodyNodePoseVectorField
. The intent of this class was to encapsulate the boilerplate required to convert a vector field over aBodyNode
's pose into configuration space. To do that, the class needs to implementevaluateVelocity()
andevaluateStatus()
in terms of equivalent functions that operate on theBodyNode
's pose.This change should significantly simplify the implementations of
MoveEndEffectorOffsetVectorField
andMoveEndEffectorPoseVectorField
. -
MetaSkeletonStateSpaceVectorField
is unnecessary because it doesn't seem to provide any functionality unless combined withBodyNodePoseVectorField
. If that is the case, we should merge that functionality directly intoBodyNodePoseVectorField
. -
MetaSkeletonStateSpaceVectorFieldIntegrator
is unnecessary** because all of the pure-virtual
functions onVectorFieldIntegrator
may be implemented for an arbitraryStateSpace
. This is nice because it means that the vast majority of the planner can operate on an arbitrary state space. -
Collision Free and Bounds Constraints. The vector field planner should operate on an opaque
Testable
constraint - it does not need access to individual collision and bounds constraints. If the goal is only to improve error messages, then that is something we can tackle using an approach similar to Testable Constraint Output #266. -
Incremental Constraint Validation. The current constraint validation logic validates the trajectory only after the integration has terminated. This is suboptimal because we won't "fail fast" if we quickly encounter a constraint violation. This also completely breaks the "move as far as possible" paradigm that we often use on HERB, e.g. to move straight as far as possible to contact the environment.
-
Incorrect Constraint Validation. The constraint validation currently discretizes the trajectory using an integration time resolution. This is incorrect because integration time is meaningless - we should be collision checking at a fixed DOF resolution. The correct implementation requires Implement collision checking for Spline trajectories #212, so I proposed a few short-term workarounds.
-
Documentation. It would be helpful to do a pass and update docstrings, particularly on classes that the user will have to interact with (e.g. by overriding functions in subclasses). A number of docstrings are missing or outdated pending the most recent refactors.
/// | ||
/// Any vector field should inherit this class to implememnt functions | ||
/// for calculating joint velocities giving joint positions and time, | ||
/// and checking planning status. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This description is no longer correct. It should say something like this:
This class is a vector field over a
MetaSkeletonStateSpace
that is defined in terms of a Cartesian vector field over the pose of aBodyNode
in thatMetaSkeleton
. The Jacobian pseudoinverse is used to map velocities in se(3) to velocities in configuration space. If multiple solutions exist, i.e. the kinematic chain leading to theBodyNode
in theMetaSkeleton
is redundant, then a heuristic is used to select velocities from the null space to avoid violating joint limits.
/// \param[in] initialStepSize Initial step size in integation. | ||
MetaSkeletonStateSpaceVectorFieldIntegrator( | ||
const MetaSkeletonStateSpaceVectorFieldPtr vectorField, | ||
const aikido::constraint::TestablePtr constraint, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: These arguments are passed by value, so it is meaningless to flag them as const
.
// Documentation inherited. | ||
bool evaluateTrajectory( | ||
const aikido::trajectory::Trajectory* trajectory, | ||
const aikido::constraint::TestablePtr collisionFreeConstraint, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nits:
- This function does not support
trajectory
beingnullptr
, so it would be cleaner to take that argumentconst &
instead of aconst *
. - This function does not take ownership of
collisionFreeConstraint
, it would be cleaner to take that argument byconst &
instead of ashared_ptr
. - This
collisionFreeConstraint
is currently passed by value, so it is meaningless to flag it is asconst
. - The name
collisionFreeConstraint
is misleading because it can be any constraint, not just aCollisionFree
constraint.
/// than distance. | ||
/// (2) When maxDistance is not set to InvalidMaxDistance, | ||
/// planning will terminate and return a path, the path length | ||
/// of which is [distance, maxDistance]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand this correctly, it may be simpler to say that:
If not specified,
maxDistance
defaults todistance
.
double angularTolerance = 0.15, | ||
double linearVelocityGain = 1.0, | ||
double initialStepSize = 5e-2, | ||
double jointLimitPadding = 3e-2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest moving all of these defaults to libherb
since they are highly robot specific.
/// Any vector field should inherit this class to implememnt functions | ||
/// for calculating joint velocities giving joint positions and time, | ||
/// and checking planning status. | ||
class MetaSkeletonStateSpaceVectorField : public VectorField |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this class is necessary - it doesn't seem to provide any value over VectorField
itself. I think we can safely roll all of this functionality into BodyNodePoseVectorField
.
// Documentation inherited. | ||
virtual bool evaluateVelocity( | ||
const aikido::statespace::StateSpace::State* state, | ||
Eigen::VectorXd& qd) const override = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was expecting this class to define evaluateVelocity()
in terms of another function that returns the Cartesian velocity of the specified BodyNode
. E.g.:
bool BodyNodePoseVectorField::evaluateVelocity(
const aikido::statespace::StateSpace::State* state,
Eigen::VectorXd& qd) const override = 0) override
{
// Pseudocode. Actual implementation will require some massaging of data types.
const auto cartesianState = mBodyNode->getPose(state);
Eigen::Vector6d cartesianVelocity;
if (!evaluateCartesianVelocity(cartesianState, cartesianVelocity))
return false;
return computeJointVelocityFromTwist(mBodyNode, cartesianVelocity, &qd);
}
// Implemented by the user:
virtual bool BodyNodePoseVectorField::evaluateCartesianVelocity(
const Eigen::Isometry3d& _pose, Eigen::Vector6d& _cartesianVelocity) = 0;
In my opinion, the whole point of this class is to allow the user to define a vector field over the pose of a BodyNode
without worrying about the boilerplate required to call `computeJointVelocityFromTwist().
|
||
// Documentation inherited. | ||
virtual VectorFieldPlannerStatus evaluateStatus( | ||
const aikido::statespace::StateSpace::State* state) const override = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was expecting this class to define evaluateStatus()
in terms of another evaluateCartesianStatus()
function that accepts the pose of the BodyNode
. See my comment on evaluateVelocity()
for details.
== mMetaSkeleton->getVelocityLowerLimits()) | ||
{ | ||
throw std::invalid_argument("Velocity space volume zero"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think these checks are necessary. Bounds should be handled entirely by the Testable
passed by the user.
/// This class defines two callback functions for a integrator. | ||
/// step() provides joint velocities the vector field planner should follow, | ||
/// check() is called after each integration step to check planner status. | ||
class MetaSkeletonStateSpaceVectorFieldIntegrator : public VectorFieldIntegrator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure why this class is necessary. I think we can safely delete this once we implement convertStateToPositions()
and convertPositionsToState()
in the base VectorField
class.
TODO:
This will be updated after #212 is merged. The current solution is adding a user-defined check resolution in |
using aikido::constraint::createTestableBounds; | ||
auto boundConstraint = createTestableBounds(mMetaSkeletonStateSpace); | ||
for (double t = trajectory.getStartTime(); t <= trajectory.getEndTime(); | ||
t += evalStepSize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two issues:
- This will not evaluate the endpoint of the trajectory if
getEndTime()
is not an integral multiple ofevalStepTime
. - This will reevaluate the trajectory from the start each call. This function should take the start time for
t
to avoid re-evaluating that part of the trajectory.
for (double t = trajectory.getStartTime(); t <= trajectory.getEndTime(); | ||
t += evalStepSize) | ||
{ | ||
auto state = mMetaSkeletonStateSpace->createState(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I suggest moving this outside the loop, so we don't do unnecessary state allocations.
if (!constraint->isSatisfied(state)) | ||
{ | ||
throw ConstraintViolatedError(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my previous comments. boundConstraint
and constraint
should be merged. The user can pass a CompoundTestable
if they wish to check more than one constraint.
|
||
namespace aikido { | ||
namespace planner { | ||
namespace vectorfield { | ||
|
||
//============================================================================== | ||
double MoveEndEffectorOffsetVectorField::InvalidMaxDistance | ||
= std::numeric_limits<double>::infinity(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
infinity()
seems like a perfectly valid value for the maximum distance if there is no limit. We commonly use this in PrPy when planning "move-until-touch" actions. I suggest using a negative value instead.
Eigen::Vector6d desiredTwist = computeGeodesicTwist(pose, mStartPose); | ||
desiredTwist.tail<3>() = mDirection * mLinearVelocityGain; | ||
cartesianVelocity = desiredTwist; | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems quite a bit different than the logic that it replaced. A few key differences: (1) the linear proportional controller is not additive and (2) there is no angular proportional controller. Were these changes intended?
/// | ||
/// \param[in] fromTrans Current transformation. | ||
/// \param[in] toTrans Goal transformation. | ||
/// \return Geodesic twist. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document the convention about how to interpret the elements of this vector. Which elements represent linear versus angular velocities? What coordinate frame is this expressed in?
/// | ||
/// \param[in] fromTrans Current transformation. | ||
/// \param[in] toTrans Goal transformation. | ||
/// \return Geodesic error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document the convention about how to interpret the elements of this vector. Which elements represent linear versus angular velocities? What coordinate frame is this expressed in?
/// \param[in] fromTrans Current transformation. | ||
/// \param[in] toTrans Goal transformation. | ||
/// \param[in] r In units of meters/radians converts radians to meters. | ||
/// \return Geodesic distance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the units on the return value?
/// \param[in] jointVelocityUpperLimits Joint velocity upper bounds. | ||
/// \param[in] jointVelocityLimited Whether joint velocity bounds are | ||
/// considered in optimization. | ||
/// \param[in] stepSize Step size in second. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this used for? Can we remove it?
/// \param[in] jointLimitPadding If less then this distance to joint | ||
/// limit, velocity is bounded in that direction to 0. | ||
/// \param[in] jointVelocityLowerLimits Joint velocity lower bounds. | ||
/// \param[in] jointVelocityUpperLimits Joint velocity upper bounds. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These arguments should only be required if jointVelocityLimited
is true
. Are they ignored otherwise?
Instead of having a jointVelocityLimitReached
flag, consider passing those arguments by const *
and ignoring the velocity limits if they are nullptr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great. Very nice work @dqyi11 - thank you for putting up with all of my comments. Bonus points for updating the docstrings - they help a lot when trying to understand how all the pieces fit together.
At this point, all of the major issues have been addressed. I left a handful of minor comments, most of which just just involve documenting limitations of the current implementation, so we remember to come back and fix them later.
Once those comments are addressed, I am happy to merge this. 👍
/// Evaluate Cartesian velocity by current pose of body node. | ||
/// | ||
/// \param[in] pose Current pose of body node. | ||
/// \param[out] cartesianVelocity Cartesian velocity defined in SE(3). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pedantic Nit: Velocities live in se(3)
, not SE(3)
.
/// could be found. | ||
/// \param[in] positionTolerance Constraint tolerance in meters. | ||
/// \param[in] angularTolerance Constraint tolerance in radians. | ||
/// \param[in] initialStepSize Initial step size. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarify that this is the maximum step size used to guarantee that the integrator does not step out of joint limits, not the initial step size.
double r, | ||
double linearVelocityGain, | ||
double angularVelocityGain, | ||
double initialStepSize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment on BodyNodePoseVectorField
. This is a confusing parameter.
|
||
/// Angular velocit gain. | ||
double mAngularVelocityGain; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This object contains a Isometry3d
member variable, which is a fixed-size vectorizable type. Therefore, we should flag this class with EIGEN_MAKE_ALIGNED_OPERATOR_NEW
.
|
||
protected: | ||
/// Goal pose. | ||
Eigen::Isometry3d mGoalPose; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This object contains a Isometry3d
member variable, which is a fixed-size vectorizable type. Therefore, we should flag this class with EIGEN_MAKE_ALIGNED_OPERATOR_NEW
.
{ | ||
throw VectorFieldTerminated("State not in constraint."); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constraint validation logic is correct, but will result in a lot of extra constraint validation checks if the the integration step size is significantly smaller than the collision checking resolution. This is common if the robot is near a singularity because the ODE is relatively stiff.
A better way to implement this is to maintain the time mLastEvaluation
at which the last constraint validation check was performed. In this function, we could collision check the trajectory from mLastEvaluation
to t
excluding the endpoint, then update mLastEvaluation = t
. Finally, when planning terminates, we need to check the unevaluated portion [mLastEvaluation, getEndTime()]
of the trajectory.
I think this is fine for now - this doesn't affect correctness. If you don't want to make this change now, then just file an issue so it doesn't slip through the cracks. 😉
trajEvalStartTime = mConstraintCheckResolution; | ||
} | ||
if (!mVectorField->evaluateTrajectory( | ||
*trajSegment.get(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This is equivalent to *trajSegment
.
} | ||
} | ||
mKnots.push_back(knot); | ||
mIndex += 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like mIndex
always equals mKnots.size()
. Is the variable necessary? 🤔
/// distance. | ||
/// \param[in] linearVelocityGain Linear velocity gain in workspace. | ||
/// \param[in] angularVelocityGain Angular velocity gain in workspace. | ||
/// \param[in] initialStepSize Initial step size. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarify that this is the maximum step size used to guarantee that the integrator does not step out of joint limits, not the initial step size.
mConstraintCheckResolution, | ||
trajEvalStartTime)) | ||
{ | ||
throw VectorFieldTerminated("State not in constraint."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a ConstraintViolatedError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// \param[in] maxStepSize The maximum step size used to guarantee | ||
/// that the integrator does not step out of joint limits. | ||
/// \param[in] jointLimitpadding If less then this distance to joint | ||
/// limit, velocity is bounded in that direction to 0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Missing docstring for the enforceJointVelocityLimits
flag. I'd also suggest we make that flag default to false
, since we typically use the vector field planner as a kinematic planner.
/// BodyNode | ||
dart::dynamics::BodyNodePtr mBodyNode; | ||
|
||
/// Initial step size of integrator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This comment should be updated to reflect the fact that this is a maximum step size.
return result; | ||
Eigen::Vector6d desiredTwist = computeGeodesicTwist(pose, mStartPose); | ||
desiredTwist.tail<3>() = mDirection; | ||
cartesianVelocity = desiredTwist; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that makes sense. 👍 Thanks for the explanation.
throw std::invalid_argument(ss.str()); | ||
} | ||
if (skeleton->getVelocityLowerLimit(i) > skeleton->getVelocityUpperLimit(i)) | ||
// dtwarn << e.what() << std::endl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove dead code.
* new vector field planner and added move end effector pose vector field * add move to end effector pose test cases * refactor code and format * add new tests to plan to end effector pose * add functions of enabling collision checking and dof limit checking * fix doc strings * add velocity gain to support controlling in complex scenarios * add new class for collision error * address JS's comments * address JS's comments and add doc strings for some member variables * Fix const-correctness in ConfigurationSpaceVectorField * Fix code style * fix doc string style * Add invalid max distance variable * update cpde style * fix typos * address Mike's comments * add parameter for constraint check resolution * add doc strings, moved implementation details, add plannerStatus as output * address Mike's comments * update for formatting * address Mike's comments * address Mike's comments * change to use TestableIntersection * fix doc strings * add changelog.md
This PR address the enhancement #267 .
The changes include
ConfigurationSpaceVectorField
is created.MoveEndEffectorOffsetVectorField
is addedMoveEndEffectorPoseVectorField
is addedVectorFieldPlanner
class is createdboost::numeric::odeint::integrate_adaptive
to integrate by adaptive step sizeplanToEndEffectorPose
planToEndEffectorPose
TODO
These will be added in another PR.
Collision checking of a trajectory (or segment) will be added (using sampling based method in
prpy
)MoveEndEffectorAlongWorkspacePathVectorField
will be addedplanWorkspacePath
will be addedDocument new methods and classes
Format code with
make format
Before merging a pull request
CHANGELOG.md