Skip to content

Commit e611909

Browse files
committed
docs
1 parent b034c00 commit e611909

File tree

7 files changed

+154
-42
lines changed

7 files changed

+154
-42
lines changed

ik_solvers/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Inverse Kinematics Solvers
2+
3+
This package implements inverse kinematics solvers for an underwater vehicle
4+
manipulator system. The solvers are designed to be used in conjunction with
5+
the whole_body_controllers/IKController.
6+
7+
## Closed-Loop Task Priority Inverse Kinematics Solver
8+
9+
A singularity-robust inverse kinematics solver proposed by Moe, et al. [^1].
10+
The solver implements joint limit constraints (using the joint limits defined
11+
in the robot description) and end-effector pose tracking. The Jacobian
12+
pseudoinverse is calculated using the damped least squares method.
13+
14+
[^1]: S. Moe, G. Antonelli, A. R. Teel, K. Y. Pettersen, and J. Schrimpf,
15+
"Set-Based Tasks within the Singularity-Robust Multiple Task-Priority Inverse
16+
Kinematics Framework: General Formulation, Stability Analysis, and Experimental
17+
Results", in *Frontiers in Robotics and AI*, 2016.
18+
19+
### Plugin Library
20+
21+
ik_solvers/task_priority_solver

ik_solvers/include/ik_solvers/task_priority_solver.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Constraint
4747
: primal_(primal),
4848
constraint_(constraint),
4949
priority_(priority),
50-
gain_(gain) {};
50+
gain_(gain){};
5151

5252
/// Destructor.
5353
virtual ~Constraint() = default;

ik_solvers/src/pseudoinverse.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include "pseudoinverse.hpp"
2+
3+
#include <cmath>
4+
5+
namespace ik_solvers::pinv
6+
{
7+
8+
auto least_squares(const Eigen::MatrixXd & jac) -> Eigen::MatrixXd
9+
{
10+
// we don't need to do much here - this is mostly a convenience function to make it easier to try out alternative
11+
// pseudoinverse methods in the solvers
12+
return jac.completeOrthogonalDecomposition().pseudoInverse();
13+
}
14+
15+
auto damped_least_squares(const Eigen::MatrixXd & jac, const Eigen::VectorXd & damping) -> Eigen::MatrixXd
16+
{
17+
if (damping.size() != jac.rows()) {
18+
throw std::invalid_argument("Damping vector must have the same size as the number of rows in the Jacobian.");
19+
}
20+
return jac.transpose() * ((jac * jac.transpose()) + damping.asDiagonal().toDenseMatrix()).inverse();
21+
}
22+
23+
auto damped_least_squares(const Eigen::MatrixXd & jac, double damping) -> Eigen::MatrixXd
24+
{
25+
return damped_least_squares(jac, damping * Eigen::MatrixXd::Identity(jac.rows(), jac.rows()));
26+
}
27+
28+
} // namespace ik_solvers::pinv

ik_solvers/src/pseudoinverse.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2025, Evan Palmer
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all
11+
// copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
// SOFTWARE.
20+
21+
#include <Eigen/Dense>
22+
23+
#include "rclcpp/rclcpp.hpp"
24+
25+
namespace ik_solvers::pinv
26+
{
27+
28+
/// Calculate the pseudoinverse of a Jacobian matrix using the Moore-Penrose method.
29+
auto least_squares(const Eigen::MatrixXd & jac) -> Eigen::MatrixXd;
30+
31+
/// Calculate the pseudoinverse of a Jacobian matrix using the damped least squares method.
32+
///
33+
/// This version allows for more granular selection of the damping values to tune the solution velocities.
34+
auto damped_least_squares(const Eigen::MatrixXd & jac, const Eigen::VectorXd & damping) -> Eigen::MatrixXd;
35+
36+
/// Calculate the pseudoinverse of a Jacobian matrix using the damped least squares method.
37+
auto damped_least_squares(const Eigen::MatrixXd & jac, double damping) -> Eigen::MatrixXd;
38+
39+
} // namespace ik_solvers::pinv

ik_solvers/src/task_priority_solver.cpp

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "pinocchio/algorithm/frames.hpp"
2929
#include "pinocchio/algorithm/jacobian.hpp"
3030
#include "pinocchio/algorithm/kinematics.hpp"
31+
#include "pseudoinverse.hpp"
3132
#include "tf2_eigen/tf2_eigen.hpp"
3233

