Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,28 @@ jobs:
cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
# ensure the 'tests' environment(s) are installed
environments: tests
# don't activate env (we'll call pixi run tests explicitly later)
# don't activate env (we'll call pixi run -e tests explicitly later)
activate-environment: false
# prefer using existing lockfile if present (faster, deterministic)
locked: true

- name: Verify pixi and run tests
run: |
pixi --version
pixi run tests
pixi run -e tests tests -v

test-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Pixi
uses: prefix-dev/setup-pixi@v0.9.3
with:
pixi-version: v0.67.0
cache: true
cache-write: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
environments: tests
activate-environment: false
locked: true
- name: Run doc snippet tests
run: pixi run -e tests test-docs
43 changes: 1 addition & 42 deletions docs/api/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,4 @@

::: drone_controllers.core

The core module provides the foundational functionality for controller parametrization.

## Key Concepts

### Controller Parametrization

The `parametrize` function automatically configures a controller with parameters for a specific drone model by inspecting the function's keyword-only arguments and filling them from the corresponding TOML file:

```python
from drone_controllers import parametrize
from drone_controllers.mellinger import state2attitude

# Get a controller configured for the Crazyflie 2.x
controller = parametrize(state2attitude, "cf2x_L250")

# Use the controller (all parameters are automatically filled in)
rpyt, pos_err = controller(pos, quat, vel, cmd)
```

### Manual Parameter Loading

Use `load_params` to inspect or override parameters directly:

```python
from drone_controllers.core import load_params

params = load_params("mellinger", "state2attitude", "cf2x_L250")
print(params["mass"]) # 0.029
print(params["kp"]) # position gain array
```

### Array Namespace Support

Both `parametrize` and `load_params` accept an `xp` argument so that static parameters are placed in the correct array namespace before being bound to the function:

```python
import jax.numpy as jnp
from drone_controllers import parametrize
from drone_controllers.mellinger import state2attitude

controller = parametrize(state2attitude, "cf2x_L250", xp=jnp)
```
See the [Parametrize user guide](../user-guide/parametrize.md) for usage examples, available drone configurations, and backend switching.
134 changes: 3 additions & 131 deletions docs/api/mellinger.md
Original file line number Diff line number Diff line change
@@ -1,133 +1,5 @@
# Mellinger Controller
# Mellinger

The Mellinger controller is a geometric tracking controller originally developed for aggressive quadrotor maneuvers [[1]](#references). This implementation is based on the Crazyflie firmware version.
::: drone_controllers.mellinger.control

## Controller Functions

### state2attitude

::: drone_controllers.mellinger.control.state2attitude

The position control component of the Mellinger controller. Converts desired position, velocity, and acceleration into attitude commands.

**Example:**
```python
from drone_controllers import parametrize
from drone_controllers.mellinger import state2attitude

controller = parametrize(state2attitude, "cf2x_L250")

rpyt, pos_err_i = controller(pos, quat, vel, cmd)
```

### attitude2force_torque

::: drone_controllers.mellinger.control.attitude2force_torque

The attitude control component that converts attitude commands into desired forces and torques.

**Example:**
```python
from drone_controllers import parametrize
from drone_controllers.mellinger import attitude2force_torque

controller = parametrize(attitude2force_torque, "cf2x_L250")

force, torque, att_err_i = controller(quat, ang_vel, rpyt_cmd)
```

### force_torque2rotor_vel

::: drone_controllers.mellinger.control.force_torque2rotor_vel

Converts desired forces and torques into individual rotor velocities using the quadrotor mixing matrix.

**Example:**
```python
from drone_controllers import parametrize
from drone_controllers.mellinger import force_torque2rotor_vel

controller = parametrize(force_torque2rotor_vel, "cf2x_L250")

rotor_speeds = controller(force, torque)
```

## Complete Controller Pipeline

Here's how to use all three components together:

```python
import numpy as np
from drone_controllers import parametrize
from drone_controllers.mellinger import (
state2attitude,
attitude2force_torque,
force_torque2rotor_vel
)

# Parametrize all three controller components
state_ctrl = parametrize(state2attitude, "cf2x_L250")
attitude_ctrl = parametrize(attitude2force_torque, "cf2x_L250")
rotor_ctrl = parametrize(force_torque2rotor_vel, "cf2x_L250")

# Define state
pos = np.array([0.0, 0.0, 1.0])
quat = np.array([0.0, 0.0, 0.0, 1.0]) # [x, y, z, w]
vel = np.array([0.0, 0.0, 0.0])
ang_vel = np.array([0.0, 0.0, 0.0])

# Define command: [x, y, z, vx, vy, vz, ax, ay, az, yaw, r_rate, p_rate, y_rate]
cmd = np.array([1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])

# Run the complete pipeline
rpyt, pos_err_i = state_ctrl(pos, quat, vel, cmd)
force, torque, att_err_i = attitude_ctrl(quat, ang_vel, rpyt)
rotor_speeds = rotor_ctrl(force, torque)

print(f"Final rotor speeds: {rotor_speeds} rad/s")
```

## Integral Error Handling

The Mellinger controller uses integral terms for robustness. You must pass integral errors between calls:

```python
# Initialize
pos_err_i = None
att_err_i = None

for step in range(100):
# Update state and command...

# Pass previous integral errors
ctrl_errors = (pos_err_i,) if pos_err_i is not None else None
rpyt, pos_err_i = state_ctrl(pos, quat, vel, cmd, ctrl_errors=ctrl_errors)

ctrl_errors = (att_err_i,) if att_err_i is not None else None
force, torque, att_err_i = attitude_ctrl(quat, ang_vel, rpyt, ctrl_errors=ctrl_errors)

rotor_speeds = rotor_ctrl(force, torque)
```

## Array API Compatibility

All Mellinger functions support the Python Array API and can be used with JAX, PyTorch, etc.:

```python
import jax.numpy as jnp
from jax import jit

# Convert to JAX arrays
pos_jax = jnp.array([0.0, 0.0, 1.0])
quat_jax = jnp.array([0.0, 0.0, 0.0, 1.0])
# ... other arrays

# JIT compile the controller
jit_controller = jit(parametrize(state2attitude, "cf2x_L250"))

rpyt, pos_err_i = jit_controller(pos_jax, quat_jax, vel_jax, cmd_jax)
```

# References

[1] D. Mellinger and V. Kumar, "Minimum snap trajectory generation and control for quadrotors," 2011 IEEE International Conference on Robotics and Automation, Shanghai, China, 2011, pp. 2520-2525, doi: 10.1109/ICRA.2011.5980409.
See the [Mellinger user guide](../user-guide/mellinger.md) for input/output tables, worked examples, and guidance on chaining all three stages.
Loading
Loading