Giter Club home page Giter Club logo

optas's Introduction

OpTaS

Code style: black Lint Run tests Build documentation

OpTaS is an OPtimization-based TAsk Specification library for trajectory optimization and model predictive control.

In the past, OpTaS supported ROS from an internal module. This functionality, with additional updates, has now been moved to a dedicated repository: optas_ros.

Example

In this example we implement an optimization-based IK problem. The problem computes an optimal joint configuration $q^*\in\mathbb{R}^n$ given by

$$ q^* = \underset{q}{\text{arg}\min}~||q - q_N||^2\quad\text{subject to}\quad p(q) = p_g, q^-\leq q \leq q^+ $$

where

  • $q\in\mathbb{R}^n$ is the joint configuration for an $n$-dof robot (in our example, we use the KUKA LWR in the above figure with $n=7$),
  • $q_N\in\mathbb{R}^n$ is a nominal joint configuration,
  • $||\cdot||$ is the Euclidean norm,
  • $p: \mathbb{R}^n\rightarrow\mathbb{R}^3$ computes the end-effector position via the forward kinematics,
  • $p_g\in\mathbb{R}^3$ is a goal position, and
  • $q^-, q^+\in\mathbb{R}^n$ is the lower and upper joint position limits respectively.

The example problem has a quadratic cost function with nonlinear constraints. We use the nominal configuration $q_N$ as the initial seed for the problem.

The following example script showcases some of the main features of OpTaS: creating a robot model, building an optimization problem, passing the problem to a solver, computing an optimal solution, and visualizing the robot in a given configuration.

import os
import pathlib

import optas

# Specify URDF filename
cwd = pathlib.Path(__file__).parent.resolve()  # path to current working directory
urdf_filename = os.path.join(
    cwd, "robots", "kuka_lwr", "kuka_lwr.urdf"
)  # KUKA LWR, 7-DoF

# Setup robot model
robot = optas.RobotModel(urdf_filename=urdf_filename)
name = robot.get_name()

# Setup optimization builder
T = 1
builder = optas.OptimizationBuilder(T, robots=robot)

# Setup parameters
qn = builder.add_parameter("q_nominal", robot.ndof)
pg = builder.add_parameter("p_goal", 3)

# Constraint: end goal
q = builder.get_model_state(name, 0)
end_effector_name = "end_effector_ball"
p = robot.get_global_link_position(end_effector_name, q)
builder.add_equality_constraint("end_goal", p, pg)

# Cost: nominal configuration
builder.add_cost_term("nominal", optas.sumsqr(q - qn))

# Constraint: joint position limits
builder.enforce_model_limits(name)  # joint limits extracted from URDF

# Build optimization problem
optimization = builder.build()

# Interface optimization problem with a solver
solver = optas.CasADiSolver(optimization).setup("ipopt")
# solver = optas.ScipyMinimizeSolver(optimization).setup("SLSQP")

# Specify a nominal configuration
q_nominal = optas.deg2rad([0, 45, 0, -90, 0, -45, 0])

# Get end-effector position in nominal configuration
p_nominal = robot.get_global_link_position(end_effector_name, q_nominal)

# Specify a goal end-effector position
p_goal = p_nominal + optas.DM([0.0, 0.3, -0.2])

# Reset solver parameters
solver.reset_parameters({"q_nominal": q_nominal, "p_goal": p_goal})

# Reset initial seed
solver.reset_initial_seed({f"{name}/q": q_nominal})

# Compute a solution
solution = solver.solve()
q_solution = solution[f"{name}/q"]

# Visualize the robot
vis = optas.Visualizer(quit_after_delay=2.0)

# Draw goal position and start visualizer
vis.sphere(0.05, rgb=[0, 1, 0], position=p_goal.toarray().flatten().tolist())
# vis.robot(robot, q=q_nominal,display_link_names=True,show_links=True)   # nominal
vis.robot(robot, q=q_solution, display_link_names=True, show_links=True)  # solution

vis.start()

Run the example script example.py. Other examples, including dual-arm planning, Model Predictive Control, Trajectory Optimization, etc can be found in the example/ directory.

Support

The following operating systems and python versions are officially supported:

  • Ubuntu 20.04 and 22.04
    • Python 3.7, 3.8, 3.9
  • Windows
    • Python 3.8, 3.9
  • Mac OS
    • Python 3.9

Note that OpTaS makes use of dataclasses that was introduced in Python 3.7, and so Python versions from 3.6 and lower are not supported on any operating system. Other operating systems or higher Python versions will likely work. If you experience problems, please submit an issue.

Install

Make sure pip is up-to-date by running $ python -m pip install --upgrade pip.

Via pip

$ pip install pyoptas

Alternatively, you can also install OpTaS using:

$ python -m pip install 'optas @ git+https://github.com/cmower/optas.git'

From source

  1. $ git clone --recursive [email protected]:cmower/optas.git (if you do not want to build the documentation then the --recursive flag is not necessary)
  2. $ cd optas
  3. $ pip install .
    • if you want to run the examples use: $ pip install .[example]
    • if you want to run the tests use: $ pip install .[test]