3334
namespace ik_solvers
@@ -173,7 +174,7 @@ namespace
173174
auto compute_nullspace(const Eigen::MatrixXd & augmented_jacobian) -> Eigen::MatrixXd
174175
{
175176
const Eigen::MatrixXd eye = Eigen::MatrixXd::Identity(augmented_jacobian.cols(), augmented_jacobian.cols());
176-
return eye - (augmented_jacobian.completeOrthogonalDecomposition().pseudoInverse() * augmented_jacobian);
177+
return eye - (pinv::least_squares(augmented_jacobian) * augmented_jacobian);
177178
}
178179

179180
/// Construct the augmented Jacobian matrix from a list of Jacobian matrices.
@@ -198,42 +199,37 @@ auto construct_augmented_jacobian(const std::vector<Eigen::MatrixXd> & jacobians
198199
return augmented_jacobian;
199200
}
200201

201-
/// Closed-loop task priority IK using the damped pseudoinverse.
202+
/// Closed-loop task priority IK.
202203
auto tpik(const hierarchy::ConstraintSet & tasks, size_t nv, double damping)
203204
-> std::expected<Eigen::VectorXd, SolverError>
204205
{
205206
if (tasks.empty()) {
206207
return std::unexpected(SolverError::NO_SOLUTION);
207208
}
208209

209-
Eigen::VectorXd vel = Eigen::VectorXd::Zero(nv);
210210
std::vector<Eigen::MatrixXd> jacobians;
211211
jacobians.reserve(tasks.size());
212+
213+
Eigen::VectorXd vel = Eigen::VectorXd::Zero(nv);
212214
Eigen::MatrixXd nullspace = Eigen::MatrixXd::Identity(nv, nv);
213215

214216
for (const auto & task : tasks) {
215-
// damped pseudoinverse of the Jacobian
216-
const Eigen::MatrixXd J = task->jacobian();
217-
const auto eye = Eigen::MatrixXd::Identity(J.rows(), J.rows());
218-
const Eigen::MatrixXd J_inv = J.transpose() * (J * J.transpose() + damping * eye).inverse();
219-
220-
// closed-loop task priority IK
217+
const Eigen::MatrixXd J_inv = pinv::damped_least_squares(task->jacobian(), damping);
221218
vel += nullspace * J_inv * (task->gain() * task->error());
222219

223-
jacobians.push_back(J);
220+
jacobians.push_back(task->jacobian());
224221
nullspace = compute_nullspace(construct_augmented_jacobian(jacobians));
225222
}
226223

227224
return vel;
228225
}
229226

