What Repuls Io Actually Does
Repuls Io is a Python library for repulsive-force-based obstacle avoidance, mostly used in robotics and autonomous drone projects. It implements variations of the artificial potential field method, where obstacles generate repulsive vectors that push a robot or agent away from collisions while a goal attracts it forward. The core idea isn't new — it dates back to Khatib's 1986 paper — but Repuls Io packages it into something you can drop into a ROS or stand-alone Python project without writing the math from scratch. I started using it about two years ago on a small UAV project where I needed lightweight obstacle rejection without running a full SLAM stack. The installation is straightforward — pip install repuls-io works on Python 3.9 and above. The main API revolves around creating a RepulsMap object, feeding it obstacle coordinates and a radius parameter, then calling a get_repulsive_field() method that returns a vector grid or a direct force at a query point, depending on which mode you're in. The default mode computes repulsive forces analytically using a piecewise potential function with a near zone and a far zone. Inside the influence radius, the force ramps up quickly. Beyond it, the force drops to zero. That means your robot only reacts to obstacles that are actually close, which keeps computation low. For a single obstacle at a known pose, the field calculation takes roughly 0.3 milliseconds on a Raspberry Pi 4. That's fast enough to run at 10 Hz without choking your control loop.
Where people usually trip up is with overlapping obstacle fields. The library doesn't automatically handle superposition the way you'd expect. I ran into this when testing with three closely spaced bollards on a warehouse floor. The repulsive vectors from adjacent obstacles added nonlinearly, and the robot ended up oscillating between two of them instead of finding a clean gap. The workaround I used was to merge nearby obstacles into a single bounding region before feeding them into Repuls Io. You can do this manually by computing a convex hull around obstacles that are within 1.5 times their combined radius, or you can enable the built-in merge_mode option if your version supports it. That fixed the oscillation almost immediately.
Setting It Up and Getting It to Work
After installation, the first thing you should verify is that your coordinate system matches what the library expects. Repuls Io uses a standard right-handed frame where positive x is forward and positive y is left. If you're pulling data from a sensor that uses a different convention — LiDAR scanners often output in a left-handed frame — you need to rotate or flip the coordinates before passing them in. Skipping this step causes the robot to repel toward the wrong side, and you'll notice it within the first few seconds of movement. Here's a minimal working example that moves a point toward a goal while avoiding a single static obstacle: Import the library and define your parameters. Set the influence radius to something practical for your environment. A radius of 2 meters works for indoor navigation. Anything smaller and you're constantly triggering avoidance. Anything larger and the robot hesitates at open space.
Get the Full Details

Create the RepulsMap. Pass in your obstacle list as tuples of (x, y, radius). Then call the field computation at your current position. Combine with a goal attractor. Repuls Io has a simple attractor function you can layer on top. The net force is the weighted sum of attraction and repulsion. I usually set the attraction weight to 1.0 and the repulsion weight to 2.0, then tune from there based on how aggressively your robot reacts. Run it in a loop. Sample the field at your updated position each cycle. Clamp the maximum force to prevent sudden jerks. A force clamp around 5.0 units keeps the motion smooth on most differential-drive platforms.
Where It Falls Apart
The biggest issue with any potential field approach, and Repuls Io is no exception, is the local minimum problem. When obstacles are arranged in a corridor or a U-shape, the repulsive forces can cancel the attractive pull toward the goal, and your robot stops moving entirely. I encountered this in a narrow hallway test where two walls on either side created a saddle point. The robot hovered there for nearly a minute before I killed the script. There's no built-in escape mechanism in Repuls Io for this scenario. You have to layer on a secondary strategy, like a sampling-based path planner or a random walk perturbation, when you detect the robot has been stationary for more than three seconds. A second limitation is that Repuls Io assumes static obstacles. If you're tracking moving objects — people, other robots, anything with velocity — the library will treat them as frozen in place. You can feed in updated positions each cycle, but the repulsive potential doesn't account for motion prediction. I added a simple time-to-collision filter myself by estimating the relative velocity from consecutive position samples and boosting the repulsion weight for approaching objects. It's not in the core library, and the codebase doesn't make it obvious where to hook it in, but it's a reasonable patch if you're dealing with dynamic environments. Performance degrades noticeably with more than ten obstacles in the near field. The field computation scales roughly linearly with obstacle count, but the Python overhead becomes significant. On a Jetson Nano, ten obstacles pushed the cycle time to about 12 milliseconds, which is acceptable but leaves little headroom. If you're working with dense obstacle maps, consider switching to the C++ backend if your version includes one, or pre-filter obstacles down to only those within your influence radius before passing them in.
Alternatives Worth Considering
If you need moving obstacle handling or dynamic replanning, look at ROS Navigation Stack's costmap_2d combined with TEA Planner or Global Planner. It's heavier but handles the local minimum problem with Dijkstra or A* backup. For simpler projects that just need basic avoidance, ObstacleAvoidanceROS by various maintainers on GitHub offers a lighter alternative with similar API patterns. If your project is purely 2D and you don't need ROS integration, Repuls Io remains one of the more straightforward options, especially if you're already working in Python and want something you can read and modify directly. The library is available on PyPI and GitHub. Check the repository README for version compatibility notes and the changelog, since the API has shifted between minor releases. The current stable version supports Python 3.9 through 3.12. Documentation is sparse beyond the README and a handful of Jupyter notebooks in the examples folder, so reading the source code is basically required if you want to customize anything beyond the default behavior.
