Metadata-Version: 2.4
Name: python-coco
Version: 0.0.1
Summary: A Python package for constitutional control in constrained navigation tasks.
Home-page: 
Author: Simon Kohaut
Author-email: simon.kohaut@cs.tu-darmstadt.de
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: promis==4.0.0.dev2
Requires-Dist: torch
Requires-Dist: nflows
Provides-Extra: doc
Requires-Dist: sphinx; extra == "doc"
Requires-Dist: nbsphinx; extra == "doc"
Requires-Dist: sphinx-markdown-builder; extra == "doc"
Requires-Dist: sphinx_rtd_theme; extra == "doc"
Requires-Dist: sphinxcontrib-programoutput; extra == "doc"
Provides-Extra: dev
Requires-Dist: black; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# CoCo — The Constitutional Controller

[![arXiv](https://img.shields.io/badge/arXiv-2507.15478-b31b1b.svg)](https://arxiv.org/abs/2507.15478)
[![PyPI](https://img.shields.io/pypi/v/python-coco)](https://pypi.org/project/python-coco/)
[![License](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg)](LICENSE)
[![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue)](https://www.python.org/)

<p align="center">
  <a href="https://github.com/simon-kohaut/resin">Resin</a> reasoning ·
  <a href="https://github.com/HRI-EU/ProMis">ProMis</a> mission design ·
  <a href="https://github.com/simon-kohaut/CoFi">CoFi</a> tracking ·
  <b>CoCo</b> control
</p>

<p align="center">
  <img src="docs/static/images/coco_comparison.gif" width="800" alt="Animation: real drone flights through an obstacle course at 0.2, 0.5 and 1.0 m/s. At 0.2 and 0.5 m/s, drones steered by CoCo and by the baseline both reach the goal. At 1.0 m/s, the baseline, which ignores the drone's own control inaccuracies, crashes into the obstacles, while CoCo takes a wider route around them.">
</p>

<p align="center">
  <b>The Constitutional Controller: Doubt-Calibrated Steering of Compliant Agents</b><br>
  Simon Kohaut, Felix Divo, Navid Hamid, Benedict Flade, Julian Eggert, Devendra Singh Dhami, Kristian Kersting<br>
  <a href="https://arxiv.org/abs/2507.15478">Paper</a> · <a href="https://simon-kohaut.github.io/CoCo/">Project Page</a> · <a href="#citation">Citation</a>
</p>

CoCo couples low-level motion control with probabilistic logic by learning a **self-doubt density**, a Conditional Normalizing Flow that models an agent's control inaccuracies as a function of its operating conditions (e.g. velocity, heading).
This doubt distribution is folded into a [ProMis](https://github.com/HRI-EU/ProMis) compliance landscape at query time, producing a *doubt-calibrated* landscape that accounts for where the agent will actually end up, not just where it intends to go.

The rules behind the landscape are written in [Resin](https://github.com/simon-kohaut/resin); see its [language reference](https://github.com/simon-kohaut/resin/blob/main/docs/language.md) for their syntax and semantics.
Paths can then be evaluated or steered to remain constitutionally compliant even under uncertainty.
Since CoCo steers agents by a known constitution, other agents can rely on it: [CoFi](https://github.com/simon-kohaut/CoFi) tracks the drones above from coarse, noisy observations by trusting them to follow the very same rules.

## Installation

```bash
pip install python-coco
```

To install from source:

```bash
git clone https://github.com/simon-kohaut/CoCo
cd CoCo
pip install -e .
```

## Quick Start

### 1. Learn a doubt density

```python
import torch
from coco import DoubtDensity

# Control errors in meters, which grow with the velocity the agent flies at
velocity = torch.rand(2_000, 1)  # m/s
errors = torch.randn(2_000, 2) * (0.05 + 0.2 * velocity)

# The doubt space: the features that condition the agent's inaccuracies
doubt_space = {"velocity": {"type": "continuous", "values": velocity}}

density = DoubtDensity(doubt_space, number_of_states=2, number_of_hidden_features=32, number_of_layers=4)
losses = density.fit(errors, doubt_space, number_of_epochs=20, batch_size=64)
density.save("doubt_density.pkl")
```

### 2. Compute where the agent is allowed to be

```python
import time
import numpy as np
from promis import ProMis, StaRMap
from promis.geo import CartesianMap, CartesianPolygon, CartesianRasterBand, PolarLocation

# The constitution: stay off the obstacle
constitution = """
over(obstacle) <- source("/star_map/over/obstacle", Probability).
landscape if not over(obstacle).
landscape -> target("/landscape").
"""

# A 2 m x 1 m obstacle in a 10 m x 10 m area
obstacle = CartesianPolygon.from_numpy(np.array([[-1, 1, 1, -1], [-0.5, -0.5, 0.5, 0.5]]), location_type="obstacle")
area = CartesianMap(PolarLocation(0, 0), {"obstacle": [obstacle]})
points = CartesianRasterBand(PolarLocation(0, 0), (50, 50), 10, 10)
star_map = StaRMap(area)
star_map.initialize(points, number_of_random_maps=1, logic=constitution)
promis = ProMis(star_map, constitution, 50 * 50)
promis.initialize(points)
time.sleep(0.05)  # Resin's writers deliver values asynchronously
landscape = promis.update()
```

### 3. Apply doubt to the landscape

```python
from coco import ConstitutionalController, DoubtDensity

density = DoubtDensity.load("doubt_density.pkl")
controller = ConstitutionalController()

# Where the agent may go when flying at 1 m/s, given where its controller will actually take it
query = {"velocity": {"type": "continuous", "values": torch.tensor([[1.0]])}}
doubtful_landscape = controller.apply_doubt(landscape, density, query, number_of_samples=100, scaler=1.0)
```

The doubtful landscape spreads each obstacle by where the controller may actually take the agent; `scaler` converts the doubt's units to the landscape's.

### 4. Evaluate path compliance

```python
# A path passing 30 cm above the obstacle, as (T, 2) positions in meters
path = np.stack([np.linspace(-3, 3, 13), np.full(13, 0.8)]).T
compliance = controller.compliance(path, landscape, density, query, number_of_samples=500)
# compliance: (T,) probability of complying at each waypoint
```

## Citation

If you use CoCo, please cite the paper below. CoCo builds on [ProMis](https://github.com/HRI-EU/ProMis).

```bibtex
@inproceedings{kohaut2026coco,
  title     = {The Constitutional Controller: Doubt-Calibrated Steering of Compliant Agents},
  author    = {Kohaut, Simon and Divo, Felix and Hamid, Navid and Flade, Benedict
               and Eggert, Julian and Dhami, Devendra Singh and Kersting, Kristian},
  booktitle = {Proceedings of the IEEE/RSJ International Conference on Intelligent
               Robots and Systems (IROS)},
  year      = {2026}
}
```
