Skip to content
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

Merged
merged 22 commits into from Mar 1, 2023

Conversation

fantaosha
Copy link
Contributor

Motivation and Context

How Has This Been Tested

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have completed my CLA (see CONTRIBUTING)
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@fantaosha fantaosha added the enhancement New feature or request label Jan 3, 2023
@fantaosha fantaosha self-assigned this Jan 3, 2023
@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jan 3, 2023
@fantaosha fantaosha changed the base branch from taoshaf.robot.prismatic_joint to taoshaf.robot.robot January 3, 2023 02:15
Copy link
Contributor

@cpaxton cpaxton left a 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):
Copy link
Contributor

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?

Copy link
Contributor Author

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):
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@@ -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]):
Copy link
Contributor

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?

Copy link
Contributor Author

@fantaosha fantaosha Jan 6, 2023

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

theseus/embodied/robot/link.py Outdated Show resolved Hide resolved
theseus/embodied/robot/forward_kinematics.py Show resolved Hide resolved
theseus/embodied/robot/forward_kinematics.py Show resolved Hide resolved
@mhmukadam mhmukadam mentioned this pull request Jan 23, 2023
11 tasks
@fantaosha fantaosha force-pushed the taoshaf.robot.forward_kinematics branch from 928fb81 to 1be2940 Compare January 23, 2023 22:11
Copy link
Contributor

@luisenp luisenp left a 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):
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ->Any OK?

Copy link
Contributor

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?

tests/embodied/robot/test_forward_kinematics.py Outdated Show resolved Hide resolved
@@ -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]):
Copy link
Contributor

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.

angle_ids = (
link.parent.parent.angle_ids
if isinstance(link.parent, FixedJoint)
else link.parent.parent.angle_ids + [link.parent.id]
Copy link
Contributor

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.

Copy link
Contributor

@exhaustin exhaustin Jan 31, 2023

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.

Copy link
Contributor Author

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make a Github issue?

Copy link
Contributor Author

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"
Copy link
Contributor

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."?

Copy link
Contributor Author

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.

Copy link
Contributor

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)
Copy link
Contributor

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).

Copy link
Contributor Author

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.

Copy link
Contributor

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)

Copy link
Contributor Author

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.

theseus/embodied/robot/forward_kinematics.py Show resolved Hide resolved


def get_forward_kinematics(robot: Robot, link_names: Optional[List[str]] = None):
ForwardKinematics, _, jforward_kinematics, _, _ = ForwardKinematicsFactory(
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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 Show resolved Hide resolved
theseus/embodied/robot/joint.py Show resolved Hide resolved
angle_ids = (
link.parent.parent.angle_ids
if isinstance(link.parent, FixedJoint)
else link.parent.parent.angle_ids + [link.parent.id]
Copy link
Contributor

@exhaustin exhaustin Jan 31, 2023

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.

@@ -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]):
Copy link
Contributor

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.

@@ -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]):
Copy link
Contributor

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)))
Copy link
Contributor

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)

Copy link
Contributor Author

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 and link.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 by robot.joints[joint_id].name.

Comment on lines +76 to +78
jposes[:, :, joint.id : joint.id + 1] = (
se3.adjoint(poses[link.id]) @ joint.axis
)
Copy link
Contributor

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.

Copy link
Contributor Author

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.

fantaosha and others added 3 commits February 28, 2023 14:10
* add device to robot model

* Refactor Joint.dof as a property (#423)

* refactor joint dof

* add virtual end-effector for tests
@fantaosha fantaosha merged commit 765fcb5 into taoshaf.robot.robot Mar 1, 2023
@fantaosha fantaosha deleted the taoshaf.robot.forward_kinematics branch March 1, 2023 18:49
fantaosha added a commit that referenced this pull request Mar 1, 2023
* 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
fantaosha added a commit that referenced this pull request Mar 1, 2023
* 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
fantaosha added a commit that referenced this pull request Mar 8, 2023
* 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
fantaosha added a commit that referenced this pull request Mar 10, 2023
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants