CAROL is a fully distributed, multi-robot control framework written in Kotlin. It bridges the gap between Aggregate Programming (via the Collektive framework) and Optimization-based Control using Control Lyapunov Functions (CLFs) and Control Barrier Functions (CBFs).
By leveraging the Alternating Direction Method of Multipliers (ADMM), CAROL allows a swarm of robots to negotiate safe and optimal trajectories in a fully distributed manner, ensuring goal convergence, obstacle avoidance, inter-robot collision avoidance, and communication range maintenance.
- Distributed Consensus (ADMM): Solves coupled multi-agent Quadratic Programs (QPs) using local and pairwise micro-iterations, eliminating the need for a central coordinator.
- Safety & Connectivity (CBFs):
- Obstacle Avoidance: Hard constraints to dodge static environment hazards.
- Collision Avoidance: Hard constraints to maintain a minimum safe distance between neighbors.
- Communication Range: Soft constraints (with L1 penalty) to maintain network connectivity without causing solver freezing when physical boundaries force a split.
- Goal Tracking (CLFs): Proportional nominal controllers paired with CLFs to ensure asymptotic convergence to target destinations.
- Zero-Order Hold (ZOH) Discretization: Exact discrete-time robustification of continuous-time dynamics, ensuring the QP solver remains strictly affine and mathematically sound.
- Formula DSL for CLF/CBF constraints: CLFs and CBFs are declared through a small Kotlin DSL that keeps formulas readable while updating state-dependent coefficients at runtime.
- Stateless Architecture: Completely thread-safe and immutable constraint definitions, ready for parallelized processing and coroutines.
Scenarios are under src/main/yaml/ and have matching effects in effects/.
| Experiment | Brief description | YAML | Entrypoint | Active CBFs |
|---|---|---|---|---|
| No obstacle, split targets | Two robot groups track two different targets in free space; focuses on coordination and collision/speed safety without obstacle constraints. | noObstacle.yml |
NoObstacleKt.noObstacleEntrypoint |
MaxSpeedCBF, CollisionAvoidanceCBF |
| Common moving target | All robots track one moving target while avoiding a static obstacle and preserving connectivity when possible. | followTarget.yml |
CommonTargetKt.commonTargetEntrypoint |
ObstacleAvoidanceCBF, MaxSpeedCBF, CollisionAvoidanceCBF, CommunicationRangeCBF |
| Leader-follower | A distributedly elected leader follows the target; neighbors follow the leader while respecting obstacle, collision, and range constraints. | followLeader.yml |
FollowLeaderKt.followLeaderEntrypoint |
ObstacleAvoidanceCBF, MaxSpeedCBF, CollisionAvoidanceCBF, CommunicationRangeCBF |
| Different targets | Robots are assigned different TargetID values and split toward separate goals with obstacle and pairwise safety constraints. |
differentTargets.yml |
MultipleTargetsKt.multipleTargetEntrypoint |
ObstacleAvoidanceCBF, MaxSpeedCBF, CollisionAvoidanceCBF |
src/main/
├── yaml/ Alchemist simulation scenarios
└── kotlin/it/unibo/
├── alchemist/ Custom Alchemist actions/effects
└── collektive/
├── entrypoints/ Scenario entrypoints (NoObstacle, FollowLeader, ...)
├── admm/ ADMM state/core/objectives
├── control/ CLF/CBF definitions and nominal control
│ └── dsl/ Formula DSL for reusable CLF/CBF constraints
├── alchemist/device/ Sensors + environment bridge + QP settings
├── solver/gurobi/ Gurobi integration, constraints, license setup
├── mathutils/ Vector and numeric utilities
└── model/ Domain models (Device, Target, Obstacle, ...)
Control functions are defined in src/main/kotlin/it/unibo/collektive/control/.
The reusable formula machinery lives in src/main/kotlin/it/unibo/collektive/control/dsl/.
The DSL keeps the important solver invariant explicit: formulas are built once when the reusable Gurobi model is installed, while robot positions, neighbor state, time step, safety margins, speed bounds, and other runtime values are evaluated again at every solver update. This lets constraints such as collision avoidance stay readable:
override fun ControlFunctionScope.formula(): ConstraintFormula {
val distance = self.position - other.position
val minDistance = max(self.safeMargin, other.safeMargin)
val h = squaredNorm(distance) - squared(minDistance)
return 2.0 * dot(distance, self.u - other.u) + slack greaterThanOrEqualTo
-(eta / timeStep) * h
}To add a new control function:
- extend
CBForCLF; - implement
ControlFunctionScope.formula(); - express changing values through
self,other,timeStep,scalar { ... }, orvector { ... }; - use
syncFromwhen the installed function must refresh dynamic providers such as moving targets or obstacles.
More details, including the extension rules and available expression operators, are documented in
src/main/kotlin/it/unibo/collektive/control/dsl/README.md.
- JDK 17+ (project compiles on JVM 17).
- Gurobi Optimizer installed.
- A valid
gurobi.liclicense file.
The runtime searches for license in this order:
GRB_LICENSE_FILEenvironment variable.GRB_LICENSE_FILEJVM system property.~/Library/gurobi/gurobi.lic(macOS default fallback).
If your license is not set globally, pass it explicitly to Gradle using an env variable.
GRB_LICENSE_FILE="/absolute/path/to/gurobi.lic" ./gradlew runNoObstacleGraphic
GRB_LICENSE_FILE="/absolute/path/to/gurobi.lic" ./gradlew runAllBatchCommon molecules/parameters used by entrypoints:
TargetID: target to track for each robot.MaxSpeed: robot max speed bound.SafeMargin: robot safety margin (and obstacle margin where applicable).ControlPeriodMS: control loop period used by ADMM step execution.PrimalTolerance/DualTolerance: ADMM stopping tolerances.CommunicationDistance: required in scenarios using communication-range constraints (followTarget,followLeader).RhoADMM(default10.0)RhoResidual(default0.5)RhoSlack(default2.0)LogEnabled(defaultfalse)
Base environment/scheduler values are defined in each YAML variables section (timeDistribution, network distance, obstacle radius/margin, trigger times, etc.).
Ensure your Gurobi license is properly set up. Run one scenario (examples):
./gradlew runFollowTargetGraphicGradle auto-generates run<Name>Graphic tasks from every .yml file in src/main/yaml/.
The control model is discrete-time with zero-order hold (ZOH):
p(k+1) = p(k) + Delta_t * u(k)
At each control step, each robot solves a local QP that keeps control close to a nominal command while enforcing safety and consensus terms. A simplified objective is:
min_u,s ||u - u_nom||^2 + rho_slack * ||s||_1 + ADMM augmented terms
- CLF terms push the state toward the assigned target (goal convergence).
- CBF terms impose forward-invariance-style inequalities for safety (speed, obstacle, and pairwise collision limits).
- Communication range (when enabled) is relaxed via slack
sto degrade gracefully instead of making the QP infeasible.
ADMM terminates when primal and dual residuals are below PrimalTolerance and DualTolerance (per robot molecules), or when scenario iteration/time limits are reached.