-
Notifications
You must be signed in to change notification settings - Fork 127
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
Add differentiable forward kinematics #420
Add differentiable forward kinematics #420
Conversation
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.
lgtm in general, although i am a bit confused about some things
# TODO: Add support for joints with DOF>1 | ||
|
||
|
||
def ForwardKinematicsFactory(robot: Robot, link_names: Optional[List[str]] = None): |
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 set this up as a factory?
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 outputs are forward kinematics for the given links of a specific robot. In some problems, we don't have to run the full forward kinematics if only interested in a subset of links.
|
||
class ForwardKinematics(torch.autograd.Function): | ||
@classmethod | ||
def forward(cls, ctx, angles): |
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 ctx? maybe a comment?
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.
ctx
is used in PyTorch for analytic backward propagation. Here we present analytical backward function for forward kinematics, which yields 10x speedup.
theseus/embodied/robot/link.py
Outdated
@@ -60,6 +65,9 @@ def set_children(self, children: List[Any]): | |||
def set_ancestors(self, ancesotrs: List["Link"]): | |||
self._ancestors = ancesotrs | |||
|
|||
def set_angle_ids(self, angle_ids: List[int]): |
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 supposed to do?
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 the ids of non-fixed joints that a link depends on. For example, a 7-link robot arm, the second link only depends on the first and second joints. This is used to improve the computational efficiency of jacobians, in particular, for robot with branched chain topology.
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 should have a more explicit/descriptive name for this than angle_ids
.
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.
Compared to tracing manually back to the root, the computational complexity isn't really affected by caching the dependent joints, since you still need linear time to go through them.
Also, performance shouldn't be an issue since you have a factory pattern to generate an object that has all of the computational paths traced.
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 should have a more explicit/descriptive name for this than
angle_ids
.
To add to this, not all joint states are angles.
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 should have a more explicit/descriptive name for this than
angle_ids
.
Now angle_ids -> ancestor_active_joint_ids
.
6ca688b
to
19a5326
Compare
928fb81
to
1be2940
Compare
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.
Great job adding all this stuff! I left some comments to address.
# TODO: Add support for joints with DOF>1 | ||
|
||
|
||
def ForwardKinematicsFactory(robot: Robot, link_names: Optional[List[str]] = None): |
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 return type annotations for 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.
Is ->Any
OK?
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 Any
? What possible options can this return?
theseus/embodied/robot/link.py
Outdated
@@ -60,6 +65,9 @@ def set_children(self, children: List[Any]): | |||
def set_ancestors(self, ancesotrs: List["Link"]): | |||
self._ancestors = ancesotrs | |||
|
|||
def set_angle_ids(self, angle_ids: List[int]): |
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 should have a more explicit/descriptive name for this than angle_ids
.
theseus/embodied/robot/robot.py
Outdated
angle_ids = ( | ||
link.parent.parent.angle_ids | ||
if isinstance(link.parent, FixedJoint) | ||
else link.parent.parent.angle_ids + [link.parent.id] |
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.
Let's add a property to Link
class called something like parent_link
, which returns parent.parent
. It will be easier to read.
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 I agree that parent.parent
is rather misleading as it looks like you're traversing two links up the kinematic tree. Having something like parent_link
, child_links
will increase the readability of the code.
Or, refer to #417 (comment) in the previous PR for an alternative way to connect Links and Joints in the tree data structure.
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.
Added Link.parent_link
, and renamed some properties of a link, i.e., Link.parent->Link.parent_joint
, Link.children->Link.child_joints
, Link.angle_ids -> Link.ancestor_active_joint_ids
, etc.
from .joint import Joint | ||
|
||
|
||
# TODO: Add support for joints with DOF>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.
Make a Github issue?
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.
def _forward_kinematics_helper(angles: torch.Tensor): | ||
if angles.ndim != 2 or angles.shape[1] != robot.dof: | ||
raise ValueError( | ||
f"Joint angles for {robot.name} should be {robot.dof}-D vectors" |
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.
Maybe add "{robot.dof}-D vectors with a batch dimension."?
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.
Not sure. If I remember correctly, we only consider the second dimension in our Lie group code.
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 just suggesting a more descriptive error message for what your code is already doing (you are checking ndim == 2 and dof being in dim 1). Your current message implies that ndim
should be ==1
.
) | ||
|
||
poses: List[Optional[torch.Tensor]] = [None] * robot.num_links | ||
poses[0] = angles.new_zeros(angles.shape[0], 3, 4) |
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.
Replace with se3.new_identity(angles)
.
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.
Maybe we need more discussions about this the behavior of se3.new_identity()
. For example, how to specify the dimensions.
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.
Is there any reasonable option other than the code below?
g = se3.new_identity(batch_size)
# g.shape = (batch_size, 3, 4)
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 liegroup
has no new_identity()
method. This is definitely in our TODO list and I will go back to this once new_identity()
is added to Lie group.
|
||
|
||
def get_forward_kinematics(robot: Robot, link_names: Optional[List[str]] = None): | ||
ForwardKinematics, _, jforward_kinematics, _, _ = ForwardKinematicsFactory( |
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.
Rename output as ForwardKinematics
--> fk_autograd_fn_cls
?
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.
Is there anything problematic about this suggestion? CamelCase
is usually used for class definitions.
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 OK for either. I will address this comment after all the PRs have been merged since lots of functions use ForwardKinematics
.
theseus/embodied/robot/robot.py
Outdated
angle_ids = ( | ||
link.parent.parent.angle_ids | ||
if isinstance(link.parent, FixedJoint) | ||
else link.parent.parent.angle_ids + [link.parent.id] |
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 I agree that parent.parent
is rather misleading as it looks like you're traversing two links up the kinematic tree. Having something like parent_link
, child_links
will increase the readability of the code.
Or, refer to #417 (comment) in the previous PR for an alternative way to connect Links and Joints in the tree data structure.
theseus/embodied/robot/link.py
Outdated
@@ -60,6 +65,9 @@ def set_children(self, children: List[Any]): | |||
def set_ancestors(self, ancesotrs: List["Link"]): | |||
self._ancestors = ancesotrs | |||
|
|||
def set_angle_ids(self, angle_ids: List[int]): |
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.
Compared to tracing manually back to the root, the computational complexity isn't really affected by caching the dependent joints, since you still need linear time to go through them.
Also, performance shouldn't be an issue since you have a factory pattern to generate an object that has all of the computational paths traced.
theseus/embodied/robot/link.py
Outdated
@@ -60,6 +65,9 @@ def set_children(self, children: List[Any]): | |||
def set_ancestors(self, ancesotrs: List["Link"]): | |||
self._ancestors = ancesotrs | |||
|
|||
def set_angle_ids(self, angle_ids: List[int]): |
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 should have a more explicit/descriptive name for this than
angle_ids
.
To add to this, not all joint states are angles.
ancestors += [anc for anc in link.ancestors] | ||
joint_ids += link.angle_ids | ||
pose_ids = sorted(list(set([anc.id for anc in ancestors] + link_ids))) | ||
joint_ids = sorted(list(set(joint_ids))) |
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.
Same comment as #417 (comment)
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.
- Yeah, I will refactor the code with
link.joint
andlink.parent
. - I think keeping the ancestor ids is important in lots of applications. This makes it possible to get the sparsity structure when the robot model is used in collision.
- We have
robot.joints
and the users get the joint names byrobot.joints[joint_id].name
.
jposes[:, :, joint.id : joint.id + 1] = ( | ||
se3.adjoint(poses[link.id]) @ joint.axis | ||
) |
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.
Correct me if I'm wrong, but this line will only be correct if the joints are revolute joints?
I propose we implement joint jacobians within the Joint object, as mentioned in #415 (comment).
Thus, this Jacobian function will not only be general across different joint types, but will also be more readable.
Happy to discuss offline.
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 code applies all types of joints, including both revolute and prismatic.
* add device to robot model * Refactor Joint.dof as a property (#423) * refactor joint dof * add virtual end-effector for tests
* add link * add id to joint * add id to link * add robot model * add name to robot * backup * refactor joint * refactor link * add robot model * robot model works * a minior refactor of joint * add ancestor to links * update dof computation * add forward kinematics function * Add differentiable forward kinematics (#420) * fixed a bug in FixedJoint * add angle_ids * add jacobians to FK * forward kinematics works * rename FK * add tests for differentiable kinematics * rename tests * simplify the computation of origin * add jacobian tests for forward kinematics * fixed a bug for vectorize=True in SE3 * improve the efficiency of forward kinematics * add tests for backward propagation * update lie group load paths * Add device to robot model (#422) * add device to robot model * Refactor Joint.dof as a property (#423) * refactor joint dof * add virtual end-effector for tests * seems to work after name changes * rename parent->parent_link and child->child_link * rename angle_ids -> ancestor_active_joint_ids * refactor tests with torch.testing.assert_close * small refactoring of the code * add Robot.get_links() * remove some setters for link and joint * made some functions private for encapsulation * consolidate if block and classmethod->staticmethod * refactor set_robot_from_urdf_file * rename ancestor_active_joint_ids -> ancestor_non_fixed_joint_ids * id=-1 -> id=None * refactor the code for simplicity * move link into joint
* add prismatic joint * refactor prismatic joint * Add robot model (#417) * add link * add id to joint * add id to link * add robot model * add name to robot * backup * refactor joint * refactor link * add robot model * robot model works * a minior refactor of joint * add ancestor to links * update dof computation * add forward kinematics function * Add differentiable forward kinematics (#420) * fixed a bug in FixedJoint * add angle_ids * add jacobians to FK * forward kinematics works * rename FK * add tests for differentiable kinematics * rename tests * simplify the computation of origin * add jacobian tests for forward kinematics * fixed a bug for vectorize=True in SE3 * improve the efficiency of forward kinematics * add tests for backward propagation * update lie group load paths * Add device to robot model (#422) * add device to robot model * Refactor Joint.dof as a property (#423) * refactor joint dof * add virtual end-effector for tests * seems to work after name changes * rename parent->parent_link and child->child_link * rename angle_ids -> ancestor_active_joint_ids * refactor tests with torch.testing.assert_close * small refactoring of the code * add Robot.get_links() * remove some setters for link and joint * made some functions private for encapsulation * consolidate if block and classmethod->staticmethod * refactor set_robot_from_urdf_file * rename ancestor_active_joint_ids -> ancestor_non_fixed_joint_ids * id=-1 -> id=None * refactor the code for simplicity * move link into joint
* add prismatic joint * refactor prismatic joint * Add robot model (#417) * add link * add id to joint * add id to link * add robot model * add name to robot * backup * refactor joint * refactor link * add robot model * robot model works * a minior refactor of joint * add ancestor to links * update dof computation * add forward kinematics function * Add differentiable forward kinematics (#420) * fixed a bug in FixedJoint * add angle_ids * add jacobians to FK * forward kinematics works * rename FK * add tests for differentiable kinematics * rename tests * simplify the computation of origin * add jacobian tests for forward kinematics * fixed a bug for vectorize=True in SE3 * improve the efficiency of forward kinematics * add tests for backward propagation * update lie group load paths * Add device to robot model (#422) * add device to robot model * Refactor Joint.dof as a property (#423) * refactor joint dof * add virtual end-effector for tests * seems to work after name changes * rename parent->parent_link and child->child_link * rename angle_ids -> ancestor_active_joint_ids * refactor tests with torch.testing.assert_close * small refactoring of the code * add Robot.get_links() * remove some setters for link and joint * made some functions private for encapsulation * consolidate if block and classmethod->staticmethod * refactor set_robot_from_urdf_file * rename ancestor_active_joint_ids -> ancestor_non_fixed_joint_ids * id=-1 -> id=None * refactor the code for simplicity * move link into joint
* add revolute joint * refactor revolute joint * add parent and child to joint * update the path to load so3 and se3 * Add prismatic joint (#416) * add prismatic joint * refactor prismatic joint * Add robot model (#417) * add link * add id to joint * add id to link * add robot model * add name to robot * backup * refactor joint * refactor link * add robot model * robot model works * a minior refactor of joint * add ancestor to links * update dof computation * add forward kinematics function * Add differentiable forward kinematics (#420) * fixed a bug in FixedJoint * add angle_ids * add jacobians to FK * forward kinematics works * rename FK * add tests for differentiable kinematics * rename tests * simplify the computation of origin * add jacobian tests for forward kinematics * fixed a bug for vectorize=True in SE3 * improve the efficiency of forward kinematics * add tests for backward propagation * update lie group load paths * Add device to robot model (#422) * Refactor Joint.dof as a property (#423) * refactor joint dof * add virtual end-effector for tests * seems to work after name changes * rename parent->parent_link and child->child_link * rename angle_ids -> ancestor_active_joint_ids * refactor tests with torch.testing.assert_close * small refactoring of the code * add Robot.get_links() * remove some setters for link and joint * made some functions private for encapsulation * consolidate if block and classmethod->staticmethod * refactor set_robot_from_urdf_file * rename ancestor_active_joint_ids -> ancestor_non_fixed_joint_ids * id=-1 -> id=None * refactor the code for simplicity * move link into joint * simplify the code * minor revision to be consistent with Lie group refatcoring * fixed a CI error * move files to labs
Motivation and Context
How Has This Been Tested
Types of changes
Checklist