Build documentation

  1. $ cd /path/to/optas/doc
  2. $ sudo apt install doxygen graphviz
  3. $ python gen_mainpage.py
  4. $ doxygen
  5. Open the documentation in either HTML or PDF:
    • html/index.html
    • latex/refman.pdf

Run tests

  1. $ cd /path/to/optas
  2. Each test can be run as follows
    • $ pytest tests/test_builder.py
    • $ pytest tests/test_examples.py
    • $ pytest tests/test_models.py
    • $ pytest tests/test_optas_utils.py
    • $ pytest tests/test_optimization.py
    • $ pytest tests/test_solver.py
    • $ pytest tests/test_spatialmath.py
    • $ pytest tests/test_sx_container.py

Known Issues

  • Loading robot models from xacro files is supported, however there can be issues if you are running this in a ROS agnositic environment. If you do not have ROS installed, then the xacro file should not contain ROS-specific features. For further details see here.
  • If NumPy ver 1.24 is installed, an AttributeError error is thrown when you try to solve an unconstrained problem with the OSQP interface. A temporary workaround is to add a constraint, e.g. x >= -1e9 where x is a decision variable. See details on the issue here and pull request here.

Citation

If you use OpTaS in your work, please consider including the following citation.

@inproceedings{mower23optas,
  author={Mower, Christopher E. and Moura, João and Behabadi, Nazanin Zamani and Vijayakumar, Sethu and Vercauteren, Tom and Bergeles, Christos},
  booktitle={2023 IEEE International Conference on Robotics and Automation (ICRA)},
  title={OpTaS: An Optimization-based Task Specification Library for Trajectory Optimization and Model Predictive Control},
  year={2023},
  volume={},
  number={},
  pages={9118-9124},
  doi={10.1109/ICRA48891.2023.10161272}
}

The preprint can be found on arXiv.

Contributing

We welcome contributions from the community. If you come across any issues or inacuracies in the documentation, please submit an issue. If you would like to contribute any new features, please fork the repository, and submit a pull request.

Acknowledgement

This research received funding from the European Union’s Horizon 2020 research and innovation program under grant agreement No. 101016985 (FAROS). Further, this work was supported by core funding from the Wellcome/EPSRC [WT203148/Z/16/Z; NS/A000049/1]. T. Vercauteren is supported by a Medtronic / RAEng Research Chair [RCSRF1819\7\34], and C. Bergeles by an ERC Starting Grant [714562]. This work has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 101017008, Enhancing Healthcare with Assistive Robotic Mobile Manipulation (HARMONY).

optas's People

Contributors

cmower avatar github-actions[bot] avatar joaomoura24 avatar mhubii avatar nazzb avatar tianhuanyu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

optas's Issues

Issue in OSQP interface

There seems to be an issue in the OSQP interface. See the example in optas_ros, here. When using OSQP, the controller does something quite weird.

New features for visualizer, and issue

The new visualizer is intended for debugging urdf's, and visualizing robot states. Currently, its use is fairly limited. This is some features that would be good to implement, and also a bug that needs fixing.

  • [bug] Visualize links. Currently I believe there is a bug in the following method. When I try to visualize the robot links, the center of the link is correctly positioned, but the cylinders that make up the axes are all in the wrong place. Update: I will just use lines for the axes rather than cylinders. See 617e216.

optas/optas/visualize.py

Lines 217 to 252 in ee6e02f

