update priorities

This commit is contained in:
Brian Delhaisse
2019-09-01 06:44:53 +02:00
parent 9cac32be9d
commit 8b4edf28fa
33 changed files with 2410 additions and 140 deletions
@@ -0,0 +1,4 @@
# joint acceleration constraints
from .dynamic_feasibility import DynamicFeasibilityConstraint
@@ -1,6 +1,17 @@
#!/usr/bin/env python
r"""Provide the dynamic feasibility constraint.
The equality joint acceleration constraint is given by:
.. math:: H(q) \ddot{q} + N(q, \dot{q}) = \sum_i J_i^T F_i
where :math:`H(q)` is the joint space inertia matrix, :math:`\ddot{q}` are the joint accelerations being optimized,
:math:`N(q, \dot{q})` is the vector of force terms that account for the Coriolis and centrifugal forces, gravity,
and any other forces acting on the system other than the contact forces given by :math:`\sum_i J_i^T F_i` (where
each :math:`J_i` is a Jacobian matrix and :math:`F_i` is a wrench vector at the contact link :math:`i`).
This formulation can be rewritten as an equality constraint math:`A_{eq} x = b_{eq}` in QP, with
:math:`x = \ddot{q}`, :math:`A_{eq} = H(q)`, and :math:`b_{eq} = \sum_i J_i^T F_i - N(q, \dot{q})`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -10,7 +21,7 @@ References:
import numpy as np
from pyrobolearn.priorities.constraints.constraint import Constraint
from pyrobolearn.priorities.constraints.constraint import EqualityConstraint, JointAccelerationConstraint
__author__ = "Brian Delhaisse"
@@ -23,10 +34,99 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class DynamicFeasibilityConstraint(Constraint):
class DynamicFeasibilityConstraint(EqualityConstraint, JointAccelerationConstraint):
r"""Dynamic Feasibility Constraint
The equality joint acceleration constraint is given by:
.. math:: H(q) \ddot{q} + N(q, \dot{q}) = \sum_i J_i^T F_i
where :math:`H(q)` is the joint space inertia matrix, :math:`\ddot{q}` are the joint accelerations being optimized,
:math:`N(q, \dot{q})` is the vector of force terms that account for the Coriolis and centrifugal forces, gravity,
and any other forces acting on the system other than the contact forces given by :math:`\sum_i J_i^T F_i` (where
each :math:`J_i` is a Jacobian matrix and :math:`F_i` is a wrench vector at the contact link :math:`i`).
This formulation can be rewritten as an equality constraint math:`A_{eq} x = b_{eq}` in QP, with
:math:`x = \ddot{q}`, :math:`A_{eq} = H(q)`, and :math:`b_{eq} = \sum_i J_i^T F_i - N(q, \dot{q})`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model):
def __init__(self, model, contact_links=[], wrenches=[]):
r"""
Initialize the constraint.
Args:
model (ModelInterface): model interface.
contact_links (list[str], list[int], None): list of unique contact link names or ids.
wrenches (list[np.array[float[6]]], None): list of associated wrenches applied to the contact links. It
must have the same size as the number of contact links.
"""
super(DynamicFeasibilityConstraint, self).__init__(model)
# set variables
self.contact_links = contact_links
self.wrenches = wrenches
# first update
self.update()
##############
# Properties #
##############
@property
def contact_links(self):
"""Get the contact links."""
return self._contact_links
@contact_links.setter
def contact_links(self, contacts):
"""Set the contact links."""
if contacts is None:
contacts = []
elif not isinstance(contacts, (list, tuple, np.ndarray)):
raise TypeError("Expecting the given 'contact_links' to be a list of names/ids, but got instead: "
"{}".format(type(contacts)))
self._contact_links = contacts
@property
def wrenches(self):
"""Get the wrenches."""
return self._wrenches
@wrenches.setter
def wrenches(self, wrenches):
"""Set the wrenches."""
if wrenches is None:
wrenches = []
elif not isinstance(wrenches, (list, tuple, np.ndarray)):
raise TypeError("Expecting the given 'wrenches' to be a list of wrench vectors, but got instead: "
"{}".format(type(wrenches)))
if isinstance(wrenches, np.ndarray) and wrenches.ndim == 1:
wrenches = wrenches.reshape(-1, 6)
self._wrenches = wrenches
###########
# Methods #
###########
def _update(self):
"""Update the equality constraint."""
# get dynamics
H = self.model.get_inertia_matrix()
N = self.model.compute_nonlinear_term()
# compute torques due to contact wrenches
tau = 0
for link, wrench in zip(self.contact_links, self.wrenches):
link = self.model.get_link_id(link)
jacobian = self.model.get_jacobian(link=link)
tau += jacobian.T.dot(wrench)
# equality constraints
self._A_eq = H
self._b_eq = tau - N
@@ -161,9 +161,9 @@ class Constraint(object):
Initialize the Constraint.
Args:
constraints (list[Constraint], None): inner constraints. By providing a list of constraints, they can be
combined easily.
model (ModelInterface): robotic model interface.
constraints (list[Constraint], None): inner constraints. By providing a list of constraints, they can be
combined easily.
"""
self.constraints = constraints
self.model = model
@@ -194,6 +194,14 @@ class Constraint(object):
"{}".format(model))
self._model = model
@property
def x_size(self):
"""Return the number of variables being optimized."""
# return self._x_size
if self.model is not None:
return self.model.num_actuated_joints
return 0
@property
def constraints(self):
"""Return the dict of inner constraints."""
@@ -492,14 +500,19 @@ class Constraint(object):
else:
raise TypeError("The given type of constraint is not currently supported.")
def _update(self):
"""Update the constraint variables. This has to be implemented in the child classes."""
pass
def update(self):
r"""
Update the various constraint matrices and vectors: :math:`A_{eq}, b_{eq}, A_{ineq}, b_l, b_u, ...`.
Args:
x (np.array[float[N]]): current optimization variables values.
"""
pass
if self.constraints:
for constraint in self.constraints:
constraint.update()
else:
self._update()
#############
# Operators #
@@ -512,14 +525,11 @@ class Constraint(object):
"""Return a string describing the class."""
return self.__class__.__name__
def __call__(self, x):
def __call__(self):
"""
Update the constraint (i.e. update the various constraint matrices and vectors.
Args:
x (np.array[float[N]]): current optimization variable values.
Update the constraint (i.e. update the various constraint matrices and vectors).
"""
return self.update(x)
return self.update()
def __len__(self):
"""
@@ -626,3 +636,13 @@ class JointAccelerationConstraint(DynamicConstraint):
class JointEffortConstraint(DynamicConstraint):
r"""Joint effort constraint."""
pass
class JointTorqueConstraint(DynamicConstraint):
r"""Joint torque constraint."""
pass
class JointForceConstraint(DynamicConstraint):
r"""Joint Force constraint."""
pass
@@ -0,0 +1,76 @@
#!/usr/bin/env python
r"""Transform a given equality constraint into a task.
An equality constraint specified by :math:`Fx = k` is transformed to a soft task :math:`||Ax - b||^2`, where
:math:`A = F` and :math:`b = k`. This allows for the equality constraint to be lightly violated; by specifying the
weight :math:`W` we can specify how much the constraint should be satisfied.
"""
import pyrobolearn as prl
from pyrobolearn.priorities.constraints import EqualityConstraint
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2019, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "GNU GPLv3"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class ConstraintFromTask(EqualityConstraint):
r"""Equality Constraint from Task
A soft task :math:`||Ax - b||^2` is transformed to an equality constraint specified by :math:`Fx = k`, where
:math:`F = A` and :math:`k = b`. This allows a priori for the task to not be violated, however this might results
in an impossible task to solve.
"""
def __init__(self, task):
"""
Initialize the task.
Args:
task (Task): task.
"""
# set task
self.task = task
# call superclass
super(ConstraintFromTask, self).__init__(model=task.model)
# first update
self.update()
##############
# Properties #
##############
@property
def task(self):
"""Get the task."""
return self._task
@task.setter
def task(self, task):
"""Set the task."""
if not isinstance(task, prl.priorities.tasks.Task):
raise TypeError("Expecting the given 'task' to be an instance of `Task`, but instead got: {}".format(task))
self._task = task
###########
# Methods #
###########
def _update(self):
"""
Update the task by computing the A matrix and b vector that will be used by the task solver.
"""
# update task
self.task.update()
# update A_eq and b_eq
self._A_eq = self.task.A
self._b_eq = self.task.b
@@ -0,0 +1,2 @@
# cartesian force constraints
@@ -0,0 +1,6 @@
# joint torque constraints
from .joint_limits import JointLimitsConstraint
from .torque_limits import JointTorqueLimitsConstraint
@@ -10,7 +10,7 @@ References:
import numpy as np
from pyrobolearn.priorities.constraints.constraint import Constraint
from pyrobolearn.priorities.constraints.constraint import BoundConstraint, JointTorqueConstraint
__author__ = "Brian Delhaisse"
@@ -23,10 +23,151 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class JointLimitsConstraint(Constraint):
class JointLimitsConstraint(BoundConstraint, JointTorqueConstraint):
r"""Joint Limits constraint.
This provides bounds/limits on the joint torques:
.. math:: k_p (q_{lb} - q) - k_d \dot{q} \leq \tau \leq k_p (q_{ub} - q) - k_d \dot{q}
where :math:`q_{lb}, q_{ub}` are the lower and upper joint position limits, :math:`kp` and :math:`kd` are the
position and velocity gains respectively, :math:`q, \dot{q}` are the current joint positions and velocities, and
:math:`\tau` are the torques that are being optimized.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model):
def __init__(self, model, q_lower_bound=None, q_upper_bound=None, kp=15000., kd=1000.):
r"""
Initialize the constraint.
Args:
model (ModelInterface): model interface.
q_lower_bound (np.array[float[N]], None): joint position lower limits. If None, it will take the lower
joint limits specified in the model. Note that if the lower limits are equal to the upper limits, they
will be set to -10 and 10 by default.
q_upper_bound (np.array[float[N]], None): joint position upper limits. If None, it will take the upper
joint limits specified in the model. Note that if the upper limits are equal to the lower limits, they
will be set to -10 and 10 by default.
kp (float, np.array[float[N]]): position gain(s).
kd (float, np.array[float[N]]): velocity gain(s).
"""
super(JointLimitsConstraint, self).__init__(model)
# set gains
self.kp = kp
self.kd = kd
# set variables
if q_lower_bound is None or q_upper_bound is None:
q_lb, q_ub = self.model.get_joint_limits()
if q_lower_bound is None:
q_lower_bound = q_lb
if q_upper_bound is None:
q_upper_bound = q_ub
if np.allclose(q_lower_bound, q_upper_bound):
print("WARNING: the joint position lower and upper limits are the same, by default they will be set "
"to -10 and 10.")
q_lower_bound = -10. * np.ones(len(q_lower_bound))
q_upper_bound = 10. * np.ones(len(q_upper_bound))
self.q_lower_bounds = q_lower_bound
self.q_upper_bounds = q_upper_bound
# first update
self.update()
##############
# Properties #
##############
@property
def q_lower_bounds(self):
"""Get the lower joint position limits."""
return self._q_lb
@q_lower_bounds.setter
def q_lower_bounds(self, q_lb):
"""Set the lower joint position limits."""
if q_lb is None:
q_lb = self.model.get_joint_limits()[0]
if not isinstance(q_lb, np.ndarray):
raise TypeError("Expecting the given lower joint position limits to be a np.array, instead got: "
"{}".format(q_lb))
if len(q_lb) != self.x_size:
raise ValueError("Expecting the length of the lower joint position limits (={}) to be the same length as "
"the number of variables being optimized =({}).".format(len(q_lb), self.x_size))
self._q_lb = q_lb
@property
def q_upper_bounds(self):
"""Get the upper joint position limits."""
return self._q_ub
@q_upper_bounds.setter
def q_upper_bounds(self, q_ub):
"""Set the upper joint position limits."""
if q_ub is None:
q_ub = self.model.get_joint_limits()[1]
if not isinstance(q_ub, np.ndarray):
raise TypeError("Expecting the given upper joint position limits to be a np.array, instead got: "
"{}".format(q_ub))
if len(q_ub) != self.x_size:
raise ValueError("Expecting the length of the upper joint position limits (={}) to be the same length as "
"the number of variables being optimized =({}).".format(len(q_ub), self.x_size))
self._q_ub = q_ub
@property
def kp(self):
"""Return the position gain."""
return self._kp
@kp.setter
def kp(self, kp):
"""Set the position gain."""
if not isinstance(kp, (float, int, np.ndarray)):
raise TypeError("Expecting the given position gain kp to be an int, float, np.array, instead got: "
"{}".format(type(kp)))
if isinstance(kp, np.ndarray) and len(kp) != self.x_size:
raise ValueError("Expecting the given position gain matrix kp to be of length {}, but instead got "
"a length of: {}".format(self.x_size, len(kp)))
if np.any(kp < 0):
raise ValueError("The position gain(s) should all be bigger or equal than 0, but found some negative "
"gains")
self._kp = kp
@property
def kd(self):
"""Return the linear velocity gain."""
return self._kd
@kd.setter
def kd(self, kd):
"""Set the linear velocity gain."""
if not isinstance(kd, (float, int, np.ndarray)):
raise TypeError("Expecting the given velocity gain kd to be an int, float, np.array, instead got: "
"{}".format(type(kd)))
if isinstance(kd, np.ndarray) and len(kd) != self.x_size:
raise ValueError("Expecting the given velocity gain matrix kd to be of length {}, but instead got "
"a length of: {}".format(self.x_size, len(kd)))
if np.any(kd < 0):
raise ValueError("The velocity gain(s) should all be bigger or equal than 0, but found some negative "
"gains")
self._kd = kd
###########
# Methods #
###########
def _update(self):
r"""
Update the lower and upper bounds.
"""
q = self.model.get_joint_positions()
dq = self.model.get_joint_velocities()
self._lower_bound = (self._q_lb - q) - self.kd * dq
self._upper_bound = (self._q_ub - q) - self.kd * dq
@@ -1,6 +1,17 @@
#!/usr/bin/env python
r"""Provide the torque limits constraint.
This provides bounds/limits on the joint torques:
.. math:: \tau_{lb} \leq \tau \leq \tau_{ub}
where :math:`(\tau_{lb}, \tau_{ub})` are the lower and upper bound on the joint torques, and :math:`\tau` are the
joint torques being optimized.
This formulation can be rewritten as the inequality constraint math:`lb \leq x \leq ub` in QP, with
:math:`lb = \tau_{lb}`, :math:`ub = \tau_{ub}`, and :math:`x = \tau`. This can also be rewritten as :math:`Gx \leq h`,
with :math:`G = [-I, I]^\top` and :math:`h = [-\tau_{lb}^\top, \tau_{ub}^\top]^\top` where :math:`I` is the square
identity matrix.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -10,7 +21,7 @@ References:
import numpy as np
from pyrobolearn.priorities.constraints.constraint import Constraint
from pyrobolearn.priorities.constraints.constraint import BoundConstraint, JointTorqueConstraint
__author__ = "Brian Delhaisse"
@@ -23,10 +34,108 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class TorqueLimitsConstraint(Constraint):
class JointTorqueLimitsConstraint(BoundConstraint, JointTorqueConstraint):
r"""Torque Limits constraint.
This provides bounds/limits on the joint torques:
.. math:: \tau_{lb} \leq \tau \leq \tau_{ub}
where :math:`(\tau_{lb}, \tau_{ub})` are the lower and upper bound on the joint torques, and
:math:`\tau` are the joint torques being optimized.
This formulation can be rewritten as the inequality constraint math:`lb \leq x \leq ub` in QP, with
:math:`lb = \tau_{lb}`, :math:`ub = \tau_{ub}`, and :math:`x = \tau`. This can also be rewritten as
:math:`Gx \leq h`, with :math:`G = [-I, I]^\top` and :math:`h = [-\tau_{lb}^\top, \tau_{ub}^\top]^\top`
where :math:`I` is the square identity matrix.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model):
super(TorqueLimitsConstraint, self).__init__(model)
def __init__(self, model, torque_lower_bound=None, torque_upper_bound=None):
"""
Initialize the constraint.
Args:
model (ModelInterface): model interface.
torque_lower_bound (np.array[float[N]], None): joint velocity lower limits. If None, it will take the lower
joint limits specified in the model. Note that if the lower limits are equal to the upper limits, they
will be set to -100 and 100 by default.
torque_upper_bound (np.array[float[N]], None): joint velocity upper limits. If None, it will take the upper
joint limits specified in the model. Note that if the upper limits are equal to the lower limits, they
will be set to -100 and 100 by default.
"""
super(JointTorqueLimitsConstraint, self).__init__(model)
# set variables
if torque_lower_bound is None or torque_upper_bound is None:
tau_lb, tau_ub = self.model.get_joint_velocity_limits()
if torque_lower_bound is None:
torque_lower_bound = tau_lb
if torque_upper_bound is None:
torque_upper_bound = tau_ub
if np.allclose(torque_lower_bound, torque_upper_bound):
print("WARNING: the joint velocity lower and upper limits are the same, by default they will be set "
"to -10 and 10.")
torque_lower_bound = -10. * np.ones(len(torque_lower_bound))
torque_upper_bound = 10. * np.ones(len(torque_upper_bound))
self.torque_lower_bounds = torque_lower_bound
self.torque_upper_bounds = torque_upper_bound
# first update
self.update()
##############
# Properties #
##############
@property
def torque_lower_bounds(self):
"""Get the lower joint velocity limits."""
return self._tau_lb
@torque_lower_bounds.setter
def torque_lower_bounds(self, torque_lb):
"""Set the lower joint velocity limits."""
if torque_lb is None:
torque_lb = self.model.get_joint_limits()[0]
if not isinstance(torque_lb, np.ndarray):
raise TypeError("Expecting the given lower joint velocity limits to be a np.array, instead got: "
"{}".format(torque_lb))
if len(torque_lb) != self.x_size:
raise ValueError("Expecting the length of the lower joint velocity limits (={}) to be the same length as "
"the number of variables being optimized =({}).".format(len(torque_lb), self.x_size))
self._tau_lb = torque_lb
@property
def torque_upper_bounds(self):
"""Get the upper joint velocity limits."""
return self._tau_ub
@torque_upper_bounds.setter
def torque_upper_bounds(self, torque_ub):
"""Set the upper joint velocity limits."""
if torque_ub is None:
torque_ub = self.model.get_joint_limits()[1]
if not isinstance(torque_ub, np.ndarray):
raise TypeError("Expecting the given upper joint velocity limits to be a np.array, instead got: "
"{}".format(torque_ub))
if len(torque_ub) != self.x_size:
raise ValueError("Expecting the length of the upper joint velocity limits (={}) to be the same length as "
"the number of variables being optimized =({}).".format(len(torque_ub), self.x_size))
self._tau_ub = torque_ub
###########
# Methods #
###########
def _update(self):
r"""
Update the lower and upper bounds.
"""
self._lower_bound = self._tau_lb
self._upper_bound = self._tau_ub
@@ -1,4 +1,6 @@
# joint velocity constraints
from .joint_limits import JointPositionLimitsConstraint
from .velocity_limits import JointVelocityLimitsConstraint
@@ -1,6 +1,17 @@
#!/usr/bin/env python
r"""Provide the Cartesian Position constraint.
The bilateral inequality cartesian position constraint is given by:
.. math:: x_{lb} \leq x + J(q) \dot{q} * dt \leq x_{ub}
where :math:`x_{lb}, x_{ub}` are the lower and upper bound on the cartesian positions of a given distal link,
:math:`x` is the current cartesian position of the distal link wrt the base link, :math:`\dot{q}` are the joint
velocities being optimized, :math:`J(q)` is the Jacobian from the base to the distal link, and :math:`dt` is the
integration time step.
This formulation can be rewritten as a bilateral inequality constraint :math:`b_l \leq A_{ineq} x \leq b_u` in QP,
with :math:`x = \dot{q}`, :math:`A_{ineq} = J(q) * dt`, :math:`b_l = (x_{lb} - x)` and :math:`b_u = (x_{ub} - x)`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -10,7 +21,7 @@ References:
import numpy as np
from pyrobolearn.priorities.constraints.constraint import Constraint
from pyrobolearn.priorities.constraints.constraint import BilateralConstraint, JointVelocityConstraint
__author__ = "Brian Delhaisse"
@@ -23,10 +34,128 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class CartesianPositionConstraint(Constraint):
class CartesianPositionConstraint(BilateralConstraint, JointVelocityConstraint):
r"""Cartesian Position constraint.
The bilateral inequality cartesian position constraint is given by:
.. math:: x_{lb} \leq x + J(q) \dot{q} * dt \leq x_{ub}
where :math:`x_{lb}, x_{ub}` are the lower and upper bound on the cartesian positions of a given distal link,
:math:`x` is the current cartesian position of the distal link wrt the base link, :math:`\dot{q}` are the joint
velocities being optimized, :math:`J(q)` is the Jacobian from the base to the distal link, and :math:`dt` is the
integration time step.
This formulation can be rewritten as a bilateral inequality constraint :math:`b_l \leq A_{ineq} x \leq b_u` in QP,
with :math:`x = \dot{q}`, :math:`A_{ineq} = J(q) * dt`, :math:`b_l = (x_{lb} - x)` and :math:`b_u = (x_{ub} - x)`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model):
def __init__(self, model, dt, distal_link, base_link=None, local_position=(0, 0, 0), x_lower_bound=None,
x_upper_bound=None):
r"""
Initialize the constraint.
Args:
model (ModelInterface): model interface.
dt (float): integration time step: use to compute :math:`x = x + v dt`.
distal_link (int, str): distal link id or name.
base_link (int, str, None): base link id or name. If None, it will be the world.
local_position (np.array[float[3]]): local position on the distal link.
x_lower_bound (np.array[float[3]], None): the lower bound on the cartesian position of the distal link wrt
the base link. If None, it will not be considered.
x_upper_bound (np.array[float[3]], None): the upper bound on the cartesian position of the distal link wrt
the base link. If None, it will not be considered.
"""
super(CartesianPositionConstraint, self).__init__(model)
# set time
self.dt = dt
# define variables
self.distal_link = self.model.get_link_id(distal_link)
self.base_link = self.model.get_link_id(base_link) if base_link is not None else base_link
self.local_position = local_position
self.position_lower_bound = x_lower_bound
self.position_upper_bound = x_upper_bound
# first update
self.update()
##############
# Properties #
##############
@property
def dt(self):
"""Return the integration time step."""
return self._dt
@dt.setter
def dt(self, dt):
"""Set the integration time step."""
if not isinstance(dt, (float, int)):
raise TypeError("Expecting the integration time step `dt` to be a float or int.")
if dt <= 0:
raise ValueError("Expecting the integration time step `dt` to be bigger than 0.")
self._dt = float(dt)
@property
def position_lower_bound(self):
"""Get the lower bound on the cartesian position of the distal link wrt the base link."""
return self._x_lower_bound
@position_lower_bound.setter
def position_lower_bound(self, bound):
"""Set the lower bound on the cartesian position of the distal link wrt the base link."""
if bound is not None:
if not isinstance(bound, (list, tuple, np.ndarray)):
raise TypeError("Expecting the given lower cartesian position bound to be a np.array, instead got: "
"{}".format(bound))
bound = np.asarray(bound)
if len(bound) != 3:
raise ValueError("Expecting the length of the lower bound to be 3, but got instead a length of: "
"{}.".format(len(bound)))
self._x_lower_bound = bound
@property
def position_upper_bound(self):
"""Get the upper bound on the cartesian position of the distal link wrt the base link."""
return self._x_upper_bound
@position_upper_bound.setter
def position_upper_bound(self, bound):
"""Set the upper bound on the cartesian position of the distal link wrt the base link."""
if bound is not None:
if not isinstance(bound, (list, tuple, np.ndarray)):
raise TypeError("Expecting the given upper cartesian position bound to be a np.array, instead got: "
"{}".format(bound))
bound = np.asarray(bound)
if len(bound) != 3:
raise ValueError("Expecting the length of the upper bound to be 3, but got instead a length of: "
"{}.".format(len(bound)))
self._x_upper_bound = bound
###########
# Methods #
###########
def _update(self):
"""Update the inequality matrix and vectors."""
if self._x_lower_bound is None and self._x_upper_bound is None:
raise ValueError("Expecting at least the lower or upper bounds of the cartesian position to be "
"specified, but instead got None for both of them.")
self._A_ineq = self.model.get_jacobian(link=self.distal_link, wrt_link=self.base_link,
point=self.local_position)[:3] * self.dt
x = self.model.get_position(link=self.distal_link, wrt_link=self.base_link)
if self._x_lower_bound is not None:
self._b_lower_bound = self._x_lower_bound - x
if self._x_upper_bound is not None:
self._b_upper_bound = self._x_upper_bound - x
@@ -1,6 +1,17 @@
#!/usr/bin/env python
r"""Provide the cartesian velocity constraint.
The bilateral inequality cartesian velocity constraint is given by:
.. math:: v_{lb} \leq J(q) \dot{q} \leq v_{ub}
where :math:`v_{lb}, v_{ub}` are the lower and upper bound on the cartesian velocities of a given distal link,
:math:`\dot{q}` are the joint velocities being optimized, and :math:`J(q)` is the Jacobian from the base to the
distal link.
This formulation can be rewritten as a bilateral inequality constraint :math:`b_l \leq A_{ineq} x \leq b_u` in QP,
with :math:`x = \dot{q}`, :math:`A_{ineq} = J(q)`, :math:`b_l = v_{lb}` and :math:`b_u = v_{ub}`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -10,7 +21,7 @@ References:
import numpy as np
from pyrobolearn.priorities.constraints.constraint import Constraint
from pyrobolearn.priorities.constraints.constraint import BilateralConstraint, JointVelocityConstraint
__author__ = "Brian Delhaisse"
@@ -23,10 +34,141 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class CartesianVelocityConstraint(Constraint):
class CartesianVelocityConstraint(BilateralConstraint, JointVelocityConstraint):
r"""Cartesian Velocity constraint.
The bilateral inequality cartesian velocity constraint is given by:
.. math:: v_{lb} \leq J(q) \dot{q} \leq v_{ub}
where :math:`v_{lb}, v_{ub}` are the lower and upper bound on the cartesian velocities of a given distal link,
:math:`\dot{q}` are the joint velocities being optimized, and :math:`J(q)` is the Jacobian from the base to the
distal link.
This formulation can be rewritten as a bilateral inequality constraint :math:`b_l \leq A_{ineq} x \leq b_u` in QP,
with :math:`x = \dot{q}`, :math:`A_{ineq} = J(q)`, :math:`b_l = v_{lb}` and :math:`b_u = v_{ub}`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model):
def __init__(self, model, distal_link, base_link=None, local_position=(0, 0, 0), linear_velocity_bounds=None,
angular_velocity_bounds=None):
r"""
Initialize the constraint.
Args:
model (ModelInterface): model interface.
distal_link (int, str): distal link id or name.
base_link (int, str, None): base link id or name. If None, it will be the world.
local_position (np.array[float[3]]): local position on the distal link.
linear_velocity_bounds (tuple[2 * np.array[float[3]]], np.array[float[3]], None): If tuple, it is the
lower and upper bounds on the linear velocity. If np.array, then the lower and upper bound would be set
to (-linear_velocity_bounds, linear_velocity_bounds). If None, it will not be considered.
angular_velocity_bounds (tuple[2 * np.array[float[3]]], np.array[float[3]], None): If tuple, it is the lower and
upper bounds on the angular velocity. If np.array, then the lower and upper bound would be set to
(-angular_velocity_bounds, angular_velocity_bounds). If None, it will not be considered.
"""
super(CartesianVelocityConstraint, self).__init__(model)
# define variables
self.distal_link = self.model.get_link_id(distal_link)
self.base_link = self.model.get_link_id(base_link) if base_link is not None else base_link
self.local_position = local_position
self.linear_velocity_bounds = linear_velocity_bounds
self.angular_velocity_bounds = angular_velocity_bounds
# first update
self.update()
##############
# Properties #
##############
@property
def linear_velocity_bounds(self):
"""Get the cartesian linear velocity bounds of the distal link wrt the base."""
return self._lin_vel_bounds
@linear_velocity_bounds.setter
def linear_velocity_bounds(self, bounds):
"""Set the cartesian linear velocity bounds of the distal link wrt the base."""
if bounds is not None:
if isinstance(bounds, tuple):
if len(bounds) != 2:
raise ValueError("Expecting the bounds to be a tuple of length 2, but got a length of "
"{}".format(len(bounds)))
for bound in bounds:
if not isinstance(bound, np.ndarray):
raise TypeError("Expecting the given bound to be a np.array, but got instead: "
"{}".format(type(bound)))
if len(bound) != 3:
raise ValueError("Expecting the given bound to be of length 3, but instead got a length of "
"{}".format(len(bound)))
elif isinstance(bounds, np.ndarray):
if len(bounds) == 3:
bounds = (-bounds, bounds)
elif len(bounds) == 6:
bounds = (bounds[:3], bounds[3:])
else:
raise TypeError("Expecting the given bounds to be a tuple of np.array, a np.array, or None, but "
"instead got: {}".format(type(bounds)))
self._lin_vel_bounds = bounds
@property
def angular_velocity_bounds(self):
"""Get the cartesian angular velocity bounds of the distal link wrt the base."""
return self._ang_vel_bounds
@angular_velocity_bounds.setter
def angular_velocity_bounds(self, bounds):
"""Set the cartesian angular velocity bounds of the distal link wrt the base."""
if bounds is not None:
if isinstance(bounds, tuple):
if len(bounds) != 2:
raise ValueError("Expecting the bounds to be a tuple of length 2, but got a length of "
"{}".format(len(bounds)))
for bound in bounds:
if not isinstance(bound, np.ndarray):
raise TypeError("Expecting the given bound to be a np.array, but got instead: "
"{}".format(type(bound)))
if len(bound) != 3:
raise ValueError("Expecting the given bound to be of length 3, but instead got a length of "
"{}".format(len(bound)))
elif isinstance(bounds, np.ndarray):
if len(bounds) == 3:
bounds = (-bounds, bounds)
elif len(bounds) == 6:
bounds = (bounds[:3], bounds[3:])
else:
raise TypeError("Expecting the given bounds to be a tuple of np.array, a np.array, or None, but "
"instead got: {}".format(type(bounds)))
self._ang_vel_bounds = bounds
###########
# Methods #
###########
def _update(self):
"""Update the inequality matrix and vectors."""
if self.linear_velocity_bounds is None and self.angular_velocity_bounds is None:
raise ValueError("Expecting at least the linear or angular velocity bounds to be specified, but instead "
"got None for both of them.")
self._A_ineq = self.model.get_jacobian(link=self.distal_link, wrt_link=self.base_link,
point=self.local_position)
if self.linear_velocity_bounds is None:
self._b_lower_bound, self._b_upper_bound = self.angular_velocity_bounds
self._A_ineq = self._A_ineq[3:]
elif self.angular_velocity_bounds is None:
self._b_lower_bound, self._b_upper_bound = self.linear_velocity_bounds
self._A_ineq = self._A_ineq[:3]
else:
b_lin_low, b_lin_up = self.linear_velocity_bounds
b_ang_low, b_ang_up = self.angular_velocity_bounds
self._b_lower_bound = np.concatenate((b_lin_low, b_ang_low))
self._b_upper_bound = np.concatenate((b_lin_up, b_ang_up))
@@ -1,6 +1,18 @@
#!/usr/bin/env python
r"""Provide the joint position limits constraint.
This provides bounds/limits on the joint positions, which are given by:
.. math:: q_{lb} \leq q + \dot{q} dt \leq q_{ub}
where :math:`(q_{lb}, q_{ub})` are the lower and upper bound of the joint positions respectively, :math:`q` are
the current joint positions, :math:`\dot{q}` are the joint velocities being optimized, and :math:`dt` is the
integration time step.
This formulation can be rewritten as the inequality constraint math:`lb \leq x \leq ub` in QP, with
:math:`lb = (q_{lb} - q) / dt`, :math:`ub = (q_{ub} - q) / dt`, and :math:`x = \dot{q}`. This can
also be rewritten as :math:`Gx \leq h`, with :math:`G = [-dt*I, dt*I]^\top` and
:math:`h = [(q - q_{lb})^\top, (q_{ub} - q)^\top]^\top` where :math:`I` is the square identity matrix.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -34,31 +46,117 @@ class JointPositionLimitsConstraint(BoundConstraint, JointVelocityConstraint):
the current joint positions, :math:`\dot{q}` are the joint velocities being optimized, and :math:`dt` is the
integration time step.
This formulation can be rewritten as the inequality constraint :math:`Gx \leq h` used in QP, with
:math:`G = [-dt*I, dt*I]^\top` and :math:`h = [(q - q_{lb})^\top, (q_{ub} - q)^\top]^\top` where :math:`I` is the
square identity matrix.
This formulation can be rewritten as the inequality constraint math:`lb \leq x \leq ub` in QP, with
:math:`lb = (q_{lb} - q) / dt`, :math:`ub = (q_{ub} - q) / dt`, and :math:`x = \dot{q}`. This can
also be rewritten as :math:`Gx \leq h`, with :math:`G = [-dt*I, dt*I]^\top` and
:math:`h = [(q - q_{lb})^\top, (q_{ub} - q)^\top]^\top` where :math:`I` is the square identity matrix.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model, dt):
def __init__(self, model, dt, q_lower_bound=None, q_upper_bound=None):
r"""
Initialize the constraint.
Args:
model (ModelInterface): model interface.
dt (float): integration time step: use to compute :math:`q = q + \dot{q} dt`.
q_lower_bound (np.array[float[N]], None): joint position lower limits. If None, it will take the lower
joint limits specified in the model. Note that if the lower limits are equal to the upper limits, they
will be set to -10 and 10 by default.
q_upper_bound (np.array[float[N]], None): joint position upper limits. If None, it will take the upper
joint limits specified in the model. Note that if the upper limits are equal to the lower limits, they
will be set to -10 and 10 by default.
"""
super(JointPositionLimitsConstraint, self).__init__(model)
# set time
self.dt = dt
bounds = self.model.get_joint_bounds()
self._lower_bound = bounds[0]
self._upper_bound = bounds[1]
# set variables
if q_lower_bound is None or q_upper_bound is None:
q_lb, q_ub = self.model.get_joint_limits()
if q_lower_bound is None:
q_lower_bound = q_lb
if q_upper_bound is None:
q_upper_bound = q_ub
if np.allclose(q_lower_bound, q_upper_bound):
print("WARNING: the joint position lower and upper limits are the same, by default they will be set "
"to -10 and 10.")
q_lower_bound = -10. * np.ones(len(q_lower_bound))
q_upper_bound = 10. * np.ones(len(q_upper_bound))
def update(self):
self.q_lower_bounds = q_lower_bound
self.q_upper_bounds = q_upper_bound
# first update
self.update()
##############
# Properties #
##############
@property
def dt(self):
"""Return the integration time step."""
return self._dt
@dt.setter
def dt(self, dt):
"""Set the integration time step."""
if not isinstance(dt, (float, int)):
raise TypeError("Expecting the integration time step `dt` to be a float or int.")
if dt <= 0:
raise ValueError("Expecting the integration time step `dt` to be bigger than 0.")
self._dt = float(dt)
@property
def q_lower_bounds(self):
"""Get the lower joint position limits."""
return self._q_lb
@q_lower_bounds.setter
def q_lower_bounds(self, q_lb):
"""Set the lower joint position limits."""
if q_lb is None:
q_lb = self.model.get_joint_limits()[0]
if not isinstance(q_lb, np.ndarray):
raise TypeError("Expecting the given lower joint position limits to be a np.array, instead got: "
"{}".format(q_lb))
if len(q_lb) != self.x_size:
raise ValueError("Expecting the length of the lower joint position limits (={}) to be the same length as "
"the number of variables being optimized =({}).".format(len(q_lb), self.x_size))
self._q_lb = q_lb
@property
def q_upper_bounds(self):
"""Get the upper joint position limits."""
return self._q_ub
@q_upper_bounds.setter
def q_upper_bounds(self, q_ub):
"""Set the upper joint position limits."""
if q_ub is None:
q_ub = self.model.get_joint_limits()[1]
if not isinstance(q_ub, np.ndarray):
raise TypeError("Expecting the given upper joint position limits to be a np.array, instead got: "
"{}".format(q_ub))
if len(q_ub) != self.x_size:
raise ValueError("Expecting the length of the upper joint position limits (={}) to be the same length as "
"the number of variables being optimized =({}).".format(len(q_ub), self.x_size))
self._q_ub = q_ub
###########
# Methods #
###########
def _update(self):
r"""
Update the bounds.
Update the lower and upper bounds.
"""
q = self.model.get_joint_positions()
self.lower_bound = 0
self.upper_bound = 0
self._lower_bound = (self._q_lb - q) / self.dt
self._upper_bound = (self._q_ub - q) / self.dt
@@ -1,6 +1,19 @@
#!/usr/bin/env python
r"""Provide the differential kinematics constraint.
This provides the joint velocity constraints which is given by:
.. math:: J(q) \dot{q} = v
where :math:`J(q)` is the jacobian from a base link to a distal link, :math:`\dot{q}` are the joint velocities
being optimized, and :math:`v` is the imposed cartesian velocity imposed on the distal link.
This formulation can be rewritten as the inequality constraint :math:`A_{eq} x = b_{eq}` used in QP, with
:math:`A_{eq} = J(q)`, :math:`x = \dot{q}`, and :math:`b_{eq} = v`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
@@ -32,9 +45,15 @@ class DifferentialKinematicsConstraint(EqualityConstraint, JointVelocityConstrai
This formulation can be rewritten as the inequality constraint :math:`A_{eq} x = b_{eq}` used in QP, with
:math:`A_{eq} = J(q)`, :math:`x = \dot{q}`, and :math:`b_{eq} = v`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model, distal_link, base_link=None, velocity=None):
def __init__(self, model, distal_link, base_link=None, local_position=(0., 0., 0.), linear_velocity=None,
angular_velocity=None):
r"""
Initialize the constraint.
@@ -42,13 +61,82 @@ class DifferentialKinematicsConstraint(EqualityConstraint, JointVelocityConstrai
model (ModelInterface): model interface.
distal_link (int, str): distal link id or name.
base_link (int, str, None): base link id or name. If None, it will be the world.
velocity (np.array[6], None): imposed velocity. If None, it will be set to 0.
local_position (np.array[float[3]]): local position on the distal link.
linear_velocity (np.array[float[3]], None): imposed linear velocity. If None, it will not be considered.
angular_velocity (np.array[float[3]], None): imposed angular velocity. If None, it will not be considered.
"""
super(DifferentialKinematicsConstraint, self).__init__(model)
raise NotImplementedError
def update(self):
# define variables
self.distal_link = self.model.get_link_id(distal_link)
self.base_link = self.model.get_link_id(base_link) if base_link is not None else base_link
self.local_position = local_position
# set velocities
self.linear_velocity = linear_velocity
self.angular_velocity = angular_velocity
# first update
self.update()
##############
# Properties #
##############
@property
def linear_velocity(self):
"""Get the cartesian linear velocity of the distal link wrt the base."""
return self._lin_vel
@linear_velocity.setter
def linear_velocity(self, velocity):
"""Set the cartesian linear velocity of the distal link wrt the base."""
if velocity is not None:
if not isinstance(velocity, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given linear velocity to be a np.array, instead got: "
"{}".format(type(velocity)))
velocity = np.asarray(velocity)
if len(velocity) != 3:
raise ValueError("Expecting the given linear velocity array to be of length 3, but instead "
"got: {}".format(len(velocity)))
self._lin_vel = velocity
@property
def angular_velocity(self):
"""Get the cartesian angular velocity of the distal link wrt the base."""
return self._ang_vel
@angular_velocity.setter
def angular_velocity(self, velocity):
"""Set the cartesian angular velocity of the distal link wrt the base."""
if velocity is not None:
if not isinstance(velocity, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given angular velocity to be a np.array, instead got: "
"{}".format(type(velocity)))
velocity = np.asarray(velocity)
if len(velocity) != 3:
raise ValueError("Expecting the given angular velocity array to be of length 3, but instead "
"got: {}".format(len(velocity)))
self._ang_vel = velocity
###########
# Methods #
###########
def _update(self):
r"""
Update the bounds.
Update the constraint by computing :math:`A_{eq}` and :math:`b_{eq}`.
"""
raise NotImplementedError
if self._lin_vel is None and self._ang_vel is None:
raise ValueError("Expecting at least the linear or angular velocity to be specified, but none are "
"provided.")
self._A_eq = self.model.get_jacobian(link=self.distal_link, wrt_link=self.base_link, point=self.local_position)
if self._lin_vel is None:
self._A_eq = self._A_eq[3:]
self._b_eq = self._ang_vel
elif self._ang_vel is None:
self._A_eq = self._A_eq[:3]
self._b_eq = self._lin_vel
else:
self._b_eq = np.concatenate((self._lin_vel, self._ang_vel))
@@ -1,6 +1,17 @@
#!/usr/bin/env python
r"""Provide the velocity limits constraint.
This provides bounds/limits on the joint velocities
.. math:: \dot{q}_{lb} \leq \dot{q} \leq \dot{q}_{ub}
where :math:`(\dot{q}_{lb}, \dot{q}_{ub})` are the lower and upper bound on the joint velocities, and
:math:`\dot{q}` are the joint velocities being optimized.
This formulation can be rewritten as the inequality constraint math:`lb \leq x \leq ub` in QP, with
:math:`lb = \dot{q}_{lb}`, :math:`ub = \dot{q}_{ub}`, and :math:`x = \dot{q}`. This can also be rewritten as
:math:`Gx \leq h`, with :math:`G = [-I, I]^\top` and :math:`h = [-\dot{q}_{lb}^\top, \dot{q}_{ub}^\top]^\top`
where :math:`I` is the square identity matrix.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -26,30 +37,105 @@ __status__ = "Development"
class JointVelocityLimitsConstraint(BoundConstraint, JointVelocityConstraint):
r"""Joint velocity limits constraint.
This provides bounds/limits on the joint velocities
This provides bounds/limits on the joint velocities:
.. math:: \dot{q}_{lb} \leq \dot{q} \leq \dot{q}_{ub}
where :math:`(\dot{q}_{lb}, \dot{q}_{ub})` are the lower and upper bound on the joint velocities, and
:math:`\dot{q}` are the joint velocities being optimized.
This formulation can be rewritten as the inequality constraint :math:`Gx \leq h` used in QP, with
:math:`G = [-I, I]^\top` and :math:`h = [-q_{lb}^\top, q_{ub}^\top]^\top` where :math:`I` is the square identity
matrix.
This formulation can be rewritten as the inequality constraint math:`lb \leq x \leq ub` in QP, with
:math:`lb = \dot{q}_{lb}`, :math:`ub = \dot{q}_{ub}`, and :math:`x = \dot{q}`. This can also be rewritten as
:math:`Gx \leq h`, with :math:`G = [-I, I]^\top` and :math:`h = [-\dot{q}_{lb}^\top, \dot{q}_{ub}^\top]^\top`
where :math:`I` is the square identity matrix.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model):
def __init__(self, model, dq_lower_bound=None, dq_upper_bound=None):
"""
Initialize the constraint.
Args:
model (ModelInterface): model interface.
dq_lower_bound (np.array[float[N]], None): joint velocity lower limits. If None, it will take the lower
joint limits specified in the model. Note that if the lower limits are equal to the upper limits, they
will be set to -10 and 10 by default.
dq_upper_bound (np.array[float[N]], None): joint velocity upper limits. If None, it will take the upper
joint limits specified in the model. Note that if the upper limits are equal to the lower limits, they
will be set to -10 and 10 by default.
"""
super(JointVelocityLimitsConstraint, self).__init__(model)
bounds = self.model.get_joint_velocity_bounds()
self.lower_bound = bounds[0]
self.upper_bound = bounds[1]
# set variables
if dq_lower_bound is None or dq_upper_bound is None:
dq_lb, dq_ub = self.model.get_joint_velocity_limits()
if dq_lower_bound is None:
dq_lower_bound = dq_lb
if dq_upper_bound is None:
dq_upper_bound = dq_ub
if np.allclose(dq_lower_bound, dq_upper_bound):
print("WARNING: the joint velocity lower and upper limits are the same, by default they will be set "
"to -10 and 10.")
dq_lower_bound = -10. * np.ones(len(dq_lower_bound))
dq_upper_bound = 10. * np.ones(len(dq_upper_bound))
def update(self):
pass
self.dq_lower_bounds = dq_lower_bound
self.dq_upper_bounds = dq_upper_bound
# first update
self.update()
##############
# Properties #
##############
@property
def dq_lower_bounds(self):
"""Get the lower joint velocity limits."""
return self._dq_lb
@dq_lower_bounds.setter
def dq_lower_bounds(self, dq_lb):
"""Set the lower joint velocity limits."""
if dq_lb is None:
dq_lb = self.model.get_joint_limits()[0]
if not isinstance(dq_lb, np.ndarray):
raise TypeError("Expecting the given lower joint velocity limits to be a np.array, instead got: "
"{}".format(dq_lb))
if len(dq_lb) != self.x_size:
raise ValueError("Expecting the length of the lower joint velocity limits (={}) to be the same length as "
"the number of variables being optimized =({}).".format(len(dq_lb), self.x_size))
self._dq_lb = dq_lb
@property
def dq_upper_bounds(self):
"""Get the upper joint velocity limits."""
return self._dq_ub
@dq_upper_bounds.setter
def dq_upper_bounds(self, dq_ub):
"""Set the upper joint velocity limits."""
if dq_ub is None:
dq_ub = self.model.get_joint_limits()[1]
if not isinstance(dq_ub, np.ndarray):
raise TypeError("Expecting the given upper joint velocity limits to be a np.array, instead got: "
"{}".format(dq_ub))
if len(dq_ub) != self.x_size:
raise ValueError("Expecting the length of the upper joint velocity limits (={}) to be the same length as "
"the number of variables being optimized =({}).".format(len(dq_ub), self.x_size))
self._dq_ub = dq_ub
###########
# Methods #
###########
def _update(self):
r"""
Update the lower and upper bounds.
"""
self._lower_bound = self._dq_lb
self._upper_bound = self._dq_ub
+53 -2
View File
@@ -119,6 +119,26 @@ class ModelInterface(object):
"""
pass
def get_joint_limits(self):
r"""
Return the joint limits.
Returns:
np.array[float[N]]: lower joint position limits.
np.array[float[N]]: upper joint position limits.
"""
pass
def get_joint_velocity_limits(self):
r"""
Return the joint velocity limits.
Returns:
np.array[float[N]]: lower joint velocity limits.
np.array[float[N]]: upper joint velocity limits.
"""
pass
def get_joint_positions(self):
"""
Get the joint positions.
@@ -239,18 +259,49 @@ class ModelInterface(object):
"""
pass
def get_pose(self, link):
def get_pose(self, link, wrt_link=None, point=(0., 0., 0.)):
"""
Return the pose of the specified link.
Return the pose of the specified link with respect to another link.
Args:
link (int, str): unique link id, or name.
wrt_link (int, str, None): the other link id, or name. If None, returns the pose wrt to the world, and
if -1 wrt to the base.
point (np.array[float[3]]): position of the point in link's local frame.
Returns:
np.array[float[7]]: pose (position and quaternion expressed as [x,y,z,w])
"""
pass
def get_position(self, link, wrt_link=None):
"""
Return the position of the specified link with respect to another link.
Args:
link (int, str): unique link id, or name.
wrt_link (int, str, None): the other link id, or name. If None, returns the position wrt to the world,
and if -1 wrt to the base.
Returns:
np.array[float[3]]: position
"""
pass
def get_orientation(self, link, wrt_link=None):
"""
Return the orientation of the specified link with respect to another link.
Args:
link (int, str): unique link id, or name.
wrt_link (int, str, None): the other link id, or name. If None, returns the orientation wrt to the world,
and if -1 wrt to the base.
Returns:
np.array[float[4]]: orientation (expressed as a quaternion [x,y,z,w])
"""
pass
def get_velocity_twist(self, link):
r"""
Compute the angular and linear velocity of a link, given by :math:`v = [\omega, \dot{p}]`.
+84 -7
View File
@@ -68,12 +68,12 @@ class RobotModelInterface(ModelInterface):
- If fixed base, this is equal to the number of actuated joints.
- If floating base, this is equal to the number of actuated joints + 6 DoFs for the base.
"""
return self.robot.num_dofs
return self.model.num_dofs
@property
def num_actuated_joints(self):
"""Return the number of actuated joints."""
return self.robot.num_actuated_joints
return self.model.num_actuated_joints
###########
# Methods #
@@ -120,6 +120,27 @@ class RobotModelInterface(ModelInterface):
"""
return -1
def get_joint_limits(self):
r"""
Return the joint limits.
Returns:
np.array[float[N]]: lower joint position limits.
np.array[float[N]]: upper joint position limits.
"""
return self.model.get_joint_limits()
def get_joint_velocity_limits(self):
r"""
Return the joint velocity limits.
Returns:
np.array[float[N]]: lower joint velocity limits.
np.array[float[N]]: upper joint velocity limits.
"""
dq = self.model.get_joint_max_velocities()
return -dq, dq
def get_joint_positions(self):
"""
Get the joint positions.
@@ -127,7 +148,7 @@ class RobotModelInterface(ModelInterface):
Returns:
np.array[float[N]]: the joint positions.
"""
return self.robot.get_joint_positions()
return self.model.get_joint_positions()
def get_joint_velocities(self):
"""
@@ -136,7 +157,7 @@ class RobotModelInterface(ModelInterface):
Returns:
np.array[float[N]]: the joint positions.
"""
return self.robot.get_joint_velocities()
return self.model.get_joint_velocities()
def get_joint_accelerations(self):
"""
@@ -145,7 +166,7 @@ class RobotModelInterface(ModelInterface):
Returns:
np.array[float[N]]: the joint positions.
"""
return self.robot.get_joint_accelerations()
return self.model.get_joint_accelerations()
def get_com_position(self):
"""
@@ -266,11 +287,11 @@ class RobotModelInterface(ModelInterface):
def get_pose(self, link, wrt_link=None, point=(0., 0., 0.)): # TODO: use point
"""
Return the pose of the specified link.
Return the pose of the specified link with respect to another link.
Args:
link (int, str): unique link id, or name.
wrt_link (int, str, None): the other link id, or name. If None, returns the position wrt to the world, and
wrt_link (int, str, None): the other link id, or name. If None, returns the pose wrt to the world, and
if -1 wrt to the base.
point (np.array[float[3]]): position of the point in link's local frame.
@@ -282,6 +303,40 @@ class RobotModelInterface(ModelInterface):
return self.model.get_link_world_poses(link)
return self.model.get_link_poses(link, self.get_link_id(wrt_link))
def get_position(self, link, wrt_link=None): # TODO: use point
"""
Return the position of the specified link with respect to another link.
Args:
link (int, str): unique link id, or name.
wrt_link (int, str, None): the other link id, or name. If None, returns the position wrt to the world,
and if -1 wrt to the base.
Returns:
np.array[float[3]]: position
"""
link = self.get_link_id(link)
if wrt_link is None:
return self.model.get_link_world_positions(link)
return self.model.get_link_positions(link, wrt_link_id=self.get_link_id(wrt_link))
def get_orientation(self, link, wrt_link=None): # TODO: use point
"""
Return the orientation of the specified link with respect to another link.
Args:
link (int, str): unique link id, or name.
wrt_link (int, str, None): the other link id, or name. If None, returns the orientation wrt to the world,
and if -1 wrt to the base.
Returns:
np.array[float[4]]: orientation (expressed as a quaternion [x,y,z,w])
"""
link = self.get_link_id(link)
if wrt_link is None:
return self.model.get_link_world_orientations(link)
return self.model.get_link_orientations(link, wrt_link_id=self.get_link_id(wrt_link))
def get_velocity_twist(self, link, point=(0., 0., 0.)): # TODO: use point
r"""
Compute the angular and linear velocity of a link, given by :math:`v = [\omega, \dot{p}]`.
@@ -417,6 +472,17 @@ class RobotModelInterface(ModelInterface):
# return \dot{J}(q) \dot{q} = \dot{v} - J(q) \ddot{q}
return acc - jacobian.dot(ddq)
def compute_com_JdotQdot(self):
r"""
Compute :math:`\dot{J}_{CoM}(q) \dot{q}` from the centroidal momentum matrix.
Returns:
np.array[float[6]]: the matrix multiplication of the first derivative of the CoM Jacobian with the joint
velocities.
"""
A_G, dA_G_dq = self.get_centroidal_dynamics()
return dA_G_dq / self.get_mass()
def compute_relative_JdotQdot(self, target_link, base_link):
r"""
Compute the relative :math:`\dot{J}(q) \dot{q}`, which appears in
@@ -496,6 +562,17 @@ class RobotModelInterface(ModelInterface):
"""
return self.model.get_centroidal_momentum_matrix()
def get_centroidal_dynamics(self):
r"""
Return the centroidal momentum matrix :math:`A_G` and its derivative multiplied by the joint velocities
:math:`\dot{A}_G \dot{q}`.
Returns:
np.array[float[6,6+N]]: the centroidal momentum matrix :math:`A_G`
np.array[float[6]]: the centroidal dynamics velocity-dependent bias vector :math:`\dot{A}_G \dot{q}`
"""
return self.model.get_centroidal_dynamics()
def update(self, q=None, dq=None, ddq=None, update_model=False):
"""Update: move to the next step."""
self._states = dict()
@@ -55,10 +55,4 @@ class NLPTaskSolver(TaskSolver):
def solve(self):
"""Solve the priority task."""
if self.task.tasks:
for soft_task in self.task.tasks:
As = np.vstack([np.dot(np.sqrt(task.weight), task.A) for task in soft_task])
bs = np.vstack([np.dot(np.sqrt(task.weight), task.b) for task in soft_task])
# x = self.solver.optimize(P=As.T.dot(As), q=-bs.T.dot(), G=, h=, A=, b=)
else:
pass
pass
@@ -1,6 +1,58 @@
#!/usr/bin/env python
r"""Provide the Cartesian acceleration task.
The Cartesian acceleration task tries to impose a desired pose, velocity and acceleration profiles for a distal
link with respect to a base link, or world frame.
Before presenting the optimization problem, here is a small reminder. The acceleration is the time derivative of
the velocity, i.e. :math:`a = \frac{dv}{dt}` where the cartesian velocities are related to joint velocities by
:math:`v = J(q) \dot{q}` where :math:`J(q)` is the Jacobian, thus deriving that expression wrt time gives us:
.. math:: a = \frac{d}{dt} v = \frac{d}{dt} J(q) \dot{q} = J(q) \ddot{q} + \dot{J}(q) \dot{q}.
Now, we can formulate our minimization problem as:
.. math:: || J(q) \ddot{q} + \dot{J} \dot{q} - (a_d + K_d (v_d - v) + K_p e) ||^2,
where :math:`\ddot{q}` are the joint accelerations being optimized, :math:`a_d` are the desired cartesian
accelerations, :math:`v_d = [v_d^\top, \omega_d^\top]^\top` are the desired cartesian velocities, :math:`v` are the
current cartesian velocities of the distal link wrt the base, :math:`J(q) \in \mathbb{R}^{6 \times N}` is the
Jacobian taken from the base to the distal link, :math:`K_p` and :math:`K_d` are the stiffness and damping gains
respectively, :math:`e \in \mathbb{R}^{6}` is the error which is the concatenation of the position error given by
:math:`e_{p} = (x_d - x)` (with :math:`x_d` being the desired position, and :math:`x` the current position), and the
orientation error given by (if expressed as quaternions :math:`o = {s, v}` where :math:`s` is the real scalar part,
and :math:`v` is the vector part) :math:`e_{o} = s v_d - s_d v - v_d \cross v`, and :math:`\dot{x}_d` is the
desired cartesian velocity for the distal link with respect to the base link.
The above formulation is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting
:math:`A = J(q)`, :math:`x = \ddot{q}`, and :math:`b = - \dot{J} \dot{q} + (a_d + K_d (v_d - v) + K_p e)`.
This task can, for instance, be used for foot pose tracking when this one is not in contact with the ground. If
the foot is in contact, we switch to a foot damping task which can be achieved by setting
:math:`a_d = v_d = e = 0` and thus we are trying to solve :math:`||J(q) \ddot{q} - \dot{J} \dot{q} - K_d v_d||^2`.
Inverse dynamics
----------------
Once the optimal joint accelerations :math:`\ddot{q}^*` have been computed, we can use inverse dynamics to
compute the corresponding torques to apply on the joints. This is given by:
.. math:: \tau = H(q) \ddot{q} + N(q,\dot{q)}
where :math:`H(q)` is the inertia joint matrix, and N(q, \dot{q}) is a vector force that accounts for all the
other non-linear forces acting on the system (Coriolis, centrifugal, gravity, external forces, friction, etc.).
Important notes:
- You don't have to specify the whole pose, you can also only specify the position or orientation.
- You can also only specify the desired cartesian accelerations by setting `kp` and `kd` to zero; you don't have
neither to provide the desired cartesian velocities, position or orientation.
.. seealso:: `tasks/velocity/cartesian.py` and `tasks/torque/cartesian_impedance_control.py`
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -8,11 +60,10 @@ References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
# TODO: finish to implement this
import numpy as np
from pyrobolearn.priorities.tasks import JointAccelerationTask
from pyrobolearn.utils.transformation import quaternion_error
__author__ = "Brian Delhaisse"
@@ -31,22 +82,29 @@ class CartesianAccelerationTask(JointAccelerationTask):
The Cartesian acceleration task tries to impose a desired pose, velocity and acceleration profiles for a distal
link with respect to a base link, or world frame.
Before presenting the optimization problem, a small reminder. The acceleration is the time derivative of the
velocity, i.e. :math:`a = \frac{dv}{dt}` where the cartesian velocities are related to joint velocities by
Before presenting the optimization problem, here is a small reminder. The acceleration is the time derivative of
the velocity, i.e. :math:`a = \frac{dv}{dt}` where the cartesian velocities are related to joint velocities by
:math:`v = J(q) \dot{q}` where :math:`J(q)` is the Jacobian, thus deriving that expression wrt time gives us:
.. math:: a = \frac{d}{dt} v = \frac{d}{dt} J(q) \dot{q} = J(q) \ddot{q} + \dot{J}(q) \dot{q}.
Now, we can formulate our minimization problem as:
.. math:: || J(q) \ddot{q} - \dot{J} \dot{q} - (a_d + K_d (v_d - v) + K_p e) ||^2,
.. math:: || J(q) \ddot{q} + \dot{J} \dot{q} - (a_d + K_d (v_d - v) + K_p e) ||^2,
where :math:`\ddot{q}` are the joint accelerations being optimized, :math:`a_d` is the desired cartesian
acceleration, :math:`v_d = [\omega_d^\top, v` is the desired cartesian velocity, ...
where :math:`\ddot{q}` are the joint accelerations being optimized, :math:`a_d` are the desired cartesian
accelerations, :math:`v_d = [v_d^\top, \omega_d^\top]^\top` are the desired cartesian velocities, :math:`v` are the
current cartesian velocities of the distal link wrt the base, :math:`J(q) \in \mathbb{R}^{6 \times N}` is the
Jacobian taken from the base to the distal link, :math:`K_p` and :math:`K_d` are the stiffness and damping gains
respectively, :math:`e \in \mathbb{R}^{6}` is the error which is the concatenation of the position error given by
:math:`e_{p} = (x_d - x)` (with :math:`x_d` being the desired pose, and :math:`x` the current pose), and the
orientation error given by (if expressed as quaternions :math:`o = {s, v}` where :math:`s` is the real scalar part,
and :math:`v` is the vector part) :math:`e_{o} = s v_d - s_d v - v_d \cross v`, and :math:`\dot{x}_d` is the
desired cartesian velocity for the distal link with respect to the base link.
The above formulation is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting
:math:`A = J(q)`, :math:`x = \ddot{q}`, and :math:`b = - \dot{J} \dot{q} - (a_d + K_d (v_d - v) + K_p e)`.
:math:`A = J(q)`, :math:`x = \ddot{q}`, and :math:`b = - \dot{J} \dot{q} + (a_d + K_d (v_d - v) + K_p e)`.
This task can, for instance, be used for foot pose tracking when this one is not in contact with the ground. If
the foot is in contact, we switch to a foot damping task which can be achieved by setting
@@ -62,14 +120,21 @@ class CartesianAccelerationTask(JointAccelerationTask):
.. math:: \tau = H(q) \ddot{q} + N(q,\dot{q)}
where :math:`H(q)` is the inertia joint matrix, and N(q, \dot{q}) is a vector force that accounts for all the
other forces acting on the system (Coriolis, centrifugal, gravity, external forces, friction, etc.).
other non-linear forces acting on the system (Coriolis, centrifugal, gravity, external forces, friction, etc.).
.. seealso:: `tasks/velocity/cartesian.py` and `tasks/torque/cartesian_impedance_control.py`
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model, distal_link, base_link=None, local_position=(0, 0, 0), x_desired=None,
dx_desired=None, kp=1., kd=1., weight=1., constraints=[]):
def __init__(self, model, distal_link, base_link=None, local_position=(0, 0, 0), desired_position=None,
desired_orientation=None, desired_linear_velocity=None, desired_angular_velocity=None,
desired_linear_acceleration=None, desired_angular_acceleration=None,
kp_position=1., kp_orientation=1., kd_linear=1., kd_angular=1., weight=1., constraints=[]):
"""
Initialize the task.
@@ -77,26 +142,393 @@ class CartesianAccelerationTask(JointAccelerationTask):
model (ModelInterface): model interface.
distal_link (int, str): distal link id or name.
base_link (int, str, None): base link id or name. If None, it will be the world.
local_position (np.array[3]): local position on the distal link.
x_desired (np.array[7], None): desired cartesian pose of distal link wrt the base.
dx_desired (np.array[6], None): desired cartesian velocity of distal link wrt the base.
kp (float, np.array[6,6]): stiffness gain.
weight (float, np.array[6,6]): weight scalar or matrix associated to the task.
constraints (list of Constraint): list of constraints associated with the task.
local_position (np.array[float[3]]): local position on the distal link.
desired_position (np.array[float[3]], None): desired position of distal link wrt the base. If None, it
will not be taken into account.
desired_orientation (np.array[float[4]], None): desired orientation (expressed as quaternion [x,y,z,w]) of
distal link wrt the base. If None, it will not be taken into account.
desired_linear_velocity (np.array[float[3]], None): desired linear velocity of distal link wrt the base.
If None, it will be set to zero.
desired_angular_velocity (np.array[float[3]], None): desired angular velocity of distal link wrt the base.
If None, it will be set to zero.
desired_linear_acceleration (np.array[float[3]], None): desired linear acceleration of distal link wrt
the base. If None, it will be set to zero.
desired_angular_acceleration (np.array[float[3]], None): desired angular acceleration of distal link wrt
the base. If None, it will be set to zero.
kp_position (float, np.array[float[3,3]]): position stiffness gain.
kp_orientation (float, np.array[float[3,3]]): orientation stiffness gain.
kd_linear (float, np.array[float[3,3]]): linear velocity damping gain.
kd_angular (float, np.array[float[3,3]]): angular velocity damping gain.
weight (float, np.array[float[6,6]], np.array[float[3,3]]): weight scalar or matrix associated to the task.
constraints (list[Constraint]): list of constraints associated with the task.
"""
super(CartesianAccelerationTask, self).__init__(model=model, weight=weight, constraints=constraints)
# set variables
self.distal_link = distal_link
self.base_link = base_link
# define variables
self.distal_link = self.model.get_link_id(distal_link)
self.base_link = self.model.get_link_id(base_link) if base_link is not None else base_link
self.local_position = local_position
# pose = (position, quaternion)
self.desired_pose = np.array([0]*6 + 1)
self.current_pose = np.array([0]*6 + 1)
if base_link is not None:
raise NotImplementedError("Currently, the base_link can only be set to the world (None).")
# velocity = (angular, linear)
self.desired_velocity = np.zeros(6)
self.current_velocity = np.zeros(6)
# gains
self.kp_position = kp_position
self.kp_orientation = kp_orientation
self.kd_linear = kd_linear
self.kd_angular = kd_angular
# acceleration = (angular, linear)
self.desired_acceleration = np.zeros(6)
# define desired references
self.desired_position = desired_position
self.desired_orientation = desired_orientation
self.desired_linear_velocity = desired_linear_velocity
self.desired_angular_velocity = desired_angular_velocity
self.desired_linear_acceleration = desired_linear_acceleration
self.desired_angular_acceleration = desired_angular_acceleration
# first update
self.update()
##############
# Properties #
##############
@property
def desired_position(self):
"""Get the desired cartesian position for the distal link wrt the base."""
return self._des_pos
@desired_position.setter
def desired_position(self, position):
"""Set the desired cartesian position for the distal link wrt the base."""
if position is not None:
if not isinstance(position, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired position to be a np.array, instead got: "
"{}".format(type(position)))
position = np.asarray(position)
if len(position) != 3:
raise ValueError("Expecting the given desired position array to be of length 3, but instead got: "
"{}".format(len(position)))
self._des_pos = position
@property
def desired_orientation(self):
"""Get the desired cartesian orientation (expressed as a quaternion [x,y,z,w]) for the distal link wrt the
base."""
return self._des_quat
@desired_orientation.setter
def desired_orientation(self, orientation):
"""Set the desired cartesian orientation (expressed as a quaternion [x,y,z,w]) for the distal link wrt the
base."""
if orientation is not None:
if not isinstance(orientation, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired orientation to be a np.array, instead got: "
"{}".format(type(orientation)))
orientation = np.asarray(orientation)
if len(orientation) != 4:
raise ValueError(
"Expecting the given desired orientation array to be of length 4, but instead got: "
"{}".format(len(orientation)))
self._des_quat = orientation
@property
def desired_linear_velocity(self):
"""Get the desired cartesian linear velocity of the distal link wrt the base."""
return self._des_lin_vel
@desired_linear_velocity.setter
def desired_linear_velocity(self, velocity):
"""Set the desired cartesian linear velocity of the distal link wrt the base."""
if velocity is None:
velocity = np.zeros(3)
elif not isinstance(velocity, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired linear velocity to be a np.array, instead got: "
"{}".format(type(velocity)))
velocity = np.asarray(velocity)
if len(velocity) != 3:
raise ValueError("Expecting the given desired linear velocity array to be of length 3, but instead "
"got: {}".format(len(velocity)))
self._des_lin_vel = velocity
@property
def desired_angular_velocity(self):
"""Get the desired cartesian angular velocity of the distal link wrt the base."""
return self._des_ang_vel
@desired_angular_velocity.setter
def desired_angular_velocity(self, velocity):
"""Set the desired cartesian angular velocity of the distal link wrt the base."""
if velocity is None:
velocity = np.zeros(3)
elif not isinstance(velocity, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired angular velocity to be a np.array, instead got: "
"{}".format(type(velocity)))
velocity = np.asarray(velocity)
if len(velocity) != 3:
raise ValueError("Expecting the given desired angular velocity array to be of length 3, but instead "
"got: {}".format(len(velocity)))
self._des_ang_vel = velocity
@property
def desired_velocity(self):
"""Return the linear and angular velocity."""
return np.concatenate((self._des_lin_vel, self._des_ang_vel))
@property
def desired_linear_acceleration(self):
"""Get the desired cartesian linear acceleration of the distal link wrt the base."""
return self._des_lin_acc
@desired_linear_acceleration.setter
def desired_linear_acceleration(self, acceleration):
"""Set the desired cartesian linear acceleration of the distal link wrt the base."""
if acceleration is None:
acceleration = np.zeros(3)
elif not isinstance(acceleration, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired linear acceleration to be a np.array, instead got: "
"{}".format(type(acceleration)))
acceleration = np.asarray(acceleration)
if len(acceleration) != 3:
raise ValueError("Expecting the given desired linear acceleration array to be of length 3, but instead "
"got: {}".format(len(acceleration)))
self._des_lin_acc = acceleration
@property
def desired_angular_acceleration(self):
"""Get the desired cartesian angular acceleration of the distal link wrt the base."""
return self._des_ang_acc
@desired_angular_acceleration.setter
def desired_angular_acceleration(self, acceleration):
"""Set the desired cartesian angular acceleration of the distal link wrt the base."""
if acceleration is None:
acceleration = np.zeros(3)
elif not isinstance(acceleration, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired angular acceleration to be a np.array, instead got: "
"{}".format(type(acceleration)))
acceleration = np.asarray(acceleration)
if len(acceleration) != 3:
raise ValueError("Expecting the given desired angular acceleration array to be of length 3, but instead "
"got: {}".format(len(acceleration)))
self._des_ang_acc = acceleration
@property
def desired_acceleration(self):
"""Return the linear and angular acceleration."""
return np.concatenate((self._des_lin_acc, self._des_ang_acc))
@property
def x_desired(self):
"""Get the desired cartesian pose for the distal link wrt to the base."""
position = self.desired_position
orientation = self.desired_orientation
if position is not None:
if orientation is not None:
return np.concatenate((position, orientation))
return position
return orientation
@x_desired.setter
def x_desired(self, x_d):
"""Set the desired cartesian pose for the distal link wrt to the base."""
if x_d is not None:
if not isinstance(x_d, (np.ndarray, list, tuple)):
raise TypeError(
"Expecting the given desired pose to be a np.array, instead got: {}".format(type(x_d)))
x_d = np.asarray(x_d)
if len(x_d) == 3: # only position is provided
x_d = np.concatenate((x_d, np.array([0., 0., 0., 1.])))
elif len(x_d) == 4: # only orientation is provided
x_d = np.concatenate((np.zeros(3), x_d))
if len(x_d) != 7:
raise ValueError("Expecting the given desired pose array to be of length 7 (3 for the position, "
"and 4 for the orientation expressed as a quaternion [x,y,z,w]), instead got a "
"length of: {}".format(len(x_d)))
self._des_pos = x_d[:3]
self._des_quat = x_d[3:]
@property
def dx_desired(self):
"""Get the desired cartesian velocity for the distal link wrt to the base."""
return np.concatenate((self._des_lin_vel, self._des_ang_vel))
@dx_desired.setter
def dx_desired(self, dx_d):
"""Set the desired cartesian velocity for the distal link wrt to the base."""
if dx_d is not None:
if not isinstance(dx_d, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired velocity to be a np.array, instead got: "
"{}".format(type(dx_d)))
dx_d = np.asarray(dx_d)
if len(dx_d) == 3: # assume that it is the linear velocity
dx_d = np.concatenate((dx_d, np.zeros(3)))
if len(dx_d) != 6:
raise ValueError("Expecting the given desired velocity array to be of length 6 (3 for the linear "
"and 3 for the angular part), instead got a length of: {}".format(len(dx_d)))
self._des_lin_vel = dx_d[:3]
self._des_ang_vel = dx_d[3:]
@property
def ddx_desired(self):
"""Get the desired cartesian acceleration for the distal link wrt to the base."""
return np.concatenate((self._des_lin_acc, self._des_ang_acc))
@ddx_desired.setter
def ddx_desired(self, ddx_d):
"""Set the desired cartesian acceleration for the distal link wrt to the base."""
if ddx_d is not None:
if not isinstance(ddx_d, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired acceleration to be a np.array, instead got: "
"{}".format(type(ddx_d)))
ddx_d = np.asarray(ddx_d)
if len(ddx_d) == 3: # assume that it is the linear acceleration
ddx_d = np.concatenate((ddx_d, np.zeros(3)))
if len(ddx_d) != 6:
raise ValueError("Expecting the given desired acceleration array to be of length 6 (3 for the linear "
"and 3 for the angular part), instead got a length of: {}".format(len(ddx_d)))
self._des_lin_acc = ddx_d[:3]
self._des_ang_acc = ddx_d[3:]
@property
def kp_position(self):
"""Return the position stiffness gain."""
return self._kp_pos
@kp_position.setter
def kp_position(self, kp):
"""Set the position stiffness gain."""
if kp is None:
kp = 1.
if not isinstance(kp, (float, int, np.ndarray)):
raise TypeError("Expecting the given position stiffness gain kp to be an int, float, np.array, instead "
"got: {}".format(type(kp)))
if isinstance(kp, np.ndarray) and kp.shape != (3, 3):
raise ValueError("Expecting the given position stiffness gain matrix kp to be of shape {}, but instead "
"got shape: {}".format((3, 3), kp.shape))
self._kp_pos = kp
@property
def kp_orientation(self):
"""Return the orientation stiffness gain."""
return self._kp_quat
@kp_orientation.setter
def kp_orientation(self, kp):
"""Set the orientation stiffness gain."""
if kp is None:
kp = 1.
if not isinstance(kp, (float, int, np.ndarray)):
raise TypeError("Expecting the given orientation stiffness gain kp to be an int, float, np.array, "
"instead got: {}".format(type(kp)))
if isinstance(kp, np.ndarray) and kp.shape != (3, 3):
raise ValueError("Expecting the given orientation stiffness gain matrix kp to be of shape {}, but "
"instead got shape: {}".format((3, 3), kp.shape))
self._kp_quat = kp
@property
def kd_linear(self):
"""Return the linear velocity damping gain."""
return self._kd_lin
@kd_linear.setter
def kd_linear(self, kd):
"""Set the linear velocity damping gain."""
if kd is None:
kd = 1.
if not isinstance(kd, (float, int, np.ndarray)):
raise TypeError("Expecting the given linear velocity damping gain kd to be an int, float, np.array, "
"instead got: {}".format(type(kd)))
if isinstance(kd, np.ndarray) and kd.shape != (3, 3):
raise ValueError("Expecting the given linear velocity damping gain matrix kd to be of shape {}, but "
"instead got shape: {}".format((3, 3), kd.shape))
self._kd_lin = kd
@property
def kd_angular(self):
"""Return the angular velocity damping gain."""
return self._kd_ang
@kd_angular.setter
def kd_angular(self, kd):
"""Set the angular velocity damping gain."""
if kd is None:
kd = 1.
if not isinstance(kd, (float, int, np.ndarray)):
raise TypeError("Expecting the given angular velocity damping gain kd to be an int, float, np.array, "
"instead got: {}".format(type(kd)))
if isinstance(kd, np.ndarray) and kd.shape != (3, 3):
raise ValueError("Expecting the given angular velocity damping gain matrix kd to be of shape {}, but "
"instead got shape: {}".format((3, 3), kd.shape))
self._kd_ang = kd
###########
# Methods #
###########
def set_desired_references(self, x_des, dx_des=None, ddx_des=None, *args, **kwargs):
"""Set the desired references.
Args:
x_des (np.array[float[7]], None): desired cartesian pose (position and quaternion [x,y,z,w]) of distal
link wrt the base. If None, it will let the initial desired pose unchanged.
dx_des (np.array[float[6]], None): desired cartesian velocity of distal link wrt the base. If None, it
will let the initial desired velocities unchanged.
ddx_des (np.array[float[6]], None): desired cartesian acceleration of distal link wrt the base. If None,
it will let the initial desired accelerations unchanged.
"""
self.x_desired = x_des
self.dx_desired = dx_des
self.ddx_desired = ddx_des
def get_desired_references(self):
"""Return the desired references.
Returns:
np.array[float[7]]: desired cartesian pose (position and quaternion [x,y,z,w]) of distal link wrt the base.
np.array[float[6]]: desired cartesian velocity of distal link wrt the base.
np.array[float[6]]: desired cartesian acceleration of distal link wrt the base.
"""
return self.x_desired, self.dx_desired, self.ddx_desired
def _update(self):
"""
Update the task by computing the A matrix and b vector that will be used by the task solver.
"""
x = self.model.get_pose(link=self.distal_link, wrt_link=self.base_link)
self._A = self.model.get_jacobian(link=self.distal_link, wrt_link=self.base_link,
point=self.local_position) # shape: (6,N)
vel = self.model.get_velocity(link=self.distal_link)
jdotqdot = self.model.compute_JdotQdot(link=self.distal_link)
# b = - \dot{J} \dot{q} + (a_d + K_d (v_d - v) + K_p e)
b = -jdotqdot + self.desired_acceleration
if self._des_quat is None: # only position and/or velocities
if self._des_pos is None: # only velocities
self._b = b + np.concatenate((np.dot(self.kd_linear, (self._des_lin_vel - vel[:3])),
np.dot(self.kd_angular, (self._des_ang_vel - vel[3:]))))
else: # only position
self._A = self._A[:3]
# compute position error
error = (self._des_pos - x[:3])
# compute b vector
lin_vel = np.dot(self.kd_linear, (self._des_lin_vel - vel[:3]))
self._b = b[:3] + np.dot(self.kp_position, error) + lin_vel
elif self._des_pos is None: # only orientation
self._A = self._A[3:]
# compute orientation error
error = quaternion_error(quat_des=self._des_quat, quat_cur=x[3:])
# compute b vector
ang_vel = np.dot(self.kd_angular, (self._des_ang_vel - vel[3:]))
self._b = b[3:] + np.dot(self.kp_orientation, error) + ang_vel
else: # both
# compute position/orientation error
position_error = (self._des_pos - x[:3])
orientation_error = quaternion_error(quat_des=self._des_quat, quat_cur=x[3:])
# compute b vector
lin_vel = np.dot(self.kd_linear, (self._des_lin_vel - vel[:3]))
ang_vel = np.dot(self.kd_angular, (self._des_ang_vel - vel[3:]))
b_lin = np.dot(self.kp_position, position_error) + lin_vel
b_ang = np.dot(self.kp_orientation, orientation_error) + ang_vel
self._b = b + np.concatenate((b_lin, b_ang))
@@ -1,6 +1,56 @@
#!/usr/bin/env python
r"""Provide the Cartesian CoM acceleration task.
The CoM task tries to impose a desired position of the CoM with respect to the world frame.
.. math:: ||J_{CoM} \dot{q} - (K_p (x_d - x) + \dot{x}_d)||^2
where :math:`J_{CoM}` is the CoM Jacobian, :math:`\dot{q}` are the joint velocities being optimized, :math:`K_p`
is the stiffness gain, :math:`x_d` and :math:`x` are the desired and current cartesian CoM position
respectively, and :math:`\dot{x}_d` is the desired linear velocity of the CoM.
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A=J_{CoM}`,
:math:`x=\dot{q}`, and :math:`b = K_p (x_d - x) + \dot{x}_d`.
Note that you can only specify the center of mass linear velocity if you wish.
The CoM acceleration task tries to impose a desired pose, velocity and acceleration profiles for the CoM with respect
to the world frame.
Before presenting the optimization problem, here is a small reminder. The acceleration is the time derivative of
the velocity, i.e. :math:`a = \frac{dv}{dt}` where the cartesian velocities are related to joint velocities by
:math:`v_{CoM} = J_{CoM}(q) \dot{q}` where :math:`J_{CoM}(q)` is the CoM Jacobian, thus deriving that expression wrt
time gives us:
.. math:: a = \frac{d}{dt} v = \frac{d}{dt} J_{CoM}(q) \dot{q} = J_{CoM}(q) \ddot{q} + \dot{J}_{CoM}(q) \dot{q}.
Now, we can formulate our minimization problem as:
.. math:: || J_{CoM}(q) \ddot{q} + \dot{J}_{CoM} \dot{q} - (a_d + K_d (v_d - v) + K_p (x_d - x)) ||^2,
where :math:`\ddot{q}` are the joint accelerations being optimized, :math:`a_d` are the desired cartesian linear
accelerations, :math:`v_d` and :math:`v` are the desired and current cartesian linear velocities,
:math:`J_{CoM}(q) \in \mathbb{R}^{3 \times N}` is the CoM Jacobian, :math:`K_p` and :math:`K_d` are the stiffness and
damping gains respectively, and :math:`x_d` and :math:`x` are the desired and current cartesian CoM position
respectively.
The above formulation is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting
:math:`A = J_{CoM}(q)`, :math:`x = \ddot{q}`, and
:math:`b = - \dot{J}_{CoM} \dot{q} + (a_d + K_d (v_d - v) + K_p (x_d - x))`.
Inverse dynamics
----------------
Once the optimal joint accelerations :math:`\ddot{q}^*` have been computed, we can use inverse dynamics to
compute the corresponding torques to apply on the joints. This is given by:
.. math:: \tau = H(q) \ddot{q} + N(q,\dot{q)}
where :math:`H(q)` is the inertia joint matrix, and N(q, \dot{q}) is a vector force that accounts for all the
other non-linear forces acting on the system (Coriolis, centrifugal, gravity, external forces, friction, etc.).
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -10,7 +60,7 @@ References:
import numpy as np
from pyrobolearn.priorities.tasks import Task
from pyrobolearn.priorities.tasks import JointAccelerationTask
__author__ = "Brian Delhaisse"
@@ -23,17 +73,257 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class CoMAccelerationTask(Task):
class CoMAccelerationTask(JointAccelerationTask):
r"""CoM Acceleration Task
The CoM task tries to impose a desired position of the CoM with respect to the world frame.
.. math:: ||J_{CoM} \dot{q} - (K_p (x_d - x) + \dot{x}_d)||^2
where :math:`J_{CoM}` is the CoM Jacobian, :math:`\dot{q}` are the joint velocities being optimized, :math:`K_p`
is the stiffness gain, :math:`x_d` and :math:`x` are the desired and current cartesian CoM position
respectively, and :math:`\dot{x}_d` is the desired linear velocity of the CoM.
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A=J_{CoM}`,
:math:`x=\dot{q}`, and :math:`b = K_p (x_d - x) + \dot{x}_d`.
Note that you can only specify the center of mass linear velocity if you wish.
The CoM acceleration task tries to impose a desired pose, velocity and acceleration profiles for the CoM wrt the
world frame.
Before presenting the optimization problem, here is a small reminder. The acceleration is the time derivative of
the velocity, i.e. :math:`a = \frac{dv}{dt}` where the cartesian velocities are related to joint velocities by
:math:`v_{CoM} = J_{CoM}(q) \dot{q}` where :math:`J_{CoM}(q)` is the CoM Jacobian, thus deriving that expression
wrt time gives us:
.. math:: a = \frac{d}{dt} v = \frac{d}{dt} J_{CoM}(q) \dot{q} = J_{CoM}(q) \ddot{q} + \dot{J}_{CoM}(q) \dot{q}.
Now, we can formulate our minimization problem as:
.. math:: || J_{CoM}(q) \ddot{q} + \dot{J}_{CoM} \dot{q} - (a_d + K_d (v_d - v) + K_p (x_d - x)) ||^2,
where :math:`\ddot{q}` are the joint accelerations being optimized, :math:`a_d` are the desired cartesian linear
accelerations, :math:`v_d` and :math:`v` are the desired and current cartesian linear velocities,
:math:`J_{CoM}(q) \in \mathbb{R}^{3 \times N}` is the CoM Jacobian, :math:`K_p` and :math:`K_d` are the stiffness
and damping gains respectively, and :math:`x_d` and :math:`x` are the desired and current cartesian CoM position
respectively.
The above formulation is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting
:math:`A = J_{CoM}(q)`, :math:`x = \ddot{q}`, and
:math:`b = - \dot{J}_{CoM} \dot{q} + (a_d + K_d (v_d - v) + K_p (x_d - x))`.
Inverse dynamics
----------------
Once the optimal joint accelerations :math:`\ddot{q}^*` have been computed, we can use inverse dynamics to
compute the corresponding torques to apply on the joints. This is given by:
.. math:: \tau = H(q) \ddot{q} + N(q,\dot{q)}
where :math:`H(q)` is the inertia joint matrix, and N(q, \dot{q}) is a vector force that accounts for all the
other non-linear forces acting on the system (Coriolis, centrifugal, gravity, external forces, friction, etc.).
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model, constraints=[]):
def __init__(self, model, desired_position=None, desired_velocity=None, desired_acceleration=None, kp=1., kd=1.,
weight=1., constraints=[]):
"""
Initialize the task.
Args:
model (ModelInterface): model interface
constraints (list of Constraint): list of constraints associated with the task.
model (ModelInterface): model interface.
desired_position (np.array[float[3]], None): desired CoM position. If None, it will be set to 0.
desired_velocity (np.array[float[3]], None): desired CoM linear velocity. If None, it will be set to 0.
desired_acceleration (np.array[float[3]], None): desired CoM linear acceleration. If None, it will be set
to 0.
kp (float, np.array[float[3,3]]): position gain.
kd (float, np.array[float[3,3]]): linear velocity gain.
weight (float, np.array[float[3,3]]): weight scalar or matrix associated to the task.
constraints (list[Constraint]): list of constraints associated with the task.
"""
super(CoMAccelerationTask, self).__init__(model=model, constraints=constraints)
super(CoMAccelerationTask, self).__init__(model=model, weight=weight, constraints=constraints)
# define variable
self.kp = kp
self.kd = kd
# define desired references
self.desired_position = desired_position
self.desired_velocity = desired_velocity
self.desired_acceleration = desired_acceleration
# first update
self.update()
##############
# Properties #
##############
@property
def desired_position(self):
"""Get the desired CoM position."""
return self._des_pos
@desired_position.setter
def desired_position(self, position):
"""Set the desired CoM position."""
if position is not None:
if not isinstance(position, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired position to be a np.array, instead got: "
"{}".format(type(position)))
position = np.asarray(position)
if len(position) != 3:
raise ValueError("Expecting the given desired position array to be of length 3, but instead got: "
"{}".format(len(position)))
self._des_pos = position
@property
def desired_velocity(self):
"""Get the desired CoM linear velocity."""
return self._des_vel
@desired_velocity.setter
def desired_velocity(self, velocity):
"""Set the desired CoM linear velocity."""
if velocity is None:
velocity = np.zeros(3)
elif not isinstance(velocity, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired linear velocity to be a np.array, instead got: "
"{}".format(type(velocity)))
velocity = np.asarray(velocity)
if len(velocity) != 3:
raise ValueError("Expecting the given desired linear velocity array to be of length 3, but instead "
"got: {}".format(len(velocity)))
self._des_vel = velocity
@property
def desired_acceleration(self):
"""Get the desired CoM linear acceleration."""
return self._des_acc
@desired_acceleration.setter
def desired_acceleration(self, acceleration):
"""Set the desired CoM linear acceleration."""
if acceleration is None:
acceleration = np.zeros(3)
elif not isinstance(acceleration, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired linear acceleration to be a np.array, instead got: "
"{}".format(type(acceleration)))
acceleration = np.asarray(acceleration)
if len(acceleration) != 3:
raise ValueError("Expecting the given desired linear acceleration array to be of length 3, but instead "
"got: {}".format(len(acceleration)))
self._des_acc = acceleration
@property
def x_desired(self):
"""Get the desired CoM position."""
return self._des_pos
@x_desired.setter
def x_desired(self, x_d):
"""Set the desired CoM position."""
self.desired_position = x_d
@property
def dx_desired(self):
"""Get the desired CoM linear velocity."""
return self._des_vel
@dx_desired.setter
def dx_desired(self, dx_d):
"""Set the desired CoM linear velocity."""
self.desired_velocity = dx_d
@property
def ddx_desired(self):
"""Get the desired CoM linear acceleration."""
return self._des_acc
@ddx_desired.setter
def ddx_desired(self, ddx_d):
"""Set the desired CoM linear acceleration."""
self.desired_acceleration = ddx_d
@property
def kp(self):
"""Return the position gain."""
return self._kp
@kp.setter
def kp(self, kp):
"""Set the position gain."""
if kp is None:
kp = 1.
if not isinstance(kp, (float, int, np.ndarray)):
raise TypeError("Expecting the given position gain kp to be an int, float, np.array, instead got: "
"{}".format(type(kp)))
if isinstance(kp, np.ndarray) and kp.shape != (3, 3):
raise ValueError("Expecting the given position gain matrix kp to be of shape {}, but instead got "
"shape: {}".format((self.x_size, self.x_size), kp.shape))
self._kp = kp
@property
def kd(self):
"""Return the linear velocity gain."""
return self._kd
@kd.setter
def kd(self, kd):
"""Set the linear velocity gain."""
if kd is None:
kd = 1.
if not isinstance(kd, (float, int, np.ndarray)):
raise TypeError("Expecting the given linear velocity gain kd to be an int, float, np.array, "
"instead got: {}".format(type(kd)))
if isinstance(kd, np.ndarray) and kd.shape != (3, 3):
raise ValueError("Expecting the given linear velocity gain matrix kd to be of shape {}, but "
"instead got shape: {}".format((3, 3), kd.shape))
self._kd = kd
###########
# Methods #
###########
def set_desired_references(self, x_des, dx_des=None, ddx_des=None, *args, **kwargs):
"""Set the desired references.
Args:
x_des (np.array[float[3]], None): desired CoM position. If None, it will be set to 0.
dx_des (np.array[float[3]], None): desired CoM linear velocity. If None, it will be set to 0.
ddx_des (np.array[float[3]], None): desired CoM linear velocity. If None, it will be set to 0.
"""
self.x_desired = x_des
self.dx_desired = dx_des
self.ddx_desired = ddx_des
def get_desired_references(self):
"""Return the desired references.
Returns:
np.array[float[3]]: desired CoM position.
np.array[float[3]]: desired CoM linear velocity.
"""
return self.x_desired, self.dx_desired, self.ddx_desired
def _update(self):
"""
Update the task by computing the A matrix and b vector that will be used by the task solver.
"""
self._A = self.model.get_com_jacobian(full=False) # shape: (3, N)
jdotqdot = self.model.compute_com_JdotQdot()[:3] # shape: (3,)
vel = self.model.get_com_velocity() # shape: (3,)
self._b = -jdotqdot + self._des_acc + np.dot(self.kd, (self._des_vel - vel)) # shape: (3,)
if self._des_pos is not None:
x = self.model.get_com_position()
self._b = self._b + np.dot(self.kp, (self._des_pos - x)) # shape: (3,)
@@ -1,6 +1,23 @@
#!/usr/bin/env python
r"""Provide the contact acceleration task.
The contact acceleration task tries to minimize the dynamic movement of a contact link:
.. math:: || C (J_c(q) \ddot{q} + \dot{J}_c(q) \dot{q}) ||^2,
where :math:`C \in \mathbb{R}^{6 \times 6}` is the contact matrix (=a diagonal selector matrix where the entries
are 1 for cartesian velocities that we wish to minimize such that the link doesn't move, and 0 for cartesian
velocities that are free to change), :math:`J_c(q)` is the contact Jacobian (the Jacobian from the world frame to
the contact point expressed in the distal link frame (i.e. the link which is in contact)),
:math:`\ddot{q}, \dot{q}` are respectively the joint accelerations being optimized and the joint velocities.
Note that because the jacobian is expressed in the distal link frame, the entries of the contact matrix specify
the accelerations/velocities in that particular frame. For instance, [0,0,1,0,0,0] means that the z
linear acceleration/velocity (where z is the z axis of the distal link frame) should be fixed.
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A = C J_c(q)`,
:math:`x = \ddot{q}`, and :math:`b = - C \dot{J}_c(q) \dot{q}`.
This task is useful for instance if we want to keep the feet in contact with the ground.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -9,8 +26,10 @@ References:
"""
import numpy as np
from scipy.linalg import block_diag
from pyrobolearn.priorities.tasks import Task
from pyrobolearn.priorities.tasks import JointAccelerationTask
from pyrobolearn.utils.transformation import get_matrix_from_quaternion
__author__ = "Brian Delhaisse"
@@ -23,17 +42,112 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class ContactAccelerationTask(Task):
class ContactAccelerationTask(JointAccelerationTask):
r"""Contact Acceleration Task
The contact acceleration task tries to minimize the dynamic movement of a contact link:
.. math:: || C (J_c(q) \ddot{q} + \dot{J}_c(q) \dot{q}) ||^2,
where :math:`C \in \mathbb{R}^{6 \times 6}` is the contact matrix (=a diagonal selector matrix where the entries
are 1 for cartesian velocities that we wish to minimize such that the link doesn't move, and 0 for cartesian
velocities that are free to change), :math:`J_c(q)` is the contact Jacobian (the Jacobian from the world frame to
the contact point expressed in the distal link frame (i.e. the link which is in contact)),
:math:`\ddot{q}, \dot{q}` are respectively the joint accelerations being optimized and the joint velocities.
Note that because the jacobian is expressed in the distal link frame, the entries of the contact matrix specify
the accelerations/velocities in that particular frame. For instance, [0,0,1,0,0,0] means that the z
linear acceleration/velocity (where z is the z axis of the distal link frame) should be fixed.
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A = C J_c(q)`,
:math:`x = \ddot{q}`, and :math:`b = - C \dot{J}_c(q) \dot{q}`.
This task is useful for instance if we want to keep the feet in contact with the ground.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model, constraints=[]):
def __init__(self, model, distal_link, contact_matrix=1., weight=1., constraints=[]):
"""
Initialize the task.
Args:
model (ModelInterface): model interface
model (ModelInterface): model interface.
distal_link (int, str): distal link id or name.
contact_matrix (np.array[float[6]], np.array[float[6,6]], None): contact selector matrix (=a diagonal
square matrix). You can specify only the diagonal elements if you wish. If None, by default it will be
set to the identity matrix.
weight (float, np.array[float[6,6]]): weight scalar or matrix associated to the task.
constraints (list of Constraint): list of constraints associated with the task.
"""
super(ContactAccelerationTask, self).__init__(model=model, constraints=constraints)
super(ContactAccelerationTask, self).__init__(model=model, weight=weight, constraints=constraints)
# set variable
self.distal_link = self.model.get_link_id(distal_link)
self.contact_matrix = contact_matrix
# set QP vector
self._b = np.zeros(6)
# first update
self.update()
##############
# Properties #
##############
@property
def contact_matrix(self):
"""Return the contact selector matrix."""
return self._contact_matrix
@contact_matrix.setter
def contact_matrix(self, matrix):
"""Set the contact selector matrix."""
if matrix is None:
matrix = np.identity(6)
# check contact matrix type
if not isinstance(matrix, (int, float, np.ndarray)):
raise TypeError("Expecting the given contact matrix to be an int, float, or diagonal np.array, instead "
"got: {}".format(type(matrix)))
# if numpy array, check its shape and make sure it is a diagonal matrix
if isinstance(matrix, np.ndarray):
if matrix.shape == (6,):
matrix = np.diag(matrix)
elif matrix.shape != (6, 6):
raise ValueError("Expecting the given contact matrix to be of shape (6,6), instead got a shape of: "
"{}".format(type(matrix)))
else:
# make sure the contact matrix is a diagonal matrix
matrix = np.diag(np.diag(matrix))
# set the contact matrix
self._contact_matrix = matrix
###########
# Methods #
###########
def _update(self):
"""
Update the task by computing the A matrix and b vector that will be used by the task solver.
"""
# get jacobians expressed in the world frame
jacobian = self.model.get_jacobian(link=self.distal_link)
jdotqdot = self.model.compute_JdotQdot(link=self.distal_link)
# express jacobians in the distal/contact link frame
orientation = self.model.get_orientation(link=self.distal_link) # shape: (4,)
rot = get_matrix_from_quaternion(orientation).T # shape: (3,3)
rot = block_diag(rot, rot) # shape: (6,6)
jacobian = rot.dot(jacobian)
jdotqdot = rot.dot(jdotqdot)
# set A and b
self._A = np.dot(self.contact_matrix, jacobian)
self._b = np.dot(self.contact_matrix, jdotqdot)
@@ -1,6 +1,18 @@
#!/usr/bin/env python
r"""Provide the postural acceleration task.
The postural task tries to bring the robot to a reference posture; that is, it minimizes the joint accelerations
such that it gets close to the specified posture (given by the desired joint positions, velocities, and
accelerations):
.. math:: || \ddot{q} - (\ddot{q}_d + K_d (\dot{q}_d - \dot{q}) + K_p (q_d - q)) ||^2,
where :math:`\ddot{q}, \dot{q}, q` are respectively the joint accelerations being optimized, joint velocities and
positions, :math:`K_p` and :math:`K_d` are the position and velocity gains respectively, and the subscript
:math:`d` means "desired".
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A=I`, :math:`x=\dot{q}`,
and :math:`b = \ddot{q}_d + K_d (\dot{q}_d - \dot{q}) + K_p (q_d - q)`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -10,7 +22,7 @@ References:
import numpy as np
from pyrobolearn.priorities.tasks import Task
from pyrobolearn.priorities.tasks import JointAccelerationTask
__author__ = "Brian Delhaisse"
@@ -23,17 +35,224 @@ __email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class PosturalAccelerationTask(Task):
class PosturalAccelerationTask(JointAccelerationTask):
r"""Postural Acceleration Task
The postural task tries to bring the robot to a reference posture; that is, it minimizes the joint accelerations
such that it gets close to the specified posture (given by the desired joint positions, velocities, and
accelerations):
.. math:: || \ddot{q} - (\ddot{q}_d + K_d (\dot{q}_d - \dot{q}) + K_p (q_d - q)) ||^2,
where :math:`\ddot{q}, \dot{q}, q` are respectively the joint accelerations being optimized, joint velocities and
positions, :math:`K_p` and :math:`K_d` are the position and velocity gains respectively, and the subscript
:math:`d` means "desired".
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A=I`, :math:`x=\dot{q}`,
and :math:`b = \ddot{q}_d + K_d (\dot{q}_d - \dot{q}) + K_p (q_d - q)`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
def __init__(self, model, constraints=[]):
def __init__(self, model, q_desired=None, dq_desired=None, ddq_desired=None, kp=1., kd=1., weight=1.,
constraints=[]):
"""
Initialize the task.
Args:
model (ModelInterface): model interface
constraints (list of Constraint): list of constraints associated with the task.
model (ModelInterface): model interface.
q_desired (np.array[float[N]], None): desired joint positions, where :math:`N` is the number of DoFs. If
None, it will not be considered.
dq_desired (np.array[float[N]], None): desired joint velocities, where :math:`N` is the number of DoFs.
If None, it will be set to 0.
ddq_desired (np.array[float[N]], None): desired joint accelerations, where :math:`N` is the number of DoFs.
If None, it will be set to 0.
kp (float, np.array[float[N,N]]): position gain(s).
kd (float, np.array[float[N,N]]): velocity gain(s).
weight (float, np.array[float[N,N]]): weight scalar or matrix associated to the task.
constraints (list[Constraint]): list of constraints associated with the task.
"""
super(PosturalAccelerationTask, self).__init__(model=model, constraints=constraints)
super(PosturalAccelerationTask, self).__init__(model=model, weight=weight, constraints=constraints)
# define variables
self.kp = kp
self.kd = kd
# define desired references
self.q_desired = q_desired
self.dq_desired = dq_desired
self.ddq_desired = ddq_desired
# first update
self.update()
##############
# Properties #
##############
@property
def q_desired(self):
"""Get the desired joint positions."""
return self._q_d
@q_desired.setter
def q_desired(self, q_d):
"""Set the desired joint positions."""
if q_d is not None:
if not isinstance(q_d, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired joint positions to be an instance of np.array, instead "
"got: {}".format(type(q_d)))
q_d = np.asarray(q_d)
if len(q_d) != self.x_size:
raise ValueError("Expecting the length of the given desired joint positions (={}) to be the same as "
"the number of DoFs (={})".format(len(q_d), self.x_size))
self._q_d = q_d
@property
def dq_desired(self):
"""Get the desired joint velocities."""
return self._dq_d
@dq_desired.setter
def dq_desired(self, dq_d):
"""Set the desired joint velocities."""
if dq_d is None:
dq_d = np.zeros(self.x_size)
if not isinstance(dq_d, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired joint velocities to be an instance of np.array, instead "
"got: {}".format(type(dq_d)))
dq_d = np.asarray(dq_d)
if len(dq_d) != self.x_size:
raise ValueError("Expecting the length of the given desired joint velocities (={}) to be the same as the "
"number of DoFs (={})".format(len(dq_d), self.x_size))
self._dq_d = dq_d
@property
def ddq_desired(self):
"""Get the desired joint velocities."""
return self._ddq_d
@ddq_desired.setter
def ddq_desired(self, ddq_d):
"""Set the desired joint velocities."""
if ddq_d is None:
ddq_d = np.zeros(self.x_size)
if not isinstance(ddq_d, (np.ndarray, list, tuple)):
raise TypeError("Expecting the given desired joint accelerations to be an instance of np.array, instead "
"got: {}".format(type(ddq_d)))
ddq_d = np.asarray(ddq_d)
if len(ddq_d) != self.x_size:
raise ValueError("Expecting the length of the given desired joint accelerations (={}) to be the same as "
"the number of DoFs (={})".format(len(ddq_d), self.x_size))
self._ddq_d = ddq_d
@property
def x_desired(self):
"""Get the desired joint positions."""
return self._q_d
@x_desired.setter
def x_desired(self, q_d):
"""Set the desired joint positions."""
self.q_desired = q_d
@property
def dx_desired(self):
"""Get the desired joint velocities."""
return self._dq_d
@dx_desired.setter
def dx_desired(self, dq_d):
"""Set the desired joint velocities."""
self.dq_desired = dq_d
@property
def ddx_desired(self):
"""Get the desired joint accelerations."""
return self._ddq_d
@ddx_desired.setter
def ddx_desired(self, ddq_d):
"""Set the desired joint accelerations."""
self.ddq_desired = ddq_d
@property
def kp(self):
"""Return the position gain."""
return self._kp
@kp.setter
def kp(self, kp):
"""Set the position gain."""
if kp is None:
kp = 1.
if not isinstance(kp, (float, int, np.ndarray)):
raise TypeError("Expecting the given position gain kp to be an int, float, np.array, instead got: "
"{}".format(type(kp)))
if isinstance(kp, np.ndarray) and kp.shape != (self.x_size, self.x_size):
raise ValueError("Expecting the given position gain matrix kp to be of shape {}, but instead got "
"shape: {}".format((self.x_size, self.x_size), kp.shape))
self._kp = kp
@property
def kd(self):
"""Return the velocity gain."""
return self._kd
@kd.setter
def kd(self, kd):
"""Set the velocity gain."""
if kd is None:
kd = 1.
if not isinstance(kd, (float, int, np.ndarray)):
raise TypeError("Expecting the given velocity gain kd to be an int, float, np.array, instead got: "
"{}".format(type(kd)))
if isinstance(kd, np.ndarray) and kd.shape != (self.x_size, self.x_size):
raise ValueError("Expecting the given velocity gain matrix kd to be of shape {}, but instead got "
"shape: {}".format((self.x_size, self.x_size), kd.shape))
self._kd = kd
###########
# Methods #
###########
def set_desired_references(self, x_des, dx_des=None, ddx_des=None, *args, **kwargs):
"""Set the desired references.
Args:
x_des (np.array[float[N]], None): desired joint positions, where :math:`N` is the number of DoFs. If None,
it will be set to 0.
dx_des (np.array[float[N]], None): desired joint velocities, where :math:`N` is the number of DoFs. If
None, it will be set to 0.
ddx_des (np.array[float[N]], None): desired joint accelerations, where :math:`N` is the number of DoFs. If
None, it will be set to 0.
"""
self.x_desired = x_des
self.dx_desired = dx_des
self.ddx_desired = ddx_des
def get_desired_references(self):
"""Return the desired references.
Returns:
np.array[float[N]]: desired joint positions.
np.array[float[N]]: desired joint velocities.
np.array[float[N]]: desired joint accelerations.
"""
return self.x_desired, self.dx_desired, self.ddx_desired
def _update(self):
"""
Update the task by computing the A matrix and b vector that will be used by the task solver.
"""
q = self.model.get_joint_positions()
dq = self.model.get_joint_velocities()
self._b = self._ddq_d + np.dot(self.kd, (self._dq_d - dq))
# update b vector
if self._q_d is not None:
self._b += np.dot(self.kp, (self._q_d - q)) # shape: (N,)
@@ -1,6 +1,8 @@
#!/usr/bin/env python
"""Provide the various dynamic tasks (i.e. objective functions) used in QP.
DEPRECATED.
References:
[1] "Quadratic Programming in Python" (https://scaron.info/blog/quadratic-programming-in-python.html), Caron, 2017
[2] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
@@ -1,6 +1,8 @@
#!/usr/bin/env python
"""Provide the various kinematic tasks (i.e. objective functions) used in QP.
DEPRECATED.
References:
[1] "Quadratic Programming in Python" (https://scaron.info/blog/quadratic-programming-in-python.html), Caron, 2017
[2] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
@@ -0,0 +1,79 @@
#!/usr/bin/env python
r"""Transform a given equality constraint into a task.
An equality constraint specified by :math:`Fx = k` is transformed to a soft task :math:`||Ax - b||^2`, where
:math:`A = F` and :math:`b = k`. This allows for the equality constraint to be lightly violated; by specifying the
weight :math:`W` we can specify how much the constraint should be satisfied.
"""
from pyrobolearn.priorities.tasks import Task
from pyrobolearn.priorities.constraints import EqualityConstraint
__author__ = "Brian Delhaisse"
__copyright__ = "Copyright 2019, PyRoboLearn"
__credits__ = ["Brian Delhaisse"]
__license__ = "GNU GPLv3"
__version__ = "1.0.0"
__maintainer__ = "Brian Delhaisse"
__email__ = "briandelhaisse@gmail.com"
__status__ = "Development"
class TaskFromConstraint(Task):
r"""Task From Equality Constraint
An equality constraint specified by :math:`Fx = k` is transformed to a soft task :math:`||Ax - b||_{W}^2`, where
:math:`A = F` and :math:`b = k`. This allows for the equality constraint to be lightly violated; by specifying the
weight :math:`W` we can specify how much the constraint should be satisfied.
"""
def __init__(self, constraint, weight=1., constraints=[]):
"""
Initialize the task.
Args:
constraint (EqualityConstraint): equality constraint.
weight (float, np.array[float[6,6]], np.array[float[3,3]]): weight scalar or matrix associated to the task.
constraints (list[Constraint]): list of constraints associated with the task.
"""
# set equality constraint
self.equality_constraint = constraint
# call superclass
super(TaskFromConstraint, self).__init__(model=constraint.model, weight=weight, constraints=constraints)
# first update
self.update()
##############
# Properties #
##############
@property
def equality_constraint(self):
"""Get the equality constraint."""
return self._constraint
@equality_constraint.setter
def equality_constraint(self, constraint):
"""Set the equality constraint."""
if not isinstance(constraint, EqualityConstraint):
raise TypeError("Expecting the given 'constraint' to be an instance of `EqualityConstraint`, but "
"instead got: {}".format(constraint))
self._constraint = constraint
###########
# Methods #
###########
def _update(self):
"""
Update the task by computing the A matrix and b vector that will be used by the task solver.
"""
# update equality constraint
self.equality_constraint.update()
# update A and b
self._A = self.equality_constraint.A_eq
self._b = self.equality_constraint.b_eq
@@ -1,15 +1,15 @@
#!/usr/bin/env python
r"""Provide the cartesian (velocity) task.
The cartesian task tries to impose a desired pose (position and orientation) of a distal link with respect to a
base link or the world frame. The minimization problem is given by:
The cartesian task tries to impose a desired pose (position and orientation) and velocity of a distal link with
respect to a base link or the world frame. The minimization problem is given by:
.. math:: || ^bJ_d(q) \dot{q} - (K_p e + \dot{x}_d) ||^2
where :math:`^bJ_d(q) \in \mathbb{R}^{6 \times N}` is the Jacobian taken from the base to the distal link,
:math:`\dot{q}` are the joint velocities being optimized, :math:`K_p` is the stiffness gain,
:math:`e \in \mathbb{R}^{6}` is the error which is the concatenation of the position error given by
:math:`e_{p} = (x_d - x)` (with :math:`x_d` being the desired pose, and :math:`x` the current pose), and the
:math:`e_{p} = (x_d - x)` (with :math:`x_d` being the desired position, and :math:`x` the current position), and the
orientation error given by (if expressed as quaternions :math:`o = {s, v}` where :math:`s` is the real scalar part,
and :math:`v` is the vector part) :math:`e_{o} = s v_d - s_d v - v_d \cross v`, and :math:`\dot{x}_d` is the
desired cartesian velocity for the distal link with respect to the base link.
@@ -48,22 +48,27 @@ __status__ = "Development"
class CartesianTask(JointVelocityTask):
r"""Cartesian (velocity) Task
The cartesian task tries to impose a desired pose (position and orientation) of a distal link with respect to a
base link or the world frame. The minimization problem is given by:
The cartesian task tries to impose a desired pose (position and orientation) and velocity of a distal link with
respect to a base link or the world frame. The minimization problem is given by:
.. math:: || ^bJ_d(q) \dot{q} - (K_p e + \dot{x}_d) ||^2
where :math:`^bJ_d(q) \in \mathbb{R}^{6 \times N}` is the Jacobian taken from the base to the distal link,
:math:`\dot{q}` are the joint velocities being optimized, :math:`K_p` is the stiffness gain,
:math:`e \in \mathbb{R}^{6}` is the error which is the concatenation of the position error given by
:math:`e_{p} = (x_d - x)` (with :math:`x_d` being the desired pose, and :math:`x` the current pose), and the
orientation error given by (if expressed as quaternions :math:`o = {s, v}` where :math:`s` is the real scalar part,
and :math:`v` is the vector part) :math:`e_{o} = s v_d - s_d v - v_d \cross v`, and :math:`\dot{x}_d` is the
:math:`e_{p} = (x_d - x)` (with :math:`x_d` being the desired position, and :math:`x` the current position), and
the orientation error given by (if expressed as quaternions :math:`o = {s, v}` where :math:`s` is the real scalar
part, and :math:`v` is the vector part) :math:`e_{o} = s v_d - s_d v - v_d \cross v`, and :math:`\dot{x}_d` is the
desired cartesian velocity for the distal link with respect to the base link.
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A = ^bJ_d(q)`,
:math:`x = \dot{q}`, and :math:`b = K_p e + \dot{x}_d`.
Important notes:
- You don't have to specify the whole pose, you can also only specify the position or orientation.
- You can also only specify the cartesian velocities without providing the cartesian position or orientation.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
@@ -91,7 +96,7 @@ class CartesianTask(JointVelocityTask):
If None, it will be set to zero.
kp_position (float, np.array[float[3,3]]): position stiffness gain.
kp_orientation (float, np.array[float[3,3]]): orientation stiffness gain.
weight (float, np.array[float[6,6]]): weight scalar or matrix associated to the task.
weight (float, np.array[float[6,6]], np.array[float[3,3]]): weight scalar or matrix associated to the task.
constraints (list[Constraint]): list of constraints associated with the task.
"""
super(CartesianTask, self).__init__(model=model, weight=weight, constraints=constraints)
@@ -304,6 +309,7 @@ class CartesianTask(JointVelocityTask):
def get_desired_references(self):
"""Return the desired references.
Returns:
np.array[float[7]]: desired cartesian pose (position and quaternion [x,y,z,w]) of distal link wrt the base.
np.array[float[6]]: desired cartesian velocity of distal link wrt the base.
+4 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
r"""Provide the center of mass velocity task.
The CoM task tries to impose a desired position of the CoM with respect to the world frame.
The CoM task tries to impose a desired position and (linear) velocity of the CoM with respect to the world frame.
.. math:: ||J_{CoM} \dot{q} - (K_p (x_d - x) + \dot{x}_d)||^2
@@ -39,7 +39,7 @@ __status__ = "Development"
class CoMTask(JointVelocityTask):
r"""Center of Mass Velocity Task
The CoM task tries to impose a desired position of the CoM with respect to the world frame.
The CoM task tries to impose a desired position and (linear) velocity of the CoM with respect to the world frame.
.. math:: ||J_{CoM} \dot{q} - (K_p (x_d - x) + \dot{x}_d)||^2
@@ -50,6 +50,8 @@ class CoMTask(JointVelocityTask):
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A=J_{CoM}`,
:math:`x=\dot{q}`, and :math:`b = K_p (x_d - x) + \dot{x}_d`.
Note that you can only specify the center of mass linear velocity if you wish.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
References:
@@ -28,7 +28,7 @@ __status__ = "Development"
class ManipulabilityTask(JointVelocityTask):
r"""Manipulability Task
The manipulability task implements a tasks that tries to maximize the manipulability measure given in [1]:
The manipulability task implements a task that tries to maximize the manipulability measure given in [1]:
.. math:: w(q) = \sqrt{ \det( J(q) W J(q)^\top ) }
@@ -3,13 +3,12 @@ r"""Provide the minimum acceleration task.
The minimum acceleration task tries to minimize the change in velocity, that is, it minimizes:
.. math:: || \dot{q}_t - \dot{q}_{t-1} ||^2
.. math:: || \dot{q} - \dot{q}_t ||^2
where :math:`\dot{q}_t` are the current joint velocities being optimized, and :math:`\dot{q}_{t-1}` are the
previous joint velocities.
where :math:`\dot{q}` are the joint velocities being optimized, and :math:`\dot{q}_t` are the current joint velocities.
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A = I`, :math:`x = \dot{q}`,
and :math:`b = \dot{q}_{t-1}`.
and :math:`b = \dot{q}_t`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -38,13 +37,13 @@ class MinAccelerationTask(JointVelocityTask):
The minimum acceleration task tries to minimize the change in velocity, that is, it minimizes:
.. math:: || \dot{q}_t - \dot{q}_{t-1} ||^2
.. math:: || \dot{q} - \dot{q}_t ||^2
where :math:`\dot{q}_t` are the current joint velocities being optimized, and :math:`\dot{q}_{t-1}` are the
previous joint velocities.
where :math:`\dot{q}` are the joint velocities being optimized, and :math:`\dot{q}_t` are the current joint
velocities.
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A = I`, :math:`x = \dot{q}`,
and :math:`b = \dot{q}_{t-1}`.
This is equivalent to the QP objective function :math:`||Ax - b||^2`, by setting :math:`A = I`,
:math:`x = \dot{q}`, and :math:`b = \dot{q}_t`.
The implementation of this class is inspired by [1] (which is licensed under the LGPLv2).
@@ -8,8 +8,6 @@ References:
- [1] "OpenSoT: A whole-body control library for the compliant humanoid robot COMAN", Rocchi et al., 2015
"""
# TODO: finish to implement this
import numpy as np
from pyrobolearn.priorities.tasks import JointVelocityTask
@@ -147,6 +147,8 @@ class PosturalTask(JointVelocityTask):
@kp.setter
def kp(self, kp):
"""Set the stiffness gain."""
if kp is None:
kp = 1.
if not isinstance(kp, (float, int, np.ndarray)):
raise TypeError("Expecting the given stiffness gain kp to be an int, float, np.array, instead got: "
"{}".format(type(kp)))