Skip to main content

Real-time motion control for Python.

Project description

Vulp – Robot/simulation switch

CI Documentation Coverage C++ version Conda version PyPI version

Vulp provides an action-observation loop to control robots from a standalone "agent" process, like this:

Action-observation loop with Vulp

The agent can be a simple Python script with few dependencies.

Vulp is designed for robots built with the mjbots stack (moteus servo controllers and pi3hat communication board). It provides a robot/simulation switch to train or test agents in Bullet before running them on the real system.

Vulp supports Linux and macOS for development, and Raspberry Pi OS for robot deployment.

Installation

From Conda

Conda version

conda install -c conda-forge vulp

From PyPI

PyPI version

pip install vulp

Example

Check out the upkie repository for an example where Vulp is used to implement simulation environments, real-robot spines, state observers and locomotion agents.

Details

More accurately, Vulp is a tiny inter-process communication (IPC) protocol shipped with reference libraries (currently in Python and C++, other languages welcome). It is suitable for tasks that require real-time but not high-frequency performance. The main use case for this is balancing, as there is theoretical and empirical evidence suggesting that bipeds and quadrupeds can balance themselves as leisurely as 5–15 Hz, although balance control is frequently implemented at 200–1000 Hz. And if you are wondering whether Python is suitable for real-time applications, we were too! Until we tried it out.

In Vulp, a fast program, called a spine, talks to a slow program, called an agent, in a standard action-observation loop. Spine and agent run in separate processes and exchange action and observation dictionaries through shared memory. For instance, action can be a set of joint commands and observation a set of joint observations. Vulp provides a pipeline API to grow more complex spines with additional controllers (for higher-level actions) and observers (for richer observations). For example, a spine can run an inverse kinematics solver, or output its own ground contact estimation.

Features and non-features

All design decisions have their pros and cons. Take a look at the features and non-features below to decide if Vulp is a fit to your use case.

Features

  • Run the same Python code on simulated and real robots
  • Interfaces with to the mjbots pi3hat and mjbots actuators
  • Interfaces with to the Bullet simulator
  • Observer pipeline to extend observations
  • 🏗️ Controller pipeline to extend actions
  • Soft real-time: spine-agent loop interactions are predictable and repeatable
  • Unit tested, and not only with end-to-end tests

Non-features

  • Low frequency: Vulp is designed for tasks that run in the 1–400 Hz range (like balancing bipeds or quadrupeds)
  • Soft, not hard real-time guarantee: the code is empirically reliable by a large margin, that's it
  • Weakly-typed IPC: typing is used within agents and spines, but the interface between them is only checked at runtime

Alternatives

If any of the non-features is a no-go to you, you may also want to check out these existing alternatives:

  • kodlab_mjbots_sdk - C++-only framework integrated with LCM for logging and remote I/O. Still a work in progress, only supports torque commands as of writing this note.
  • mc_rtc - C++ real-time control framework from which Vulp inherited, among others, the idea of running the same code on simulated and real robots. The choice of a weakly-typed dictionary-based IPC was also inspired by mc_rtc's data store. C++ controllers are bigger cathedrals to build but they can run at higher frequencies.
  • robot_interfaces - Similar IPC between non-realtime Python and real-time C++ processes. The main difference lies in the use of Python bindings and action/observation types (more overhead, more safeguards) where Vulp goes structureless (faster changes, faster blunders). Also, robot_interfaces enforces process synchronization with a time-series API while in Vulp this is up to the agent (most agents act greedily on the latest observation).
  • ros2_control - A C++ framework for real-time control using ROS2 (still a work in progress). Its barrier of entry is steeper than the other alternatives, making it a fit for production rather than prototyping, as it aims for compatibility with other ROS frameworks like MoveIt. A Vulp C++ spine is equivalent to a ROS ControllerInterface implementing the dictionary-based IPC protocol.

If your robot is built with some of the following open hardware components, you can also use their corresponding Python bindings directly:

Using control bindings directly is a simpler alternative if you don't need the action-observation loop and simulation/real-robot switch from Vulp.

Q and A

Performance

How can motion control be real-time in Python, with garbage collection and all?

Python agents talk with Vulp spines via the SpineInterface, which can process both actions and observations in about 0.7 ± 0.3 ms. This leaves plenty of room to implement other control components in a low-frequency loop. You may also be surprised at how Python performance has improved in recent years (most "tricks" that were popular ten years ago have been optimized away in CPython 3.8+). To consider one data point, here are the cycle periods measured in a complete Python agent for Upkie (the Pink balancer from upkie) running on a Raspberry Pi 4 Model B (Quad core ARM Cortex-A72 @ 1.5GHz). It performs non-trivial tasks like balancing and whole-body inverse kinematics by quadratic programming:

Note that the aforementioned 0.7 ± 0.3 ms processing time happens on the Python side, and is thus included in the 5.0 ms cycles represented by the orange curve. Meanwhile the spine is set to a reference frequency of 1.0 kHz and its corresponding cycle period was measured here at 1.0 ± 0.05 ms.

I just started a simulation spine but it's surprisingly slow, how come?

Make sure you switch Bazel's compilation mode to "opt" when running both robot experiments and simulations. The compilation mode is "fastbuild" by default. Note that it is totally fine to compile agents in "fastbuild" during development while testing them on a spine compiled in "opt" that keeps running in the background.

I have a Bullet simulation where the robot balances fine, but the agent repeatedly warns it "Skipped X clock cycles". What could be causing this?

This happens when your CPU is not powerful enough to run the simulator in real-time along with your agent and spine. You can call Spine::simulate with nb_substeps = 1 instead of Spine::run, which will result in the correct simulation time from the agent's point of view but make the simulation slower than real-time from your point of view.

I'm running a pi3hat spine, why are my timings more erratic than the ones plotted above?

Make sure you configure CPU isolation and set the scaling governor to performance for real-time performance on a Raspberry Pi.

Design choices

Why use dictionaries rather than an interface description language like Protocol Buffers?

Interface description languages like Protocol Buffers are strongly typed: they formally specify a data exchange format that has to be written down and maintained, but brings benefits like versioning or breaking-change detection. Vulp, on the other hand, follows a weakly-typed, self-describing approach that is better suited to prototyping with rapidly-changing APIs: the spec is in the code. If an agent and spine communicate with incompatible/incomplete actions/observations, execution will break, begging for developers to fix it.

Why the weakly-typed dictionary IPC rather than Python–C++ bindings?

Vulp is designed for prototyping: it strives to eliminate intermediaries when it can, and keep a low barrier of entry. Python bindings bring the benefits of typing and are a good choice in production contexts, but like interface description languages, they also add overhead in terms of developer training, bookkeeping code and compilation time. Vulp rather goes for a crash-early approach: fast changes, fast blunders (interface errors raise exceptions that end execution), fast fixes (know immediately when an error was introduced).

Is it possible to run two agents at the same time?

That is not possible. One of the core assumptions in Vulp is that the agent and the spine are two respective processes communicating via one single shared-memory area. In this Vulp differs from e.g. ROS, which is multi-process by design. This design choice is discussed in #55.

Why the name "Vulp"?

Vulp means "fox" in Romansh, a language spoken in the Swiss canton of the Grisons. Foxes are arguably quite reliable in their reaction times 🦊

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vulp-2.2.2.tar.gz (19.7 kB view hashes)

Uploaded Source

Built Distribution

vulp-2.2.2-py3-none-any.whl (15.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page