def create_cylinder_actor(start, end, radius):
s = cs.np.array(start)
e = cs.np.array(end)
a = e - s
l = cs.np.linalg.norm(a)
p = 0.5*a
y = a / l
temp = cs.np.random.random(3)
temp /= cs.np.linalg.norm(temp)
x = cs.np.cross(y, temp)
z = cs.np.cross(x, y)
T = cs.np.eye(4)
T[:3, 0] = x
T[:3, 1] = y
T[:3, 2] = z
T[:3, 3] = p
cylinder = vtkCylinderSource()
cylinder.SetRadius(radius)
cylinder.SetHeight(l)
cylinder.SetResolution(20)
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(cylinder.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
transform = vtk.vtkTransform()
transform.SetMatrix(T.flatten().tolist())
actor.SetUserTransform(transform)
return actor

  • [feature] Support materials from the urdf. Currently, links are visualized in white. The colors and potentially textures should be supported.
  • [feature] Visualize additional objects (e.g. boxes/spheres).
  • [feature] Visualize robot motion, i.e. a planned trajectory. Option: video-link motion, and also a static figure where older states decrease the opacity of the robot.
  • [feature] Support loading several robots at once.
  • [feature] Support visualization of task models/plans (e.g. a 3D trajectory represented by a sphere flying through space)
  • [feature] Support visualization of other meshes (e.g. tables)
  • [feature] The above may need a revamp of the RobotVisualizer class, and also the visualization.py example script will need to be updated

Wrong computation of the rotation in quaternion

Where are you doing the quaternion computations?
Are you computing the links orientation originally in quaternions or in rotation matrices?
There is a bug in the computation of the quaternions.
Here is a working example:

# Python standard lib
import os
import pathlib
import numpy as np

from scipy.spatial.transform import Rotation as R

# OpTaS
import optas

cwd = pathlib.Path(__file__).parent.resolve() # path to current working directory
link_ee = 'end_effector_ball'  # end-effector link name

# Setup robot
urdf_filename = os.path.join(cwd, 'robots', 'kuka_lwr.urdf')
robot = optas.RobotModel(
    urdf_filename=urdf_filename,
    time_derivs=[0],  # i.e. joint position/velocity trajectory
)
robot_name = robot.get_name()
print(robot_name)

q_nom = optas.DM.zeros(robot.ndof)
ori_rot_fnc = robot.get_global_link_rotation_function(link=link_ee)
ori_quat_fnc = robot.get_global_link_quaternion_function(link=link_ee)
print('-----------------------------')
rot_mat = ori_rot_fnc(q_nom)
print(rot_mat)
quat = ori_quat_fnc(q_nom)
print(quat)
print(R.from_matrix(rot_mat).as_quat())
print('-----------------------------')

As i am running all my code inside ROS and i can check things with Rviz, i noticed straight away that the quaternion reading was incorrect.
Looking at the rotation matrix, it seems correct - so my guess is that you are computing the orientation in rotation matrices and then there is a bug when transforming it to quaternions.

RobotModel methods should handle trajectories

For methods such as get_link_transform, currently, you can only handle trajectories if you get the function handles and give it an n. However, these methods should return a trajectory. For example, if you pass a trajectory of joint states to get_link_transform then you should get a trajectory of transforms (in this case it would make sense to return a list of 4-by-4 arrays). This interface would be similar to Peter Corkes robot toolbox.

Visualizer and draw_sphere are missing

Based on default branch, the example.py can not be run.
The error code is shown here:

Traceback (most recent call last):
  File "optas/example/example.py", line 66, in <module>
    vis = optas.RobotVisualizer(robot, q=q_solution, params=params)  # solution
AttributeError: module 'optas' has no attribute 'RobotVisualizer'

However, I don't find these classes in optas.

Optimize subset of joint angles

At the moment i do not see an obvious way of optimizing declaring only a subset of the joint angles for optimization.

Imagine that you load a dual arm system, but you only want to optimize the motion of one of the arms.

The issue is that the OptimizationBuilder declares all the joints as optimization variables, which is not desirable in this specific case.

Any idea?

SyntaxError: invalid syntax during Run tests after the installation

I install OpTaS, and couldn't pass the test. I change the python version of 3.6.9 or 3.8. But this error is still exits.
../../.local/lib/python3.6/site-packages/_pytest/assertion/rewrite.py:170: in exec_module
exec(co, module.dict)
tests/test_builder.py:2: in
import optas
optas/init.py:4: in
from .models import RobotModel, TaskModel
E File "", line 1
E (time_deriv=)
E ^
E SyntaxError: invalid syntax

Nice to have: deg 2 rad and viceversa

We usually use the numpy functions and it is a easy conversion, but maybe nice to have it from optas.

I realized that once i was trying to write down the simplest example possible with minimum use of other packages.
Code example:
deg2rad = @(deg) deg*(optas.pi/180.) % convert degrees into radians
rad2deg = @(rad) rad*(180./optas.pi) % convert radians into degrees

Error pip installing it

I got the following output when pip installing it:

Defaulting to user installation because normal site-packages is not writeable
Processing /home/joao/Documents/Projects/optas
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Requirement already satisfied: osqp in /home/joao/.local/lib/python3.8/site-packages (from optas==1.0.0) (0.6.2.post5)
Requirement already satisfied: cvxopt in /home/joao/.local/lib/python3.8/site-packages (from optas==1.0.0) (1.3.0)
Requirement already satisfied: urdf-parser-py in /home/joao/.local/lib/python3.8/site-packages (from optas==1.0.0) (0.0.4)
Requirement already satisfied: casadi in /home/joao/.local/lib/python3.8/site-packages (from optas==1.0.0) (3.5.5)
Requirement already satisfied: scipy in /home/joao/.local/lib/python3.8/site-packages (from optas==1.0.0) (1.7.1)
Requirement already satisfied: numpy in /home/joao/.local/lib/python3.8/site-packages (from optas==1.0.0) (1.21.2)
Requirement already satisfied: qdldl in /home/joao/.local/lib/python3.8/site-packages (from osqp->optas==1.0.0) (0.1.5.post2)
Requirement already satisfied: pyyaml in /usr/lib/python3/dist-packages (from urdf-parser-py->optas==1.0.0) (5.3.1)
Requirement already satisfied: lxml in /usr/lib/python3/dist-packages (from urdf-parser-py->optas==1.0.0) (4.5.0)
Building wheels for collected packages: optas
Building wheel for optas (pyproject.toml) ... done
Created wheel for optas: filename=optas-1.0.0-py3-none-any.whl size=20675 sha256=2852db080f184c009e10d96e47f70d2a9872d584e83c858ac2eae7472ea0aa47
Stored in directory: /tmp/pip-ephem-wheel-cache-aak1sstk/wheels/36/13/df/b6785d46fea69fd332a5834b56b576b3f47424963cace846ec
Successfully built optas
Installing collected packages: optas
Successfully installed optas-1.0.0
--- Logging error ---
Traceback (most recent call last):
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/utils/logging.py", line 177, in emit
self.console.print(renderable, overflow="ignore", crop=False, style=style)
File "/home/joao/.local/lib/python3.8/site-packages/pip/_vendor/rich/console.py", line 1752, in print
extend(render(renderable, render_options))
File "/home/joao/.local/lib/python3.8/site-packages/pip/_vendor/rich/console.py", line 1390, in render
for render_output in iter_render:
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/utils/logging.py", line 134, in rich_console
for line in lines:
File "/home/joao/.local/lib/python3.8/site-packages/pip/_vendor/rich/segment.py", line 245, in split_lines
for segment in segments:
File "/home/joao/.local/lib/python3.8/site-packages/pip/_vendor/rich/console.py", line 1368, in render
renderable = rich_cast(renderable)
File "/home/joao/.local/lib/python3.8/site-packages/pip/_vendor/rich/protocol.py", line 36, in rich_cast
renderable = cast_method()
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/self_outdated_check.py", line 130, in rich
pip_cmd = get_best_invocation_for_this_pip()
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/utils/entrypoints.py", line 58, in get_best_invocation_for_this_pip
if found_executable and os.path.samefile(
File "/usr/lib/python3.8/genericpath.py", line 101, in samefile
s2 = os.stat(f2)
FileNotFoundError: [Errno 2] No such file or directory: '/usr/bin/pip3.8'
Call stack:
File "/home/joao/.local/bin/pip3", line 8, in
sys.exit(main())
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/cli/main.py", line 70, in main
return command.main(cmd_args)
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/cli/base_command.py", line 101, in main
return self._main(args)
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/cli/base_command.py", line 223, in _main
self.handle_pip_version_check(options)
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/cli/req_command.py", line 148, in handle_pip_version_check
pip_self_version_check(session, options)
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/self_outdated_check.py", line 237, in pip_self_version_check
logger.info("[present-rich] %s", upgrade_prompt)
File "/usr/lib/python3.8/logging/init.py", line 1446, in info
self._log(INFO, msg, args, **kwargs)
File "/usr/lib/python3.8/logging/init.py", line 1589, in _log
self.handle(record)
File "/usr/lib/python3.8/logging/init.py", line 1599, in handle
self.callHandlers(record)
File "/usr/lib/python3.8/logging/init.py", line 1661, in callHandlers
hdlr.handle(record)
File "/usr/lib/python3.8/logging/init.py", line 954, in handle
self.emit(record)
File "/home/joao/.local/lib/python3.8/site-packages/pip/_internal/utils/logging.py", line 179, in emit
self.handleError(record)
Message: '[present-rich] %s'
Arguments: (UpgradePrompt(old='22.1.2', new='22.2.2'),)

Potential issue with Quaternion class

While I've been implementing unit tests, I realized that the way we implement Quaternion multiplication is "backwards". See the test below.

def test_mul_correct_output(self):
for _ in range(NUM_RANDOM):
quat0 = self._random_quaternion()
quat1 = self._random_quaternion()
quat_optas = quat0 * quat1
q0 = quat0.getquat().toarray().flatten()
q1 = quat1.getquat().toarray().flatten()
quat_lib = (Rot.from_quat(q1) * Rot.from_quat(q0)).as_quat()
assert isclose(quat_optas.getquat().toarray().flatten(), quat_lib)

I generate two random quaternions, then multiply them using optas.spatialmath methods, and then I perform the same calculation but with Scipy - and compare the output to check optas is getting the correct answer (obviously this is assuming Scipy always has the correct answer).

The issue is that Scipy has the opposite convention to optas. My thought is, should optas adopt the same convention as Scipy? Or should we stick with our own convention?

@joaomoura24, do you have any thoughts on this?

In terms of required effort to make the change, if we want to make the switch we need to modify the following methods.

optas/optas/spatialmath.py

Lines 496 to 506 in 3eeeaf3

def __mul__(self, quat):
"""Quaternion multiplication"""
assert isinstance(quat, Quaternion), "unsupported type"
x0, y0, z0, w0 = self.split()
x1, y1, z1, w1 = quat.split()
return Quaternion(
x1 * w0 + y1 * z0 - z1 * y0 + w1 * x0,
-x1 * z0 + y1 * w0 + z1 * x0 + w1 * y0,
x1 * y0 - y1 * x0 + z1 * w0 + w1 * z0,
-x1 * x0 - y1 * y0 - z1 * z0 + w1 * w0,
)

optas/optas/models.py

Lines 794 to 828 in 3eeeaf3

@arrayify_args
@listify_output
def get_global_link_quaternion(self, link, q):
"""Get a quaternion in the global frame."""
# Setup
assert link in self._urdf.link_map.keys(), "given link does not appear in URDF"
root = self._urdf.get_root()
quat = Quaternion(0.0, 0.0, 0.0, 1.0)
if link == root:
return quat.getquat()
# Iterate over joints in chain
for joint_name in self._urdf.get_chain(root, link, links=False):
joint = self._urdf.joint_map[joint_name]
xyz, rpy = self.get_joint_origin(joint)
if joint.type == "fixed":
quat = Quaternion.fromrpy(rpy) * quat
continue
joint_index = self._get_actuated_joint_index(joint.name)
qi = q[joint_index]
quat = Quaternion.fromrpy(rpy) * quat
if joint.type in {"revolute", "continuous"}:
quat = Quaternion.fromangvec(qi, self.get_joint_axis(joint)) * quat
elif joint.type == "prismatic":
pass
else:
raise JointTypeNotSupported(joint.type)
return quat.getquat()

optas/optas/models.py

Lines 834 to 840 in 3eeeaf3

@arrayify_args
@listify_output
def get_link_quaternion(self, link, q, base_link):
"""Get the quaternion defined in a given base frame."""
quat_L_W = Quaternion(self.get_global_link_quaternion(link, q))
quat_B_W = Quaternion(self.get_global_link_quaternion(base_link, q))
return (quat_L_W * quat_B_W.inv()).getquat()

One reason perhaps to stick with our current convention, however, is that if you look at how we compute general homogenous transforms (below) for robot links you can see it is the same way round as our current convention for quaternion multiplication.

optas/optas/models.py

Lines 736 to 742 in 3eeeaf3

@arrayify_args
@listify_output
def get_link_transform(self, link, q, base_link):
"""Get the link transform in a given base frame."""
T_L_W = self.get_global_link_transform(link, q)
T_B_W = self.get_global_link_transform(base_link, q)
return T_L_W @ invt(T_B_W)

Suggested feature: solve() should not return dictionary, rather it should return an object called Solution

Currently, the solve() method returns a dictionary containing the solution. I'm proposing here that we should implement a solution class that has the solution data packed inside but also comes with some useful methods.

For example, it could look something like the following.

import casadi as cs
from dataclasses import dataclass
from typing import Dict, List
from .models import Model

@dataclass
class Solution:
    solution_dict: Dict[str, cs.DM]
    success: bool

    def get_model_solution_state(self, name, t):
         return self.solution_dict[f{name}/q'][:, t]

    def interpolate(self, name, duration):
        pass  # todo

This would be a little re-work but I think it would help optas become more extensible. I think this would be worthwhile. @joaomoura24, if this means a lot of hassle you're end. We could still incorporate the feature but put a flag in the Solver class interface. E.g. we put a flag saying solution_return_type='dict' by default (and the return type is what it is currently), and if the user wants to get the solution as the Solution class (something like above) perhaps they put solution_return_type='soln_cls' or something. I prefer not to do this but happy to if it means a lot of work your end.

I've not been a fan of having to do solution[f'{self.name}/q'] all the time, and also solver.interpolate doesn't make so much sense so would be good to put that functionality in a place that makes more sense.

Finalize OPTAS for first pip release

License

  • Finalize license, ensure copyright notice is correctly labelled

Documentation

Scripts requiring documentation

  • builder.py
  • models.py
  • optimization.py
  • ros.py
  • solver.py
  • spatialmath.py
  • sx_container.py

Need also

  • some basic explanation of library
  • very basic example to demonstrate (also include in README)
  • list of requirements

Examples

Required examples

  • dual arm, 2 kukas, see dual_arm.py
  • task space planning optimization and MPC (e.g. port pushing example), see pushing.py
  • ROS interface, fine to be dependant on ROS-PyBullet interface (for examples, I think it is better to have a simple pybullet api)
  • small PyBullet API, see pybullet_api.py
  • planning example (i.e. show off trajectory optimization capability), see figure_eight_plan.py

Implement mixed-integer optimization class and example

Whilst we support discrete variables and (in theory) you can use optas for mixed-integer optimization, there does not exist a dedicated Optimization sub-class for handling this specific use-case. It would be good to create an optimization type in optimization.py.

Provisionally

class MixedIntegerNonlinearCostNonlinearConstrained(NonlinearCostNonlinearConstrained):
    """
        min f(x, z; p)
         x,z

            subject to

                k(x, z; p) = M(p).x + L(p).z + c(p) >= 0
                a(x, z; p) = A(p).x + G(p).z + b(p) == 0
                g(x, z; p) >= 0, and
                h(x, z; p) == 0
    """

It may also be worth implementing the QP cost and linear constrained versions (as in the other optimization types). However, my feeling is that there will only 1 or 2 solvers that we will interface with so probably they will handle the general case (above).

@joaomoura24, do you know what mixed-integer solvers CasADi interfaces with? Also, if there are any mixed-integer solvers that we can create solver interfaces. Some ideas here.

Need better function name for initial_configuration

The function

def initial_configuration(self, name, init=None, time_deriv=0, t0=0):

is useful for not only specifying initial configuration constraints but actually it can be used to specify, for example, final position/velocity configurations. However, calling initial_configuration when specifying a final state does not make sense. Indeed, intermediate configurations could also be specified.

I think it would be good to deprecate initial_configuration. However, quite a bit of internal code for other projects would likely need updating. So, I will add a new function with a more appropriate name and also a deprecation warning for initial_configuration with a plan to deprecate it at the first full release for optas.

Deprecating manipulability measure methods

@joaomoura24 I am planning to deprecate all the manipulability measure RobotModel methods in #37 . Will this affect your code base?

The reason I want to get rid of them is because (i) it's a lot to maintain, (ii) "manipulability" is somewhat ambiguous since there is other measures than the common Yoshikawa measure, and (iii) it's not such a difficult thing for a user to implement themselves given all the other methods we provide.

I think in the future it would be nice to revisit this and properly implement manipulability measure methods that handle all the cases given a nicer interface. For example, see here.

Put optas on PyPI

Unfortunately, pip install optas is taken. So perhaps, pip install pyoptas instead.

AttributeError: module 'xacro' has no attribute 'process'

When running the simple_joint_space_planner.py example in the master branch i get the following error related with the xacro functionality:

Traceback (most recent call last): File "example/simple_joint_space_planner.py", line 138, in <module> main() File "example/simple_joint_space_planner.py", line 102, in main kuka = KukaLBR() File "/home/joao/Documents/Projects/test/optas/example/pybullet_api.py", line 168, in __init__ urdf_string = xacro.process(xacro_filename) AttributeError: module 'xacro' has no attribute 'process'

Return status when infeasible

When the problem is infeasible, how can we catch that instead of just having the solver throwing an error into the terminal?
We might want to have situations that we rather have the solver fail but we don't necessarily want to shutdown the program.

Update experiment scripts

OpTaS has had several updates that mean the experiments now won't run unless you checkout on a previous commit. Perhaps also they should make use of the new pybullet_api.py script, rather than the ROS-PyBullet Interface.

TypeError: __init__() got an unexpected keyword argument 'layout'

When running the point_mass_planner.py from the master branch i get the following error to do with the Animate() method:

Traceback (most recent call last): File "example/point_mass_planner.py", line 168, in <module> main() File "example/point_mass_planner.py", line 165, in main Animate(animate).show() File "example/point_mass_planner.py", line 99, in __init__ self.fig, self.ax = plt.subplot_mosaic([['birdseye', 'position'], File "/home/joao/.local/lib/python3.8/site-packages/matplotlib/pyplot.py", line 1519, in subplot_mosaic fig = figure(**fig_kw) File "/home/joao/.local/lib/python3.8/site-packages/matplotlib/pyplot.py", line 797, in figure manager = new_figure_manager( File "/home/joao/.local/lib/python3.8/site-packages/matplotlib/pyplot.py", line 316, in new_figure_manager return _backend_mod.new_figure_manager(*args, **kwargs) File "/home/joao/.local/lib/python3.8/site-packages/matplotlib/backend_bases.py", line 3544, in new_figure_manager fig = fig_cls(*args, **kwargs) TypeError: __init__() got an unexpected keyword argument 'layout'

Naming convention violated for jacobian methods in models.py

Almost all the get methods in RobotModel use incorrect naming conventions. They all miss the word link. These should be replaced.

There is lots of code that is using the incorrect naming convention, so I think it would be best for a while to add deprecation warnings.

Visualizer missing functionality and updates

Currently the following are missing from the visualizer

  • the link names should be optionally displayed
  • currently the user can visualize the robot links, but cannot customize their properties
  • move functions like

    optas/optas/visualize.py

    Lines 19 to 51 in 17f1a18

    def line(start, end, rgb, alpha, linewidth):
    points = vtk.vtkPoints()
    points.InsertNextPoint(*start)
    points.InsertNextPoint(*end)
    line = vtk.vtkLine()
    line.GetPointIds().SetId(0, 0)
    line.GetPointIds().SetId(0, 1)
    lines = vtk.vtkCellArray()
    lines.InsertNextCell(line)
    polydata = vtk.vtkPolyData()
    polydata.SetPoints(points)
    polydata.SetLines(lines)
    mapper = vtk.vtkPolyDataMapper()
    mapper.SetInputData(polydata)
    actor = vtk.vtkActor()
    actor.SetMapper(mapper)
    actor.GetProperty().SetLineWidth(linewidth)
    if isinstance(rgb, list):
    assert len(rgb) == 3, f"rgb is incorrect length, got {len(rgb)} expected 3"
    actor.GetProperty().SetColor(*rgb)
    if alpha < 1.0:
    assert alpha >= 0.0, "the scalar alpha must be in the range [0, 1]"
    actor.GetProperty().SetOpacity(alpha)
    return actor
    into the main visualizer class, see #106
  • in

    optas/optas/visualize.py

    Lines 622 to 654 in 17f1a18

    if urdf_link.visual is None:
    continue
    material = urdf_link.visual.material
    rgb = None
    if isinstance(material.name, str) and material.name in material_names:
    rgba = get_material_rgba(urdf_link.visual.material.name)
    rgb = rgba[:3]
    if isinstance(geometry, Mesh):
    if geometry.filename.lower().endswith(".stl"):
    filename = geometry.filename
    if not os.path.exists(filename):
    relpath = robot_model.get_urdf_dirname()
    filename = os.path.join(relpath, filename)
    scale = None
    if geometry.scale is not None:
    scale = geometry.scale
    actors.append(
    stl(
    filename,
    scale=scale,
    rgb=rgb,
    alpha=alpha,
    position=position,
    orientation=orientation,
    euler_seq="xyz",
    euler_degrees=True,
    )
    )
    we potentially miss out other visuals, need to iterate over urdf_link.visuals.
  • Add documentation to visualize.py

Missing assert error message

In the quaternion class in spatialmath.py there is a bug in the constructor. The error message in the assert statement is missing.

Issue with differential IK

@joaomoura24, I am a bit confused with an example I'm trying to setup re differential IK. Could you take a look at the file: https://github.com/cmower/optas/blob/dev-diff-ik-example/example/linear_differential_ik_controller_example.py

When you run it using the position control, you will see that the robot doesn't keep up with the goal sphere. I expect some error but not as much as you can see in the example. When you change to velocity control, it does something but the motion doesn't look quite right for such a simple problem.

I wanted to setup a differential IK example using a QP solver on the robot that solves

  dq* = argmin || J(qc)*dq - vg||^2  s.t.  q- <= qc + dt*dq <= q+

where J in this case returns the 3-by-ndof linear part of the Jacobian and vg is the goal linear velocity.

Do you have any thoughts?

Issue with visualizer

I'm not sure when this happened, but the LBR model in the examples is messed up. From running other code, I am pretty much certain there is nothing wrong with optas.RobotModel. I think the issue must be in optas.Visualizer. Strange that the LWR is fine. It could be something to do with how this visual frames are defined (I made changes in the visualizer that handled the case when multiple visual frames for a link are defined). When I load the same model for the LBR into Pybullet it is put together fine. I probably missed something standard.

Add jacobian in robot model for homogenous transform

The end-effector pose and joint states q are related by the forward kinematics, represented by the homogenous transform

  T(q) = [ x(q) | y(q) | z(q) | p(q) ]
         [    0      0      0      1 ]

The jacobian of p(q) in an arbitrary base frame is the linear Jacobian matrix, implemented here:

def get_linear_jacobian(self, link, q, base_link):

It would be great to have similar functions for x, y, and z.

Documentation builds incorrectly

I noticed that doxygen builds incorrectly on GitHub Pages, see here: https://cmower.github.io/optas/classoptas_1_1builder_1_1OptimizationBuilder.html#a5116672a39a98371e5dc0323ac8efcb9

The list of parameters and return values appears as follows:

   @param T Number of time steps for the trajectory.
   @param robots A list of robot models.
   @param tasks A list of task models.
   @param derivs_align When true, the time derivatives align for each time step. Default is False.
   @return An instance of the OptimizationBuilder class.

This should be rendered as in https://cmower.github.io/optas/namespaceoptas_1_1spatialmath.html#a6b7dbd5ac8b2b3ebbbd51056dc9e0fe4

Strangely, when I build the documentation locally, everything renders correctly. Perhaps a version issue?

Problem with solving single configuration IK

When trying to solve a single configuration IK, meaning specifying equality constraint for the desired end-effector position and setting as cost distance to a nominal configuration, the ipopt solver seems to ignore the equality constraints.

I noticed that if i set simpler equality constraints (on the joints for instance), the solver does not ignore them.
So this must be an issue of the translation between casadi and the solver.

Here is a minimum working example: (run it from the example directory - if it is easier to debug i can create a new branch with the added file).

# Python standard lib
import os
import pathlib
import numpy as np

# OpTaS
import optas

cwd = pathlib.Path(__file__).parent.resolve() # path to current working directory
link_ee = 'end_effector_ball'  # end-effector link name

# Setup robot
urdf_filename = os.path.join(cwd, 'robots', 'kuka_lwr.urdf')
robot = optas.RobotModel(
    urdf_filename=urdf_filename,
    time_derivs=[0],  # i.e. joint position/velocity trajectory
)
robot_name = robot.get_name()
print(robot_name)

# Setup optimization builder
builder = optas.OptimizationBuilder(T=1, robots=[robot])

# get robot state variables
q_var = builder.get_model_states(robot_name)

# desired end-effector position
pos_T = np.asarray([-0.5, 0.0, 0.5])

# equality constraint on position
pos_fnc = robot.get_global_link_position_function(link=link_ee)
builder.add_equality_constraint('final_pos', pos_fnc(q_var), rhs=pos_T)

# optimization cost: close to nominal config
q_nom = optas.DM.zeros(robot.ndof)
builder.add_cost_term('nom_config', 0.001*optas.sumsqr(q_var-q_nom))

# setup solver
optimization = builder.build()
solver = optas.CasADiSolver(optimization=optimization).setup('ipopt')
# set initial seed
solver.reset_initial_seed({f'{robot_name}/q': q_nom})
# solve problem
solution = solver.solve()

qT_array = solution[f'{robot_name}/q']
print('********************************')
print(solver.opt_type)
print(q_nom)
print(qT_array)
print('********************************')

Add explanation/description for each example

The examples are just dumped into the example/ directory without much explanation. It would be useful to have a short description for each (perhaps in example/README.md and the documentation) that gives some explanation on exactly what the user should get from each example and an explanation of the planner/controller, its assumptions, etc.

Add get_link_axis method

Recently the method get_link_axis_jacobian was added but there is no function for get_link_axis.

Extend option to add variables as parameters or optimized to the TaskModel

In the RobotModel we are adding the ability to provide a list of param_joints, so that we can optimize a subset of the robot joint positions and/or velocities.
This concept could be generalized and extended to the the Model, extending it to the TaskModel.

Because we are currently not making use of the TaskModel for specific problems, it is not so useful now to extend this feature, but once we have an example of usage of the TaskModel, we should work on this.

Allow user to specify robot model name

At the moment, the name for the robot model is extracted from the URDF. This will create issues when interfacing with the ROS-PyBullet interface (eg when there are two robots in scene using the same URDF).

User should be able to set name. It can be that if they don't set it we use the one from the URDF as the default behaviour.

Suggestion: deprecate optimize_time

The goal of the optimize_time flag in the OptimizationBuilder interface was originally meant as a helper for when the user wanted to make dt part of the decision variables. In reality, this doesn't happen very much and in the case that it does, it is not such a big ask for the user to add this manually. Furthermore, because of optimize_time, it convolutes the methods below, making it very difficult to follow the logic (these methods are useful).

optas/optas/builder.py

Lines 563 to 623 in 3eeeaf3

def _integr(self, m, n):
"""Returns an integration function where m is the state dimension, and n is the number of trajectory points."""
xd = cs.SX.sym("xd", m)
x0 = cs.SX.sym("x0", m)
x1 = cs.SX.sym("x1", m)
dt = cs.SX.sym("dt")
integr = cs.Function("integr", [x0, x1, xd, dt], [x0 + dt * xd - x1])
return integr.map(n)
def integrate_model_states(self, name, time_deriv, dt=None):
"""Integrates the model states over time.
Syntax
------
builder.integrate_model_states(name, time_deriv, dt=None)
Parameters
----------
name (string)
Name of the model.
time_deriv (int)
The time-deriviative required (i.e. position is 0, velocity is 1, etc.).
dt (None, float, or array-like: casadi.SX, casadi.DM, or list or numpy.ndarray)
Integration time step.
"""
if self.optimize_time and dt is not None:
raise ValueError("dt is given but user specified optimize_time as True")
if not self.optimize_time and dt is None:
raise ValueError("dt is not given")
model = self.get_model(name)
xd = self.get_model_states(name, time_deriv)
x = self.get_model_states(name, time_deriv - 1)
n = x.shape[1]
if self.derivs_align:
xd = xd[:, :-1]
if self.optimize_time:
dt = self.get_dt()[:n]
else:
dt = cs.vec(dt)
assert dt.shape[0] in {
1,
n - 1,
}, f"dt should be scalar or have {n-1} elements"
if dt.shape[0] == 1:
dt = dt * cs.DM.ones(n - 1)
dt = cs.vec(dt).T # ensure dt is 1-by-(n-1) array
if isinstance(model, RobotModel):
integr = self._integr(model.num_opt_joints, n - 1)
else:
integr = self._integr(model.dim, n - 1)
name = f"__integrate_model_states_{name}_{time_deriv}__"
self.add_equality_constraint(name, integr(x[:, :-1], x[:, 1:], xd, dt))

@joaomoura24, unless you have any major objections, I will deprecate this feature as part of the #37 PR. I am guessing you probably aren't using this flag in your code?

Add unit test that runs example.py

It would be good to add the example.py script to the test_examples.py test suit (to prevent issues such as #102):

# def test_example():
# path = examples_path / "example.py"
# module = import_module(path)

Unfortunately, this throws an error by the GitHub action when I try to add (cf #104).

I think this has something to do with the fact that the example.py script launches a GUI (using vtk) and the default PC that github is running can not handle it - even with the automatic timeout.

I found this which helped in the sense that the error changed, but didn't fix the issue. So maybe there is something in that.

I'll keep this issue left open since it doesn't affect the main optas library (its a wish list) but it should not be added to the first release plan #63.

Issue with OSQP interface

Some solutions for a QP problem are perfectly fine using CasADiSovler with IPOPT, but the solutions using OSQPSolver are wrong.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.