230-
/// Check if the solution is feasible with respect to the set constraints.
227+
/// Check if the solution is feasible.
231228
auto is_feasible(const hierarchy::ConstraintSet & constraints, const Eigen::VectorXd & solution) -> bool
232229
{
233230
for (const auto & constraint : constraints) {
234231
auto set_task = std::dynamic_pointer_cast<hierarchy::SetConstraint>(constraint);
235232
const double pred = (constraint->jacobian() * solution).value();
236-
237233
if (!((set_task->primal() > set_task->lower_threshold() && pred < 0) ||
238234
(set_task->primal() < set_task->upper_threshold() && pred > 0))) {
239235
return false;
@@ -253,39 +249,32 @@ auto search_solutions(
253249
return std::unexpected(SolverError::NO_SOLUTION);
254250
}
255251

256-
Eigen::VectorXd solution;
257-
252+
// there is only one solution if there are no set tasks
258253
if (set_tasks.empty()) {
259-
const auto out = tpik(hierarchies.front(), nv, damping);
260-
if (!out.has_value()) {
261-
return std::unexpected(out.error());
262-
}
263-
solution = out.value();
264-
} else {
265-
std::vector<Eigen::VectorXd> solutions;
266-
solutions.reserve(hierarchies.size());
267-
268-
for (const auto & tasks : hierarchies) {
269-
const auto out = tpik(tasks, nv, damping);
270-
if (!out.has_value()) {
271-
continue;
272-
}
254+
return tpik(hierarchies.front(), nv, damping);
255+
}
273256

274-
const Eigen::VectorXd & current_solution = out.value();
275-
if (is_feasible(set_tasks, current_solution)) {
276-
solutions.push_back(current_solution);
277-
}
257+
std::vector<Eigen::VectorXd> solutions;
258+
solutions.reserve(hierarchies.size());
259+
260+
for (const auto & tasks : hierarchies) {
261+
const auto out = tpik(tasks, nv, damping);
262+
if (!out.has_value()) {
263+
continue;
278264
}
279265

280-
if (solutions.empty()) {
281-
return std::unexpected(SolverError::NO_SOLUTION);
266+
const Eigen::VectorXd & current_solution = out.value();
267+
if (is_feasible(set_tasks, current_solution)) {
268+
solutions.push_back(current_solution);
282269
}
270+
}
283271

284-
// Choose the solution with the smallest norm
285-
solution = *std::ranges::min_element(solutions, {}, [](const auto & a) { return a.norm(); });
272+
if (solutions.empty()) {
273+
return std::unexpected(SolverError::NO_SOLUTION);
286274
}
287275

288-
return solution;
276+
// Choose the solution with the smallest norm
277+
return *std::ranges::min_element(solutions, {}, [](const auto & a) { return a.norm(); });
289278
}
290279

291280
auto pinocchio_to_eigen(const pinocchio::SE3 & pose) -> Eigen::Affine3d
@@ -354,7 +343,7 @@ auto TaskPriorityIKSolver::solve_ik(const Eigen::Affine3d & target_pose, const E
354343
const double activation = params_.joint_limit_task.constrained_joints_map[joint_name].activation_threshold;
355344
const double joint_limit_gain = params_.joint_limit_task.constrained_joints_map[joint_name].gain;
356345

357-
// insert the joint constraint
346+
// insert the joint limit constraint
358347
hierarchy_.insert(std::make_shared<hierarchy::JointConstraint>(
359348
model_, primal, ub, lb, tol, activation, joint_name, joint_limit_gain));
360349
}

velocity_controllers/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ T<sub>ry</sub>, T<sub>rz</sub> in N and Nm, respectively.
4848

4949
- adaptive_integral_terminal_sliding_mode_controller/reference [geometry_msgs::msg::Twist]
5050
- adaptive_integral_terminal_sliding_mode_controller/system_state [nav_msgs::msg::Odometry]
51-
- robot_description [std_msgs::msg::String]
5251

5352
### Publishers
5453

@@ -97,7 +96,6 @@ T<sub>ry</sub>, T<sub>rz</sub> in N and Nm, respectively.
9796

9897
- integral_sliding_mode_controller/reference [geometry_msgs::msg::Twist]
9998
- integral_sliding_mode_controller/system_state [nav_msgs::msg::Odometry]
100-
- robot_description [std_msgs::msg::String]
10199

102100
### Publishers
103101

whole_body_controllers/README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,41 @@
11
# Whole Body Controllers
22

33
This package provides a collection of whole-body controllers for underwater
4-
vehicle manipulator systems.
4+
vehicle manipulator systems (UVMS).
5+
6+
## Inverse Kinematics Controller
7+
8+
A chainable controller that uses inverse kinematics solvers to compute joint
9+
states required to achieve a desired end effector pose. This controller
10+
can be used with UVMS that have a single manipulator.
11+
12+
### Plugin Library
13+
14+
whole_body_controllers/ik_controller
15+
16+
### References
17+
18+
- Target end effector pose $\eta$<sub>EE</sub>
19+
20+
### State Feedback
21+
22+
- Measured system velocity V: V<sub>vehicle</sub>, V<sub>manipulator</sub>
23+
- Measured system position q: q<sub>vehicle</sub>, q<sub>manipulator</sub>
24+
25+
### Commands
26+
27+
The output of this controller is the solution to the chosen inverse kinematics
28+
solver.
29+
30+
### Subscribers
31+
32+
- ik_controller/reference [geometry_msgs::msg::Pose]
33+
- ik_controller/vehicle_state [nav_msgs::msg::Odometry]
34+
35+
> [!NOTE]
36+
> The IK controller does not currently support sending manipulator states via
37+
> a topic interface.
38+
39+
### Publishers
40+
41+
- ik_controller/status [auv_control_msgs::msg::IKControllerStateStamped]

0 commit comments

Comments
 (0)