diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index fba7eab..9177001 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -16,7 +16,7 @@ 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 @@ -24,4 +24,20 @@ jobs: - 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 diff --git a/docs/api/core.md b/docs/api/core.md index dccc60d..9602c16 100644 --- a/docs/api/core.md +++ b/docs/api/core.md @@ -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. diff --git a/docs/api/mellinger.md b/docs/api/mellinger.md index 68de824..d4a75e7 100644 --- a/docs/api/mellinger.md +++ b/docs/api/mellinger.md @@ -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. diff --git a/docs/api/transform.md b/docs/api/transform.md index 63d236a..c156c1d 100644 --- a/docs/api/transform.md +++ b/docs/api/transform.md @@ -2,308 +2,68 @@ ::: drone_controllers.transform -The transform module provides utility functions for converting between different physical representations of quadrotor parameters. +The transform module provides utility functions for converting between different physical representations of quadrotor motor state. -## Motor Force Conversions +## Motor force / rotor velocity -### motor_force2rotor_vel +### `motor_force2rotor_vel` -Convert motor forces to rotor velocities using the thrust coefficient: +Invert the quadratic thrust curve $f = a + b\,\omega + c\,\omega^2$ to recover rotor speed from motor force: ```python import numpy as np +from drone_controllers.core import load_params +from drone_controllers.mellinger import force_torque2rotor_vel from drone_controllers.transform import motor_force2rotor_vel -motor_forces = np.array([0.1, 0.1, 0.1, 0.1]) # N -kf = 3.16e-10 # Thrust coefficient -rotor_speeds = motor_force2rotor_vel(motor_forces, kf) -print(f"Rotor speeds: {rotor_speeds} rad/s") -``` - -### rotor_vel2body_force - -Convert rotor velocities to body forces: - -```python -from drone_controllers.transform import rotor_vel2body_force +params = load_params(force_torque2rotor_vel, "cf2x_L250") +rpm2thrust = params["rpm2thrust"] -rotor_speeds = np.array([1000, 1000, 1000, 1000]) # rad/s -kf = 3.16e-10 -body_force = rotor_vel2body_force(rotor_speeds, kf) # Returns [0, 0, total_thrust] +forces = np.array([0.05, 0.08, 0.10, 0.05]) # N per motor +rpms = motor_force2rotor_vel(forces, rpm2thrust) +rpms.shape # (4,) ``` -### rotor_vel2body_torque +### `rotor_vel2body_force` -Convert rotor velocities to body torques using mixing matrix: +Sum rotor forces into a body-frame force vector. Only the z-component is nonzero for an X-frame quad: ```python -from drone_controllers.transform import rotor_vel2body_torque - -rotor_speeds = np.array([1000, 1000, 1000, 1000]) # rad/s -kf = 3.16e-10 -km = 7.94e-12 -L = 0.046 # Arm length in meters -mixing_matrix = np.array([[1, -1, 1], [-1, 1, 1], [1, 1, -1], [-1, -1, -1]]) - -body_torque = rotor_vel2body_torque(rotor_speeds, kf, km, L, mixing_matrix) -``` - -## PWM Conversions - -### force2pwm - -Convert thrust forces to PWM values: - -```python -from drone_controllers.transform import force2pwm - -thrust = 0.4 # N -thrust_max = 0.6 # N -pwm_max = 65535 - -pwm_value = force2pwm(thrust, thrust_max, pwm_max) -print(f"PWM value: {pwm_value}") -``` - -### pwm2force - -Convert PWM values back to thrust forces: - -```python -from drone_controllers.transform import pwm2force - -pwm_value = 43690 -thrust_max = 0.6 # N -pwm_max = 65535 - -thrust = pwm2force(pwm_value, thrust_max, pwm_max) -print(f"Thrust: {thrust} N") -``` - -## Array API Compatibility - -All transform functions work with the array API standard and support broadcasting: - -```python -import jax.numpy as jnp -from drone_controllers.transform import motor_force2rotor_vel - -# Works with JAX arrays -motor_forces_jax = jnp.array([[0.1, 0.1, 0.1, 0.1], - [0.2, 0.2, 0.2, 0.2]]) # Batch of 2 -kf = 3.16e-10 - -rotor_speeds = motor_force2rotor_vel(motor_forces_jax, kf) -print(f"Batch output shape: {rotor_speeds.shape}") # (2, 4) -``` - -## Usage in Controller Pipeline - -These transforms are typically used at the end of the control pipeline: - -```python -from drone_controllers import parametrize -from drone_controllers.mellinger import state2attitude, attitude2force_torque, force_torque2rotor_vel - -# Set up controller pipeline -state_ctrl = parametrize(state2attitude, "cf2x_L250") -attitude_ctrl = parametrize(attitude2force_torque, "cf2x_L250") -rotor_ctrl = parametrize(force_torque2rotor_vel, "cf2x_L250") - -# Run full pipeline -rpyt, _ = state_ctrl(pos, quat, vel, ang_vel, cmd) -force, torque, _ = attitude_ctrl(pos, quat, vel, ang_vel, rpyt) -rotor_speeds = rotor_ctrl(force, torque) - -# Convert to actual motor forces if needed -from drone_controllers.transform import motor_force2rotor_vel -# The rotor_ctrl already returns rotor speeds, but if you had forces: -# rotor_speeds = motor_force2rotor_vel(motor_forces, kf=3.16e-10) -``` - -The transform module provides utilities for coordinate transformations and rotation representations commonly used in drone control. - -## Rotation Conversions - -### Rotation Matrices - -Functions for working with 3x3 rotation matrices: - -```python -from drone_controllers.transform import ( - rotation_matrix_x, rotation_matrix_y, rotation_matrix_z, - matrix_to_euler, euler_to_matrix -) import numpy as np +from drone_controllers.core import load_params +from drone_controllers.mellinger import force_torque2rotor_vel +from drone_controllers.transform import rotor_vel2body_force -# Elementary rotations -R_x = rotation_matrix_x(np.pi/4) # 45° about x-axis -R_y = rotation_matrix_y(np.pi/6) # 30° about y-axis -R_z = rotation_matrix_z(np.pi/3) # 60° about z-axis - -# Combined rotation -R_combined = R_z @ R_y @ R_x -``` - -### Euler Angles - -Convert between rotation matrices and Euler angles: - -```python -# Convert rotation matrix to Euler angles (ZYX convention) -roll, pitch, yaw = matrix_to_euler(rotation_matrix) - -# Convert Euler angles to rotation matrix -R = euler_to_matrix(roll, pitch, yaw) -``` - -### Quaternions - -Quaternion operations for attitude representation: - -```python -from drone_controllers.transform import ( - quaternion_to_matrix, matrix_to_quaternion, - quaternion_multiply, quaternion_conjugate -) - -# Convert between quaternions and rotation matrices -quat = matrix_to_quaternion(rotation_matrix) -R = quaternion_to_matrix(quat) - -# Quaternion operations -q1 = np.array([0.7071, 0, 0, 0.7071]) # 90° about z-axis -q2 = np.array([0.7071, 0.7071, 0, 0]) # 90° about x-axis -q_combined = quaternion_multiply(q1, q2) -``` - -## Vector Operations - -### Cross Product Matrix - -Generate skew-symmetric matrices for cross products: - -```python -from drone_controllers.transform import skew_symmetric - -vector = np.array([1, 2, 3]) -skew_matrix = skew_symmetric(vector) - -# Now: skew_matrix @ other_vector == np.cross(vector, other_vector) -``` - -### Vector Projections - -Project vectors onto planes or other vectors: - -```python -from drone_controllers.transform import project_vector_onto_plane - -vector = np.array([1, 1, 1]) -plane_normal = np.array([0, 0, 1]) # XY plane -projected = project_vector_onto_plane(vector, plane_normal) -``` - -## Coordinate Frame Transformations - -### Frame Conversions - -Transform vectors between different coordinate frames: - -```python -from drone_controllers.transform import transform_vector - -# Transform vector from body to world frame -vector_body = np.array([1, 0, 0]) # Forward in body frame -rotation_body_to_world = euler_to_matrix(0, np.pi/4, 0) # 45° pitch -vector_world = transform_vector(vector_body, rotation_body_to_world) -``` - -### Common Frames - -Utilities for standard aerospace coordinate frames: - -```python -from drone_controllers.transform import ( - ned_to_enu, enu_to_ned, - body_to_world, world_to_body -) - -# Convert between NED and ENU conventions -position_ned = np.array([10, 5, -2]) # North, East, Down -position_enu = ned_to_enu(position_ned) # East, North, Up -``` - -## Angular Velocity Operations - -### Integration on SO(3) - -Integrate angular velocities on the rotation group: - -```python -from drone_controllers.transform import integrate_angular_velocity - -current_attitude = np.eye(3) -angular_velocity = np.array([0.1, 0.2, 0.05]) # rad/s -dt = 0.01 - -new_attitude = integrate_angular_velocity( - current_attitude, angular_velocity, dt -) -``` - -### Rodrigues Formula - -Direct integration using Rodrigues' rotation formula: - -```python -from drone_controllers.transform import rodrigues_rotation +params = load_params(force_torque2rotor_vel, "cf2x_L250") +rpm2thrust = params["rpm2thrust"] -axis = np.array([0, 0, 1]) # Rotation axis -angle = np.pi/2 # 90 degrees -rotation_matrix = rodrigues_rotation(axis, angle) +rotor_speeds = np.full(4, 12_000.) # RPM +body_force = rotor_vel2body_force(rotor_speeds, rpm2thrust) +body_force.shape # (3,) -- x, y are zero; z is total thrust ``` -## Utility Functions - -### Angle Normalization +### `rotor_vel2body_torque` -Normalize angles to standard ranges: +Compute body-frame torques from individual rotor speeds using the mixing matrix. -```python -from drone_controllers.transform import normalize_angle, wrap_to_pi - -angle = 3 * np.pi # Large angle -normalized = normalize_angle(angle) # Wrapped to [0, 2π) -wrapped = wrap_to_pi(angle) # Wrapped to [-π, π] -``` +## PWM conversions -### Rotation Composition +### `force2pwm` / `pwm2force` -Compose multiple rotations efficiently: +Linear conversion between thrust in Newtons and the PWM signal sent to the motors: ```python -from drone_controllers.transform import compose_rotations - -rotations = [R1, R2, R3] # List of rotation matrices -composed = compose_rotations(rotations) # Equivalent to R3 @ R2 @ R1 -``` - -## Constants and Conventions +import numpy as np +from drone_controllers import mellinger +from drone_controllers.core import load_core_params +from drone_controllers.transform import force2pwm, pwm2force -The module defines standard constants and conventions: +core = load_core_params(mellinger, "cf2x_L250") +thrust_max = float(core["thrust_max"]) +pwm_max = float(core["pwm_max"]) -```python -from drone_controllers.transform import ( - GRAVITY_EARTH, # Standard gravity (9.80665 m/s²) - IDENTITY_ROTATION, # 3x3 identity matrix - ZERO_VECTOR, # Zero 3D vector -) +thrust = np.array([0.05, 0.10]) +pwms = force2pwm(thrust, thrust_max, pwm_max) +thrust_recovered = pwm2force(pwms, thrust_max, pwm_max) +np.allclose(thrust, thrust_recovered) # True ``` - -## Performance Notes - -- Rotation matrices are preferred for computational efficiency -- Quaternions are used when interpolation is needed -- Euler angles are primarily for human-readable output -- All functions support vectorized operations where possible diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..51025b2 --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,128 @@ +# Examples + +These examples build on each other; each one introduces one new idea on top of the previous. Start from the top if you're new, or jump to the section you need. + +## Single stage with NumPy + +The simplest starting point: use `parametrize` to bind the drone's physical parameters to `state2attitude`, then call it with a hovering state and a zero setpoint. The drone is at the origin, upright, with no velocity. + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") + +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) # xyzw — identity (no rotation) +vel = np.zeros(3) +cmd = np.zeros(13) # setpoint at origin, yaw = 0 + +rpyt, int_pos_err = ctrl(pos, quat, vel, cmd) +``` + +`rpyt[:3]` is the attitude correction in radians; `rpyt[3]` is the collective thrust in Newtons. At the setpoint with no error, RPY is `[0, 0, 0]` and thrust is the hover thrust. + +## Full pipeline + +Chain all three stages to convert a full-state setpoint into individual motor speeds. Each stage feeds directly into the next. + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import ( + attitude2force_torque, + force_torque2rotor_vel, + state2attitude, +) + +state_ctrl = parametrize(state2attitude, "cf2x_L250") +att_ctrl = parametrize(attitude2force_torque, "cf2x_L250") +rotor_ctrl = parametrize(force_torque2rotor_vel, "cf2x_L250") + +pos = np.array([0., 0., 1.]) # 1 m altitude +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +ang_vel = np.zeros(3) +cmd = np.zeros(13) +cmd[:3] = np.array([0., 0., 1.]) # hover at 1 m + +rpyt, _ = state_ctrl(pos, quat, vel, cmd) +force, torque, _ = att_ctrl(quat, ang_vel, rpyt) +rotor_speeds = rotor_ctrl(force, torque) +rotor_speeds.shape # (4,) +``` + +## Batched evaluation + +Add a leading batch dimension to evaluate many states in one call. The controller broadcasts identically to the scalar case. + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") + +N = 64 +pos = np.random.randn(N, 3) * 0.5 +quat = np.tile(np.array([0., 0., 0., 1.]), (N, 1)) +vel = np.zeros((N, 3)) +cmd = np.zeros((N, 13)) + +rpyt, int_pos_err = ctrl(pos, quat, vel, cmd) +rpyt.shape # (64, 4) +``` + +## Integral error loop + +Controllers are pure functions; integral state is an explicit return value. Pass it back as `ctrl_errors` on every subsequent call to accumulate the integral term. + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import attitude2force_torque, state2attitude + +state_ctrl = parametrize(state2attitude, "cf2x_L250") +att_ctrl = parametrize(attitude2force_torque, "cf2x_L250") + +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +ang_vel = np.zeros(3) +cmd = np.zeros(13) +cmd[:3] = np.array([1., 0., 1.]) # 1 m offset in x and z + +state_errs = None +att_errs = None + +for step in range(20): + rpyt, int_pos_err = state_ctrl(pos, quat, vel, cmd, ctrl_errors=state_errs) + state_errs = (int_pos_err,) + + force, torque, r_int_err = att_ctrl(quat, ang_vel, rpyt, ctrl_errors=att_errs) + att_errs = (r_int_err,) + +# Integral errors have been accumulating for 20 steps. +``` + +## Switching backends + +`parametrize` accepts an `xp` keyword to convert parameters upfront into any Array API-compatible framework, avoiding per-call conversion overhead. + +```python +import torch +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +# Parameters stored as torch tensors. +ctrl = parametrize(state2attitude, "cf2x_L250", xp=torch) + +pos = torch.zeros(3) +quat = torch.tensor([0., 0., 0., 1.]) +vel = torch.zeros(3) +cmd = torch.zeros(13) + +rpyt, int_pos_err = ctrl(pos, quat, vel, cmd) +type(rpyt) # +``` diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 96da2ef..92ad26d 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -1,62 +1,50 @@ # Installation -Drone Controllers requires Python 3.11 or later. +Select your installation method from the tabs below, then read the notes under each section for what it includes. -## Install from PyPI +=== "pip" -!!! info "Coming Soon" - The package will be available on PyPI soon. For now, install from source. + ```bash + pip install drone-controllers + ``` -```bash -pip install drone-controllers -``` +=== "pixi" -## Install from Source + ```bash + git clone https://github.com/learnsyslab/drone-controllers.git + cd drone-controllers + pixi shell + ``` -Clone the repository and install in development mode: +=== "pixi + tests" -```bash -git clone https://github.com/learnsyslab/drone-controllers.git -cd drone-controllers -pip install -e . -``` + ```bash + git clone https://github.com/learnsyslab/drone-controllers.git + cd drone-controllers + pixi shell -e tests + ``` -## Dependencies +--- -Drone Controllers has minimal dependencies: +## Developer install -- `numpy>=2.0.0` - For numerical computations -- `scipy` - For optimization and signal processing -- `array-api-compat` - For array API compatibility -- `array-api-extra` - Additional array operations -- `array-api-typing` - Type hints for array APIs +[Pixi](https://pixi.sh/) creates a fully reproducible environment and installs `drone-controllers` in editable mode. Any source change takes effect immediately without reinstalling. Recommended for contributors and researchers who modify the controllers. -## Verify Installation +## Testing -Test your installation by importing the package: +Adds `pytest` and `pytest-markdown-docs` for running the test suite and doc snippet tests. -```python -import drone_controllers -print(drone_controllers.__version__) +```bash +pixi run -e tests tests # unit tests +pixi run -e tests test-docs # doc code snippet tests ``` -## Development Installation +## Verify the installation -If you plan to contribute to Drone Controllers, install the development dependencies using [pixi](https://pixi.sh/): - -```bash -# Install pixi if you haven't already -curl -fsSL https://pixi.sh/install.sh | bash - -# Clone and install -git clone https://github.com/learnsyslab/drone-controllers.git -cd drone-controllers -pixi install +```python +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude -# Run tests -pixi run -e tests tests +ctrl = parametrize(state2attitude, "cf2x_L250") +list(ctrl.keywords.keys())[:3] # ['mass', 'kp', 'kd'] ``` - -## What's Next? - -Once you have Drone Controllers installed, check out the [Quick Start](quick-start.md) guide to learn the basics. diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 02eb7c7..b825746 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -1,189 +1,55 @@ # Quick Start -This guide will get you up and running with Drone Controllers in just a few minutes. +This page walks through the minimal workflow: bind parameters to a drone, set up a state, and call the first stage of the Mellinger controller. -## Basic Controller Usage +## State and command -The library implements controllers as pure functions that can be parametrized for specific drone models. Here's how to use the Mellinger controller: +Every controller stage shares the same state representation: -```python -import numpy as np -from drone_controllers import parametrize -from drone_controllers.mellinger import state2attitude, attitude2force_torque, force_torque2rotor_vel - -# Parametrize controllers for a specific drone model -state_ctrl = parametrize(state2attitude, "cf2x_L250") -attitude_ctrl = parametrize(attitude2force_torque, "cf2x_L250") -rotor_ctrl = parametrize(force_torque2rotor_vel, "cf2x_L250") - -# Define current state -pos = np.array([0.0, 0.0, 0.5]) # Current position [x, y, z] -quat = np.array([0.0, 0.0, 0.0, 1.0]) # Current quaternion [x, y, z, w] -vel = np.array([0.0, 0.0, 0.0]) # Current velocity [vx, vy, vz] -ang_vel = np.array([0.0, 0.0, 0.0]) # Current angular velocity [wx, wy, wz] - -# Define command (13 elements) -# [x, y, z, vx, vy, vz, ax, ay, az, yaw, roll_rate, pitch_rate, yaw_rate] -cmd = np.array([1.0, 0.0, 1.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - -# Step 1: State to attitude control -rpyt_cmd, pos_error_integral = state_ctrl(pos, quat, vel, cmd) -print(f"Attitude command (R,P,Y,T): {rpyt_cmd}") - -# Step 2: Attitude to force/torque -force, torque, att_error_integral = attitude_ctrl(quat, ang_vel, rpyt_cmd) -print(f"Desired force: {force[0]:.3f} N") -print(f"Desired torque: {torque}") - -# Step 3: Force/torque to rotor velocities -rotor_speeds = rotor_ctrl(force, torque) -print(f"Rotor speeds: {rotor_speeds} rad/s") -``` - -## Working with Batches - -All controllers support batching through broadcasting. You can process multiple drones or time steps simultaneously: - -```python -import numpy as np -from drone_controllers import parametrize -from drone_controllers.mellinger import state2attitude - -# Parametrize controller -controller = parametrize(state2attitude, "cf2x_L250") - -# Batch processing: 3 drones, 5 time steps each -batch_shape = (3, 5) - -# Create batch states (3 drones × 5 timesteps) -pos_batch = np.random.randn(*batch_shape, 3) -quat_batch = np.tile([0, 0, 0, 1], (*batch_shape, 1)) # Level attitude -vel_batch = np.random.randn(*batch_shape, 3) * 0.1 -ang_vel_batch = np.random.randn(*batch_shape, 3) * 0.1 +| Variable | Shape | Description | +|---|---|---| +| `pos` | `(3,)` | Position in world frame [m] | +| `quat` | `(4,)` | Attitude as unit quaternion, scalar-last `xyzw` | +| `vel` | `(3,)` | Linear velocity in world frame [m/s] | -# Create batch commands -cmd_batch = np.zeros((*batch_shape, 13)) -cmd_batch[..., :3] = pos_batch + np.random.randn(*batch_shape, 3) * 0.5 # Target positions +The command `cmd` for `state2attitude` is a 13-element array: +`[x, y, z, vx, vy, vz, ax, ay, az, yaw, roll_rate, pitch_rate, yaw_rate]` in SI units and radians. -# Process entire batch at once -rpyt_batch, pos_err_batch = controller(pos_batch, quat_batch, vel_batch, cmd_batch) +## Evaluate a controller -print(f"Batch output shape: {rpyt_batch.shape}") # Should be (3, 5, 4) -print(f"Per-drone commands: {rpyt_batch[0, 0, :]}") # First drone, first timestep -``` - -## Manual Parameter Loading - -You can inspect or override parameters using `load_params`: - -```python -from drone_controllers.core import load_params - -params = load_params("mellinger", "state2attitude", "cf2x_L250") -print(f"Position gains: {params['kp']}") -print(f"Velocity gains: {params['kd']}") -print(f"Drone mass: {params['mass']} kg") -``` - -## Array API Compatibility - -The controllers work with different array libraries. Here's an example with JAX: - -```python -import jax.numpy as jnp -from drone_controllers import parametrize -from drone_controllers.mellinger import state2attitude - -# Create JAX arrays -pos = jnp.array([0.0, 0.0, 1.0]) -quat = jnp.array([0.0, 0.0, 0.0, 1.0]) -vel = jnp.array([0.0, 0.0, 0.0]) -ang_vel = jnp.array([0.0, 0.0, 0.0]) -cmd = jnp.ones(13) - -# Parametrize controller (works with any array API) -controller = parametrize(state2attitude, "cf2x_L250") - -# JIT compile for performance -from jax import jit -jit_controller = jit(controller) - -rpyt, pos_err = jit_controller(pos, quat, vel, cmd) -print(f"Output type: {type(rpyt)}") # JAX array -``` - -## Error Handling with Integral Terms - -Controllers maintain integral errors for robustness. Here's how to handle them properly: +`parametrize` loads the physical parameters for a specific drone and returns a `functools.partial` with those parameters pre-filled. You then call it with just the state and command. ```python import numpy as np from drone_controllers import parametrize from drone_controllers.mellinger import state2attitude -controller = parametrize(state2attitude, "cf2x_L250") - -# Initialize state -pos = np.array([0.0, 0.0, 0.5]) -quat = np.array([0.0, 0.0, 0.0, 1.0]) -vel = np.array([0.0, 0.0, 0.0]) +ctrl = parametrize(state2attitude, drone_model="cf2x_L250") -# Target hover at 1m altitude -cmd = np.array([0.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]) +# State: hovering at origin, upright, stationary. +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) # xyzw, identity (no rotation) +vel = np.zeros(3) -# First call - no integral error history -rpyt1, pos_err_i1 = controller(pos, quat, vel, cmd, ctrl_errors=None) +# Command: setpoint at origin, zero velocity and acceleration, yaw = 0. +cmd = np.zeros(13) -# Subsequent calls - pass integral error from previous step -rpyt2, pos_err_i2 = controller(pos, quat, vel, cmd, ctrl_errors=(pos_err_i1,)) - -print(f"Integral error evolution: {np.linalg.norm(pos_err_i1)} -> {np.linalg.norm(pos_err_i2)}") +rpyt, int_pos_err = ctrl(pos, quat, vel, cmd) ``` -## Available Drone Models - -Currently supported drone models: - -```python -from drone_controllers.drones import Drones - -# See all available models -for drone in Drones: - print(f"Model: {drone.value}") - -# Currently available: -# - cf2x_L250: Crazyflie 2.x with 250mm frame -``` +## Outputs -## Next Steps +`state2attitude` returns two arrays: -Now that you've seen the basics, explore: +| Return | Shape | Description | +|---|---|---| +| `rpyt` | `(4,)` | Attitude + thrust command: `[roll_rad, pitch_rad, yaw_rad, thrust_N]` | +| `int_pos_err` | `(3,)` | Position integral error; pass back next call to accumulate | -- **[API Reference](../api/core.md)** - Complete API documentation +## Next steps -## Common Issues - -### Import Errors - -If you get import errors, make sure you've installed the package correctly: - -```bash -pip install -e . # For development installation -``` - -### Array API Compatibility - -If you encounter issues with array operations, ensure you're using compatible versions: - -```bash -pip install numpy>=2.0.0 array-api-compat array-api-extra -``` - -### Parameter Loading Errors - -If parameter loading fails, check that the drone model is supported: - -```python -from drone_controllers.drones import Drones -print(list(Drones)) # Shows available models -``` +- [Controllers](../user-guide/controllers.md): all three Mellinger stages and how they chain +- [Mellinger](../user-guide/mellinger.md): input/output tables for every stage +- [Parametrize](../user-guide/parametrize.md): available drone models and array backends +- [Batching](../user-guide/batching.md): vectorized evaluation over many drones +- [Integral Errors](../user-guide/integral-errors.md): carrying state across timesteps diff --git a/docs/img/logo.png b/docs/img/logo.png new file mode 100644 index 0000000..ac95271 Binary files /dev/null and b/docs/img/logo.png differ diff --git a/docs/index.md b/docs/index.md index 4e553ab..3360b41 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,92 +1,45 @@ # Drone Controllers -$$ -u = K_p e + K_i \int e \, dt + K_d \frac{de}{dt} -$$ +**Drone Controllers** is a Python library providing faithful reimplementations of onboard drone controllers for simulation and research. +- **Array API standard**: works with NumPy, JAX, PyTorch, and any other compliant library +- **Pure functions**: every controller is stateless and JIT-compilable +- **Batching**: arbitrary leading batch dimensions via broadcasting +- **Research-focused**: designed for quadrotor UAV control and sim-to-real transfer -[![CI](https://github.com/learnsyslab/drone-controllers/actions/workflows/testing.yml/badge.svg)](https://github.com/learnsyslab/drone-controllers/actions) -[![License](https://img.shields.io/github/license/learnsyslab/drone-controllers)](https://github.com/learnsyslab/drone-controllers/blob/main/LICENSE) - -**Drone Controllers** is a Python library providing faithful reimplementations of onboard drone controllers that can be used for simulation and modelling. - -## Why use Drone Controllers? - -- **Array API Standard** — Controllers work with NumPy, JAX, PyTorch, and other array libraries -- **Pure Functions** — All controllers are implemented as pure functions for easy JIT compilation -- **Batching Support** — Built-in support for arbitrary batch dimensions via broadcasting -- **Research-focused** — Designed specifically for robotics research with quadrotor UAVs -- **Type-safe** — Full type hints for better development experience - -## Quick Start - -[Installation](getting-started/installation.md) is simple with pip: - -```bash -pip install drone-controllers -``` - -Here's a basic example using the Mellinger controller: +## Quick example ```python import numpy as np from drone_controllers import parametrize from drone_controllers.mellinger import state2attitude -# Get controller parameters for a specific drone model -controller = parametrize(state2attitude, "cf2x_L250") - -# Define state and command -pos = np.array([0.0, 0.0, 1.0]) # position [x, y, z] -quat = np.array([0.0, 0.0, 0.0, 1.0]) # quaternion [x, y, z, w] -vel = np.array([0.0, 0.0, 0.0]) # velocity [vx, vy, vz] +ctrl = parametrize(state2attitude, "cf2x_L250") -# 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]) +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +cmd = np.zeros(13) # [x, y, z, vx, vy, vz, ax, ay, az, yaw, rr, pr, yr] -# Compute control output -rpyt, pos_err_i = controller(pos, quat, vel, cmd) -print(f"Roll-Pitch-Yaw-Thrust command: {rpyt}") +rpyt, int_pos_err = ctrl(pos, quat, vel, cmd) ``` -## Key Features - -### Implemented Controllers - -- **[Mellinger Controller](api/mellinger.md)** — Geometric tracking controller based on the original Crazyflie implementation - -### Supported Drone Models - -- **cf2x_L250** — Crazyflie 2.x with 250mm frame -- More models coming soon! - -### Core Functionality - -- **[Parameter System](api/core.md)** — Automatic controller parametrization for different drone models -- **[Transform utilities](api/transform.md)** — Conversions between motor forces, rotor speeds, and PWM -- **[Drone registry](api/drones.md)** — Centralized catalog of supported drone platforms - -## Controller Architecture - -The library implements controllers as a pipeline of pure functions: - -1. **State → Attitude**: Convert desired state to attitude commands -2. **Attitude → Force/Torque**: Convert attitude commands to desired forces and torques -3. **Force/Torque → Rotor Speeds**: Convert desired forces/torques to individual motor commands +## Supported controllers -This modular design allows mixing and matching different components while maintaining compatibility. +| Controller | Module | Description | +|---|---|---| +| Mellinger | `drone_controllers.mellinger` | Geometric tracking controller based on the Crazyflie firmware [[1]](#references) | -## Array API Compatibility +Controllers are implemented as plain Python functions, so adding a new one requires no registration or subclassing. Contributions are welcome; see the [GitHub repository](https://github.com/learnsyslab/drone-controllers) to get started. -All controllers support the Python Array API standard, meaning you can use them with: +## Getting help -- **NumPy** — Standard numerical computing -- **JAX** — JIT compilation and automatic differentiation -- **PyTorch** — Deep learning integration -- **CuPy** — GPU acceleration +- [Get Started](getting-started/installation.md): install the package +- [User Guide](user-guide/controllers.md): how everything fits together +- [Examples](examples/index.md): progressive runnable examples +- [API Reference](api/core.md): complete function signatures +- [GitHub Issues](https://github.com/learnsyslab/drone-controllers/issues): bug reports and feature requests -## Getting Help +## References -- Read the [Getting Started](getting-started/installation.md) guide -- Browse the [API Reference](api/core.md) -- Report issues on [GitHub](https://github.com/learnsyslab/drone-controllers/issues) +[1] D. Mellinger and V. Kumar, "Minimum snap trajectory generation and control for quadrotors," ICRA 2011, doi: 10.1109/ICRA.2011.5980409. diff --git a/docs/user-guide/batching.md b/docs/user-guide/batching.md new file mode 100644 index 0000000..8d3582c --- /dev/null +++ b/docs/user-guide/batching.md @@ -0,0 +1,41 @@ +# Batching + +All controllers are built on Array API operations that broadcast over leading dimensions. Add a leading batch dimension to your state and command arrays and the controller evaluates all instances in a single call, with no loops and no special API. + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") + +N = 100 +pos = np.zeros((N, 3)) +quat = np.tile(np.array([0., 0., 0., 1.]), (N, 1)) +vel = np.zeros((N, 3)) +cmd = np.zeros((N, 13)) + +rpyt, int_pos_err = ctrl(pos, quat, vel, cmd) +rpyt.shape # (100, 4) +``` + +## Higher-dimensional batches + +Any number of leading dimensions works. A common pattern is a grid of environments, each containing multiple drones: + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") + +# 10 environments × 5 drones each +pos = np.zeros((10, 5, 3)) +quat = np.broadcast_to(np.array([0., 0., 0., 1.]), (10, 5, 4)).copy() +vel = np.zeros((10, 5, 3)) +cmd = np.zeros((10, 5, 13)) + +rpyt, _ = ctrl(pos, quat, vel, cmd) +rpyt.shape # (10, 5, 4) +``` diff --git a/docs/user-guide/controllers.md b/docs/user-guide/controllers.md new file mode 100644 index 0000000..b0e7c24 --- /dev/null +++ b/docs/user-guide/controllers.md @@ -0,0 +1,28 @@ +# Controllers + +A controller is a function that maps the current drone state and a command to actuator outputs. Every controller in this package is: + +- **A pure function**: no hidden state; integral errors are explicit return values you pass back on the next call +- **Array-API compatible**: works identically with NumPy, JAX, PyTorch, or any compliant library +- **Batchable**: add leading dimensions to any input array and the function evaluates all instances at once + +## The Mellinger pipeline + +The Mellinger controller [[1]](#references) is split into three stages that form a pipeline. Each stage can be used on its own, or all three can be chained to convert a full-state setpoint into individual motor speeds. + +| Stage | Function | Takes | Produces | +|---|---|---|---| +| 1 | [`state2attitude`](mellinger.md#state-to-attitude) | State + 13-element setpoint | RPYT command + position integral error | +| 2 | [`attitude2force_torque`](mellinger.md#attitude-to-force-torque) | Attitude + RPYT command | Collective force, body torques + angular velocity integral error | +| 3 | [`force_torque2rotor_vel`](mellinger.md#force-torque-to-rotor-velocities) | Force + torques | 4 motor speeds [RPM] | + + +## Available controllers + +| Module | Controller | Stages | +|---|---|---| +| `drone_controllers.mellinger` | Mellinger | `state2attitude`, `attitude2force_torque`, `force_torque2rotor_vel` | + +## References + +[1] D. Mellinger and V. Kumar, "Minimum snap trajectory generation and control for quadrotors," ICRA 2011, doi: 10.1109/ICRA.2011.5980409. diff --git a/docs/user-guide/integral-errors.md b/docs/user-guide/integral-errors.md new file mode 100644 index 0000000..6aaa426 --- /dev/null +++ b/docs/user-guide/integral-errors.md @@ -0,0 +1,75 @@ +# Integral Errors + +The Mellinger controller uses integral terms to reject steady-state errors. Because every controller in this package is a **pure function**, integral state cannot be stored inside the function; it is an explicit return value that you pass back on the next call. + +## Initialisation + +Pass `ctrl_errors=None` on the first call. The controller initialises the integral error to zero internally. + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +cmd = np.zeros(13) + +# First call — no integral history. +rpyt, int_pos_err = ctrl(pos, quat, vel, cmd, ctrl_errors=None) +``` + +## Carrying errors across timesteps + +Wrap the returned error in a tuple and pass it as `ctrl_errors` on the next call: + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +cmd = np.zeros(13) +cmd[0] = 1.0 # 1 m setpoint error in x + +ctrl_errors = None +for _ in range(10): + rpyt, int_pos_err = ctrl(pos, quat, vel, cmd, ctrl_errors=ctrl_errors) + ctrl_errors = (int_pos_err,) # carry forward + +# After 10 steps at 100 Hz, integral error ≈ 10 × 0.01 = 0.1 m +``` + +## Both stages have integral errors + +`state2attitude` tracks position error; `attitude2force_torque` tracks angular velocity error. Manage them independently: + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import attitude2force_torque, state2attitude + +state_ctrl = parametrize(state2attitude, "cf2x_L250") +att_ctrl = parametrize(attitude2force_torque, "cf2x_L250") + +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +ang_vel = np.zeros(3) +cmd = np.zeros(13) + +state_errs = None +att_errs = None + +for _ in range(5): + rpyt, int_pos_err = state_ctrl(pos, quat, vel, cmd, ctrl_errors=state_errs) + state_errs = (int_pos_err,) + + force, torque, r_int_err = att_ctrl(quat, ang_vel, rpyt, ctrl_errors=att_errs) + att_errs = (r_int_err,) +``` diff --git a/docs/user-guide/jit.md b/docs/user-guide/jit.md new file mode 100644 index 0000000..adde975 --- /dev/null +++ b/docs/user-guide/jit.md @@ -0,0 +1,66 @@ +# JIT Compilation + +Every controller is a pure function with no hidden state or side effects. In addition, they are implemented exclusively with Array API operations that are compatible with lazy JIT frameworks. Together, these two properties mean that every controller can be JIT compiled without any modification. + +```python +import jax +import jax.numpy as jnp +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250", xp=jnp) +jit_ctrl = jax.jit(ctrl) + +pos = jnp.zeros(3) +quat = jnp.array([0., 0., 0., 1.]) +vel = jnp.zeros(3) +cmd = jnp.zeros(13) + +rpyt, int_pos_err = jit_ctrl(pos, quat, vel, cmd) +``` +## Integral errors under JIT + +Integral errors are regular arrays and are handled as JAX pytree leaves, so they pass through `jax.jit` without any special treatment. + +```python +import jax +import jax.numpy as jnp +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250", xp=jnp) +jit_ctrl = jax.jit(ctrl) + +pos = jnp.zeros(3) +quat = jnp.array([0., 0., 0., 1.]) +vel = jnp.zeros(3) +cmd = jnp.zeros(13) + +ctrl_errors = None +for _ in range(10): + rpyt, int_pos_err = jit_ctrl(pos, quat, vel, cmd, ctrl_errors=ctrl_errors) + ctrl_errors = (int_pos_err,) +``` + +## Batched JIT + +Batching and JIT compose directly. Add leading dimensions to the state arrays and the same compiled function handles the entire batch. + +```python +import jax +import jax.numpy as jnp +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250", xp=jnp) +jit_ctrl = jax.jit(ctrl) + +N = 1_000 +pos = jnp.zeros((N, 3)) +quat = jnp.broadcast_to(jnp.array([0., 0., 0., 1.]), (N, 4)) +vel = jnp.zeros((N, 3)) +cmd = jnp.zeros((N, 13)) + +rpyt, _ = jit_ctrl(pos, quat, vel, cmd) +rpyt.shape # (1000, 4) +``` diff --git a/docs/user-guide/mellinger.md b/docs/user-guide/mellinger.md new file mode 100644 index 0000000..cf709c8 --- /dev/null +++ b/docs/user-guide/mellinger.md @@ -0,0 +1,153 @@ +# Mellinger Controller + +The Mellinger controller converts a full-state setpoint into individual motor speeds through three chained pure functions. The implementation closely follows the Crazyflie firmware to minimise sim-to-real gap. + +## State representation + +All three stages share the same state convention: + +| Variable | Shape | Units | Description | +|---|---|---|---| +| `pos` | `(..., 3)` | m | Position in world frame | +| `quat` | `(..., 4)` | | Attitude as unit quaternion, scalar-last `xyzw` | +| `vel` | `(..., 3)` | m/s | Linear velocity in world frame | +| `ang_vel` | `(..., 3)` | rad/s | Angular velocity in body frame | + +## Stage 1: State to attitude {#state-to-attitude} + +`state2attitude` is the position control loop. It converts a full-state setpoint into an attitude and collective thrust command (RPYT). + +**Inputs:** + +| Argument | Shape | Description | +|---|---|---| +| `pos` | `(..., 3)` | Current position [m] | +| `quat` | `(..., 4)` | Current attitude, xyzw | +| `vel` | `(..., 3)` | Current velocity [m/s] | +| `cmd` | `(..., 13)` | Setpoint: `[x, y, z, vx, vy, vz, ax, ay, az, yaw, rr, pr, yr]` | +| `ctrl_errors` | `tuple` or `None` | Integral errors from the previous call; `None` initialises to zero | +| `ctrl_freq` | `float` | Control frequency in Hz (default 100) | + +**Outputs:** + +| Return | Shape | Description | +|---|---|---| +| `rpyt` | `(..., 4)` | Attitude + thrust: `[roll_rad, pitch_rad, yaw_rad, thrust_N]` | +| `int_pos_err` | `(..., 3)` | Position integral error; pass as `ctrl_errors=(int_pos_err,)` next call | + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") + +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +cmd = np.zeros(13) # setpoint at origin, yaw = 0 + +rpyt, int_pos_err = ctrl(pos, quat, vel, cmd) +rpyt.shape # (4,) +int_pos_err.shape # (3,) +``` + +## Stage 2: Attitude to force/torque {#attitude-to-force-torque} + +`attitude2force_torque` is the attitude control loop. It converts an RPYT command into collective thrust and body-frame torques. + +**Inputs:** + +| Argument | Shape | Description | +|---|---|---| +| `quat` | `(..., 4)` | Current attitude, xyzw | +| `ang_vel` | `(..., 3)` | Current angular velocity in body frame [rad/s] | +| `cmd` | `(..., 4)` | RPYT from stage 1: `[roll_rad, pitch_rad, yaw_rad, thrust_N]` | +| `prev_ang_vel` | `(..., 3)` or `None` | Angular velocity from the previous call; `None` initialises to zero | +| `ctrl_errors` | `tuple` or `None` | Integral errors; `None` initialises to zero | +| `ctrl_freq` | `int` | Control frequency in Hz (default 500) | + +**Outputs:** + +| Return | Shape | Description | +|---|---|---| +| `force` | `(..., 1)` | Collective thrust [N] | +| `torque` | `(..., 3)` | Body-frame torques [N·m] | +| `r_int_err` | `(..., 3)` | Angular velocity integral error; pass as `ctrl_errors=(r_int_err,)` next call | + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import attitude2force_torque + +ctrl = parametrize(attitude2force_torque, "cf2x_L250") + +quat = np.array([0., 0., 0., 1.]) # identity, no rotation +ang_vel = np.zeros(3) +cmd = np.array([0., 0., 0., 0.3]) # level attitude, 0.3 N thrust + +force, torque, r_int_err = ctrl(quat, ang_vel, cmd) +force.shape # (1,) +torque.shape # (3,) +``` + +## Stage 3: Force/torque to rotor velocities {#force-torque-to-rotor-velocities} + +`force_torque2rotor_vel` converts collective thrust and body-frame torques into individual motor speeds, accounting for the motor mixing matrix. + +**Inputs:** + +| Argument | Shape | Description | +|---|---|---| +| `force` | `(..., 1)` | Desired collective thrust [N] | +| `torque` | `(..., 3)` | Desired body-frame torques [N·m] | + +**Outputs:** + +| Return | Shape | Description | +|---|---|---| +| `rotor_speeds` | `(..., 4)` | Individual motor speeds [RPM] | + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import force_torque2rotor_vel + +ctrl = parametrize(force_torque2rotor_vel, "cf2x_L250") + +force = np.array([0.2]) # total thrust [N] +torque = np.zeros(3) # no corrective torque + +rotor_speeds = ctrl(force, torque) +rotor_speeds.shape # (4,) +``` + +## Chaining all three stages + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import ( + attitude2force_torque, + force_torque2rotor_vel, + state2attitude, +) + +state_ctrl = parametrize(state2attitude, "cf2x_L250") +att_ctrl = parametrize(attitude2force_torque, "cf2x_L250") +rotor_ctrl = parametrize(force_torque2rotor_vel, "cf2x_L250") + +pos = np.array([0., 0., 1.]) # 1 m altitude +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +ang_vel = np.zeros(3) +cmd = np.zeros(13) +cmd[:3] = np.array([0., 0., 1.]) # hover at 1 m + +rpyt, _ = state_ctrl(pos, quat, vel, cmd) +force, torque, _ = att_ctrl(quat, ang_vel, rpyt) +rotor_speeds = rotor_ctrl(force, torque) +rotor_speeds.shape # (4,) +``` + +Integral errors from each stage should be passed back on the next call. See [Integral Errors](integral-errors.md) for the full pattern. diff --git a/docs/user-guide/parametrize.md b/docs/user-guide/parametrize.md new file mode 100644 index 0000000..aa7d6f8 --- /dev/null +++ b/docs/user-guide/parametrize.md @@ -0,0 +1,128 @@ +# Parametrize + +Every controller function takes physical parameters (gains, mass, mixing matrix, PWM bounds) as keyword-only arguments, and the exact values differ per drone. Rather than passing them at every call site, `parametrize` loads them for a named drone and binds them upfront, so call sites only need to provide state and command. + +The parameters stay individually accessible after binding. Because they are plain keyword-argument defaults on a `functools.partial`, any of them can be overridden at call time, or batched across a set of environments, without re-parametrizing the function. This makes it straightforward to randomize physical properties across a simulated batch. + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") + +# Inspect what was bound +list(ctrl.keywords.keys()) +# ['mass', 'kp', 'kd', 'ki', 'gravity_vec', 'mass_thrust', +# 'int_err_max', 'thrust_max', 'pwm_max'] +``` + +## Overriding parameters at call time + +Because `parametrize` returns a `functools.partial`, the bound parameters are just keyword-argument defaults. Pass a different value at call time to override for that call only; `ctrl.keywords` is not modified: + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +cmd = np.zeros(13) + +# Simulate with a heavier drone for this call only. +rpyt, _ = ctrl(pos, quat, vel, cmd, mass=0.035) +``` + +To make a change persist across all future calls, mutate `ctrl.keywords` directly: + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") +ctrl.keywords["mass"] = np.float64(0.035) +``` + +!!! warning + `ctrl.keywords` is a mutable dict shared across all references to the same partial. Call `parametrize` again for an independent copy. + +## Available drone configurations + +The following configurations ship with pre-fitted parameters: + +| `drone_model` | Platform | +|---|---| +| `"cf2x_L250"` | Crazyflie 2.x, L250 props | +| `"cf2x_P250"` | Crazyflie 2.x, P250 props | +| `"cf2x_T350"` | Crazyflie 2.x, T350 props | +| `"cf21B_500"` | Crazyflie 2.1 Brushless, 500 props | + +Pass the model name as a plain string: + +```python +import numpy as np +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250") +pos = np.zeros(3) +quat = np.array([0., 0., 0., 1.]) +vel = np.zeros(3) +cmd = np.zeros(13) +rpyt, _ = ctrl(pos, quat, vel, cmd) +``` + +## Loading raw parameters + +Use `load_params` to inspect or override the values that `parametrize` would bind: + +```python +from drone_controllers.core import load_params +from drone_controllers.mellinger import state2attitude + +params = load_params(state2attitude, "cf2x_L250") +float(params["mass"]) # 0.029 +``` + +`load_core_params` returns the shared `[core]` section for an entire controller module without filtering to a specific function's signature. This is useful when you need parameters like `rpm2thrust` that are not accepted by a particular stage: + +```python +from drone_controllers import mellinger +from drone_controllers.core import load_core_params + +core = load_core_params(mellinger, "cf2x_L250") +core["L"] # arm length [m] +``` + +## Switching array backends + +By default parameters are stored as NumPy arrays. Pass `xp` to convert them upfront, which avoids per-call conversion overhead in frameworks like PyTorch or JAX: + +```python +import torch +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize(state2attitude, "cf2x_L250", xp=torch) +``` + +You can also specify a compute device: + +```python +import jax +import jax.numpy as jnp +from drone_controllers import parametrize +from drone_controllers.mellinger import state2attitude + +ctrl = parametrize( + state2attitude, "cf2x_L250", + xp=jnp, device=jax.devices("cpu")[0], +) +``` + +The output backend is always inferred from the arrays you pass at call time, regardless of where the parameters live. + diff --git a/drone_controllers/core.py b/drone_controllers/core.py index 6f3c0c7..f520b9f 100644 --- a/drone_controllers/core.py +++ b/drone_controllers/core.py @@ -31,18 +31,18 @@ def parametrize( device: The device to use. If None, the device is inferred from the xp module. Example: - >>> from drone_controllers.core import parametrize - >>> from drone_controllers.mellinger import state2attitude - >>> controller_fn = parametrize(state2attitude, drone_model="cf2x_L250") - >>> command_rpyt, int_pos_err = controller_fn( - ... pos=pos, - ... quat=quat, - ... vel=vel, - ... ang_vel=ang_vel, - ... cmd=cmd, - ... ctrl_errors=(int_pos_err,), - ... ctrl_freq=100, - ... ) + ```python + import numpy as np + from drone_controllers import parametrize + from drone_controllers.mellinger import state2attitude + + ctrl = parametrize(state2attitude, "cf2x_L250") + pos = np.zeros(3) + quat = np.array([0.0, 0.0, 0.0, 1.0]) + vel = np.zeros(3) + cmd = np.zeros(13) + rpyt, int_pos_err = ctrl(pos, quat, vel, cmd) + ``` Returns: The parametrized controller function with all keyword argument only parameters filled in. diff --git a/drone_controllers/mellinger/control.py b/drone_controllers/mellinger/control.py index af4a026..03d002d 100644 --- a/drone_controllers/mellinger/control.py +++ b/drone_controllers/mellinger/control.py @@ -1,4 +1,13 @@ -"""...""" +"""Mellinger controller reimplementation based on the Crazyflie firmware. + +The controller is split into three pure functions that form a pipeline: +``state2attitude`` → ``attitude2force_torque`` → ``force_torque2rotor_vel``. +Each stage can be used independently or chained together to produce per-motor +RPM commands from a full-state setpoint. + +Reference: D. Mellinger and V. Kumar, "Minimum snap trajectory generation and +control for quadrotors", ICRA 2011. +""" from __future__ import annotations diff --git a/mkdocs.yml b/mkdocs.yml index f1a4e3b..a85b863 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,16 +7,20 @@ repo_name: learnsyslab/drone-controllers theme: name: material logo: img/logo_white.png - favicon: img/logo_white.png + favicon: img/logo.png features: - - navigation.tabs + - content.code.copy + - content.code.select + - content.code.annotate + - navigation.footer + - navigation.indexes - navigation.sections + - navigation.tabs - navigation.top - navigation.tracking - search.highlight - - search.share - - content.code.copy - - content.code.annotate + - search.suggest + - toc.follow palette: - scheme: default primary: custom @@ -33,9 +37,17 @@ theme: nav: - Home: index.md - - Getting Started: + - Get Started: - Installation: getting-started/installation.md - Quick Start: getting-started/quick-start.md + - User Guide: + - Controllers: user-guide/controllers.md + - Mellinger: user-guide/mellinger.md + - Parametrize: user-guide/parametrize.md + - Batching: user-guide/batching.md + - JIT Compilation: user-guide/jit.md + - Integral Errors: user-guide/integral-errors.md + - Examples: examples/index.md - API Reference: - Core: api/core.md - Drones: api/drones.md @@ -59,11 +71,7 @@ plugins: markdown_extensions: - admonition - pymdownx.details - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.superfences - pymdownx.highlight: anchor_linenums: true line_spans: __span @@ -90,3 +98,6 @@ extra: extra_css: - stylesheets/extra.css + +watch: + - drone_controllers/ diff --git a/pixi.lock b/pixi.lock index 275b54c..f70e8ad 100644 --- a/pixi.lock +++ b/pixi.lock @@ -146,7 +146,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/0e/bf298920729f216adcb002acf7ea01b90842603d2e4e2ce9b900d9ee8fab/nh3-0.3.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -211,7 +211,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/30/d162e99746a2fb1d98bb0ef23af3e201b156cf09f7de867c7390c8fe1c06/nh3-0.3.5-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl @@ -416,6 +416,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-34_h59b9bed_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-34_he106b2a_openblas.conda @@ -434,6 +435,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py313h8060acc_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py313hf6604e3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda @@ -452,7 +454,40 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - pypi: https://files.pythonhosted.org/packages/a0/d3/54cd560804a8c2b898824778e86c13c2a14600bc83532a9c4f69f2f469c3/array_api_compat-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/f7/9e14be985fd77ae26fee9136c9735e8987772e0ecf5f1f4e6e2b84cadc46/array_api_extra-0.10.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/11/d0/c177e29701cf1d3008d7d2b16b5fc626592ce13bd535f8795c5f57187e0e/cuda_pathfinder-1.5.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/95/305854c2ef2b645f7df1666be66b1167c392cc39384d09aca2e9499b71bf/jaxlib-0.10.0-cp313-cp313-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/b9/c3df11997d29e69b3f8edae1e903bf44eaf4774ccf4c5b6ddcebde88931c/pytest_markdown_docs-0.9.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: ./ osx-arm64: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -462,6 +497,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda @@ -477,6 +513,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.3-py314h1569ea8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda @@ -496,7 +533,21 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/a0/d3/54cd560804a8c2b898824778e86c13c2a14600bc83532a9c4f69f2f469c3/array_api_compat-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/f7/9e14be985fd77ae26fee9136c9735e8987772e0ecf5f1f4e6e2b84cadc46/array_api_extra-0.10.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/b9/c3df11997d29e69b3f8edae1e903bf44eaf4774ccf4c5b6ddcebde88931c/pytest_markdown_docs-0.9.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: ./ packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -835,6 +886,75 @@ packages: - check-sdist ; extra == 'pep8test' - click>=8.0.1 ; extra == 'pep8test' requires_python: '>=3.8,!=3.9.0,!=3.9.1' +- pypi: https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: cuda-bindings + version: 13.2.0 + sha256: 7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d + requires_dist: + - cuda-pathfinder~=1.1 + - cuda-toolkit[nvfatbin,nvjitlink,nvrtc,nvvm]==13.* ; extra == 'all' + - cuda-toolkit[cufile]==13.* ; sys_platform == 'linux' and extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/11/d0/c177e29701cf1d3008d7d2b16b5fc626592ce13bd535f8795c5f57187e0e/cuda_pathfinder-1.5.4-py3-none-any.whl + name: cuda-pathfinder + version: 1.5.4 + sha256: 9563d3175ce1828531acf4b94e1c1c7d67208c347ca002493e2654878b26f4b7 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl + name: cuda-toolkit + version: 13.0.2 + sha256: b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb + requires_dist: + - nvidia-cublas==13.1.0.3.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-cccl==13.0.85.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-crt==13.0.88.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-culibos==13.0.85.* ; sys_platform == 'linux' and extra == 'all' + - nvidia-cuda-cupti==13.0.85.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-cuxxfilt==13.0.85.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-nvcc==13.0.88.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-nvrtc==13.0.88.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-opencl==13.0.85.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-profiler-api==13.0.85.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-runtime==13.0.96.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-sanitizer-api==13.0.85.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cufft==12.0.0.61.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cufile==1.15.1.6.* ; sys_platform == 'linux' and extra == 'all' + - nvidia-curand==10.4.0.35.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cusolver==12.0.4.66.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cusparse==12.6.3.3.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-npp==13.0.1.2.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-nvfatbin==13.0.85.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-nvjitlink==13.0.88.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-nvjpeg==13.0.1.86.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-nvml-dev==13.0.87.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-nvptxcompiler==13.0.88.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-nvtx==13.0.85.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-nvvm==13.0.88.* ; (sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all') + - nvidia-cuda-cccl==13.0.85.* ; (sys_platform == 'linux' and extra == 'cccl') or (sys_platform == 'win32' and extra == 'cccl') + - nvidia-cuda-crt==13.0.88.* ; (sys_platform == 'linux' and extra == 'crt') or (sys_platform == 'win32' and extra == 'crt') + - nvidia-cublas==13.1.0.3.* ; (sys_platform == 'linux' and extra == 'cublas') or (sys_platform == 'win32' and extra == 'cublas') + - nvidia-cuda-runtime==13.0.96.* ; (sys_platform == 'linux' and extra == 'cudart') or (sys_platform == 'win32' and extra == 'cudart') + - nvidia-cufft==12.0.0.61.* ; (sys_platform == 'linux' and extra == 'cufft') or (sys_platform == 'win32' and extra == 'cufft') + - nvidia-cufile==1.15.1.6.* ; sys_platform == 'linux' and extra == 'cufile' + - nvidia-cuda-culibos==13.0.85.* ; sys_platform == 'linux' and extra == 'culibos' + - nvidia-cuda-cupti==13.0.85.* ; (sys_platform == 'linux' and extra == 'cupti') or (sys_platform == 'win32' and extra == 'cupti') + - nvidia-curand==10.4.0.35.* ; (sys_platform == 'linux' and extra == 'curand') or (sys_platform == 'win32' and extra == 'curand') + - nvidia-cusolver==12.0.4.66.* ; (sys_platform == 'linux' and extra == 'cusolver') or (sys_platform == 'win32' and extra == 'cusolver') + - nvidia-cusparse==12.6.3.3.* ; (sys_platform == 'linux' and extra == 'cusparse') or (sys_platform == 'win32' and extra == 'cusparse') + - nvidia-cuda-cuxxfilt==13.0.85.* ; (sys_platform == 'linux' and extra == 'cuxxfilt') or (sys_platform == 'win32' and extra == 'cuxxfilt') + - nvidia-npp==13.0.1.2.* ; (sys_platform == 'linux' and extra == 'npp') or (sys_platform == 'win32' and extra == 'npp') + - nvidia-cuda-nvcc==13.0.88.* ; (sys_platform == 'linux' and extra == 'nvcc') or (sys_platform == 'win32' and extra == 'nvcc') + - nvidia-nvfatbin==13.0.85.* ; (sys_platform == 'linux' and extra == 'nvfatbin') or (sys_platform == 'win32' and extra == 'nvfatbin') + - nvidia-nvjitlink==13.0.88.* ; (sys_platform == 'linux' and extra == 'nvjitlink') or (sys_platform == 'win32' and extra == 'nvjitlink') + - nvidia-nvjpeg==13.0.1.86.* ; (sys_platform == 'linux' and extra == 'nvjpeg') or (sys_platform == 'win32' and extra == 'nvjpeg') + - nvidia-nvml-dev==13.0.87.* ; (sys_platform == 'linux' and extra == 'nvml') or (sys_platform == 'win32' and extra == 'nvml') + - nvidia-nvptxcompiler==13.0.88.* ; (sys_platform == 'linux' and extra == 'nvptxcompiler') or (sys_platform == 'win32' and extra == 'nvptxcompiler') + - nvidia-cuda-nvrtc==13.0.88.* ; (sys_platform == 'linux' and extra == 'nvrtc') or (sys_platform == 'win32' and extra == 'nvrtc') + - nvidia-nvtx==13.0.85.* ; (sys_platform == 'linux' and extra == 'nvtx') or (sys_platform == 'win32' and extra == 'nvtx') + - nvidia-nvvm==13.0.88.* ; (sys_platform == 'linux' and extra == 'nvvm') or (sys_platform == 'win32' and extra == 'nvvm') + - nvidia-cuda-opencl==13.0.85.* ; (sys_platform == 'linux' and extra == 'opencl') or (sys_platform == 'win32' and extra == 'opencl') + - nvidia-cuda-profiler-api==13.0.85.* ; (sys_platform == 'linux' and extra == 'profiler') or (sys_platform == 'win32' and extra == 'profiler') + - nvidia-cuda-sanitizer-api==13.0.85.* ; (sys_platform == 'linux' and extra == 'sanitizer') or (sys_platform == 'win32' and extra == 'sanitizer') - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl name: docutils version: 0.22.4 @@ -843,7 +963,7 @@ packages: - pypi: ./ name: drone-controllers version: 0.2.0 - sha256: f97b3f74b3f3163b18ac4e2d4ac832ff714b92b3a7b9bbbd2144c695125ed1c5 + sha256: 04842707efcfc96772e70bcf19652e879b79d52f6a17229e2a77c99fd280615b requires_dist: - numpy>=2.0.0 - scipy>=1.17.0 @@ -872,6 +992,119 @@ packages: - pkg:pypi/exceptiongroup?source=hash-mapping size: 21333 timestamp: 1763918099466 +- pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + name: filelock + version: 3.29.0 + sha256: 96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + name: fsspec + version: 2026.4.0 + sha256: 11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2 + requires_dist: + - adlfs ; extra == 'abfs' + - adlfs ; extra == 'adl' + - pyarrow>=1 ; extra == 'arrow' + - dask ; extra == 'dask' + - distributed ; extra == 'dask' + - pre-commit ; extra == 'dev' + - ruff>=0.5 ; extra == 'dev' + - numpydoc ; extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - yarl ; extra == 'doc' + - dropbox ; extra == 'dropbox' + - dropboxdrivefs ; extra == 'dropbox' + - requests ; extra == 'dropbox' + - adlfs ; extra == 'full' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'full' + - dask ; extra == 'full' + - distributed ; extra == 'full' + - dropbox ; extra == 'full' + - dropboxdrivefs ; extra == 'full' + - fusepy ; extra == 'full' + - gcsfs>2024.2.0 ; extra == 'full' + - libarchive-c ; extra == 'full' + - ocifs ; extra == 'full' + - panel ; extra == 'full' + - paramiko ; extra == 'full' + - pyarrow>=1 ; extra == 'full' + - pygit2 ; extra == 'full' + - requests ; extra == 'full' + - s3fs>2024.2.0 ; extra == 'full' + - smbprotocol ; extra == 'full' + - tqdm ; extra == 'full' + - fusepy ; extra == 'fuse' + - gcsfs>2024.2.0 ; extra == 'gcs' + - pygit2 ; extra == 'git' + - requests ; extra == 'github' + - gcsfs ; extra == 'gs' + - panel ; extra == 'gui' + - pyarrow>=1 ; extra == 'hdfs' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'http' + - libarchive-c ; extra == 'libarchive' + - ocifs ; extra == 'oci' + - s3fs>2024.2.0 ; extra == 's3' + - paramiko ; extra == 'sftp' + - smbprotocol ; extra == 'smb' + - paramiko ; extra == 'ssh' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test' + - numpy ; extra == 'test' + - pytest ; extra == 'test' + - pytest-asyncio!=0.22.0 ; extra == 'test' + - pytest-benchmark ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-recording ; extra == 'test' + - pytest-rerunfailures ; extra == 'test' + - requests ; extra == 'test' + - aiobotocore>=2.5.4,<3.0.0 ; extra == 'test-downstream' + - dask[dataframe,test] ; extra == 'test-downstream' + - moto[server]>4,<5 ; extra == 'test-downstream' + - pytest-timeout ; extra == 'test-downstream' + - xarray ; extra == 'test-downstream' + - adlfs ; extra == 'test-full' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test-full' + - backports-zstd ; python_full_version < '3.14' and extra == 'test-full' + - cloudpickle ; extra == 'test-full' + - dask ; extra == 'test-full' + - distributed ; extra == 'test-full' + - dropbox ; extra == 'test-full' + - dropboxdrivefs ; extra == 'test-full' + - fastparquet ; extra == 'test-full' + - fusepy ; extra == 'test-full' + - gcsfs ; extra == 'test-full' + - jinja2 ; extra == 'test-full' + - kerchunk ; extra == 'test-full' + - libarchive-c ; extra == 'test-full' + - lz4 ; extra == 'test-full' + - notebook ; extra == 'test-full' + - numpy ; extra == 'test-full' + - ocifs ; extra == 'test-full' + - pandas<3.0.0 ; extra == 'test-full' + - panel ; extra == 'test-full' + - paramiko ; extra == 'test-full' + - pyarrow ; extra == 'test-full' + - pyarrow>=1 ; extra == 'test-full' + - pyftpdlib ; extra == 'test-full' + - pygit2 ; extra == 'test-full' + - pytest ; extra == 'test-full' + - pytest-asyncio!=0.22.0 ; extra == 'test-full' + - pytest-benchmark ; extra == 'test-full' + - pytest-cov ; extra == 'test-full' + - pytest-mock ; extra == 'test-full' + - pytest-recording ; extra == 'test-full' + - pytest-rerunfailures ; extra == 'test-full' + - python-snappy ; extra == 'test-full' + - requests ; extra == 'test-full' + - smbprotocol ; extra == 'test-full' + - tqdm ; extra == 'test-full' + - urllib3 ; extra == 'test-full' + - zarr ; extra == 'test-full' + - zstandard ; python_full_version < '3.14' and extra == 'test-full' + - tqdm ; extra == 'tqdm' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/ghp-import-2.1.0-pyhd8ed1ab_2.conda sha256: 40fdf5a9d5cc7a3503cd0c33e1b90b1e6eab251aaaa74e6b965417d089809a15 md5: 93f742fe078a7b34c29a182958d4d765 @@ -1131,6 +1364,54 @@ packages: - pytest-mypy>=1.0.1 ; extra == 'type' - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/70/aa/dfac6d72cc35bc07e7587115b6946e333ef4ccb2e6cd26ecf639438c5d26/jax-0.10.0-py3-none-any.whl + name: jax + version: 0.10.0 + sha256: 76c42ba163c8db3dc2e449e225b888c0edfb623ded31efdc96d85e0fda1d26e8 + requires_dist: + - jaxlib<=0.10.0,>=0.10.0 + - ml-dtypes>=0.5.0 + - numpy>=2.0 + - opt-einsum + - scipy>=1.14 + - jaxlib==0.10.0 ; extra == 'minimum-jaxlib' + - jaxlib==0.9.2 ; extra == 'ci' + - jaxlib<=0.10.0,>=0.10.0 ; extra == 'tpu' + - libtpu==0.0.40.* ; extra == 'tpu' + - requests ; extra == 'tpu' + - jaxlib<=0.10.0,>=0.10.0 ; extra == 'cuda' + - jax-cuda12-plugin[with-cuda]<=0.10.0,>=0.10.0 ; extra == 'cuda' + - jaxlib<=0.10.0,>=0.10.0 ; extra == 'cuda12' + - jax-cuda12-plugin[with-cuda]<=0.10.0,>=0.10.0 ; extra == 'cuda12' + - jaxlib<=0.10.0,>=0.10.0 ; extra == 'cuda13' + - jax-cuda13-plugin[with-cuda]<=0.10.0,>=0.10.0 ; extra == 'cuda13' + - jaxlib<=0.10.0,>=0.10.0 ; extra == 'cuda12-local' + - jax-cuda12-plugin<=0.10.0,>=0.10.0 ; extra == 'cuda12-local' + - jaxlib<=0.10.0,>=0.10.0 ; extra == 'cuda13-local' + - jax-cuda13-plugin<=0.10.0,>=0.10.0 ; extra == 'cuda13-local' + - jaxlib<=0.10.0,>=0.10.0 ; extra == 'rocm7-local' + - jax-rocm7-plugin==0.10.0.* ; extra == 'rocm7-local' + - kubernetes ; extra == 'k8s' + - xprof ; extra == 'xprof' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/a7/25/e1e52a21786b321fb6a2edf9ef9971aa70f06bb2738aef9afd6d8f46a441/jaxlib-0.10.0-cp314-cp314-macosx_11_0_arm64.whl + name: jaxlib + version: 0.10.0 + sha256: 98b26672943672742873f65bc03216819fc55325c99f146590d007c0172bff30 + requires_dist: + - scipy>=1.14 + - numpy>=2.0 + - ml-dtypes>=0.5.0 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f7/95/305854c2ef2b645f7df1666be66b1167c392cc39384d09aca2e9499b71bf/jaxlib-0.10.0-cp313-cp313-manylinux_2_27_x86_64.whl + name: jaxlib + version: 0.10.0 + sha256: d303dc31b65e8b793d5600f81b1583be03dc9b876a4c10b3e259b6609a1cbe3b + requires_dist: + - scipy>=1.14 + - numpy>=2.0 + - ml-dtypes>=0.5.0 + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl name: jeepney version: 0.9.0 @@ -1638,40 +1919,37 @@ packages: - pkg:pypi/markdown?source=hash-mapping size: 80353 timestamp: 1750360406187 -- pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl name: markdown-it-py - version: 4.2.0 - sha256: 9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a + version: 3.0.0 + sha256: 355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 requires_dist: - mdurl~=0.1 - psutil ; extra == 'benchmarking' - pytest ; extra == 'benchmarking' - pytest-benchmark ; extra == 'benchmarking' + - pre-commit~=3.0 ; extra == 'code-style' - commonmark~=0.9 ; extra == 'compare' - markdown~=3.4 ; extra == 'compare' - mistletoe~=1.0 ; extra == 'compare' - - mistune~=3.0 ; extra == 'compare' + - mistune~=2.0 ; extra == 'compare' - panflute~=2.3 ; extra == 'compare' - - markdown-it-pyrs ; extra == 'compare' - linkify-it-py>=1,<3 ; extra == 'linkify' - - mdit-py-plugins>=0.5.0 ; extra == 'plugins' + - mdit-py-plugins ; extra == 'plugins' - gprof2dot ; extra == 'profiling' - - mdit-py-plugins>=0.5.0 ; extra == 'rtd' + - mdit-py-plugins ; extra == 'rtd' - myst-parser ; extra == 'rtd' - pyyaml ; extra == 'rtd' - sphinx ; extra == 'rtd' - sphinx-copybutton ; extra == 'rtd' - sphinx-design ; extra == 'rtd' - - sphinx-book-theme~=1.0 ; extra == 'rtd' + - sphinx-book-theme ; extra == 'rtd' - jupyter-sphinx ; extra == 'rtd' - - ipykernel ; extra == 'rtd' - coverage ; extra == 'testing' - pytest ; extra == 'testing' - pytest-cov ; extra == 'testing' - pytest-regressions ; extra == 'testing' - - pytest-timeout ; extra == 'testing' - - requests ; extra == 'testing' - requires_python: '>=3.10' + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py313h8060acc_1.conda sha256: d812caf52efcea7c9fd0eafb21d45dadfd0516812f667b928bee50e87634fae5 md5: 21b62c55924f01b6eef6827167b46acb @@ -1968,11 +2246,56 @@ packages: - pkg:pypi/mkdocstrings-python?source=hash-mapping size: 57013 timestamp: 1770730332317 +- pypi: https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl + name: ml-dtypes + version: 0.5.4 + sha256: 2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22 + requires_dist: + - numpy>=1.21 + - numpy>=1.21.2 ; python_full_version >= '3.10' + - numpy>=1.23.3 ; python_full_version >= '3.11' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=2.1.0 ; python_full_version >= '3.13' + - absl-py ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pylint>=2.6.0 ; extra == 'dev' + - pyink ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: ml-dtypes + version: 0.5.4 + sha256: 533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d + requires_dist: + - numpy>=1.21 + - numpy>=1.21.2 ; python_full_version >= '3.10' + - numpy>=1.23.3 ; python_full_version >= '3.11' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - numpy>=2.1.0 ; python_full_version >= '3.13' + - absl-py ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pylint>=2.6.0 ; extra == 'dev' + - pyink ; extra == 'dev' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl name: more-itertools version: 11.0.2 sha256: 6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4 requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + name: mpmath + version: 1.3.0 + sha256: a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c + requires_dist: + - pytest>=4.6 ; extra == 'develop' + - pycodestyle ; extra == 'develop' + - pytest-cov ; extra == 'develop' + - codecov ; extra == 'develop' + - wheel ; extra == 'develop' + - sphinx ; extra == 'docs' + - gmpy2>=2.1.0a4 ; platform_python_implementation != 'PyPy' and extra == 'gmpy' + - pytest>=4.6 ; extra == 'tests' - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 md5: 47e340acb35de30501a76c7c799c41d7 @@ -1992,6 +2315,49 @@ packages: purls: [] size: 797030 timestamp: 1738196177597 +- pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + name: networkx + version: 3.6.1 + sha256: d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 + requires_dist: + - asv ; extra == 'benchmarking' + - virtualenv ; extra == 'benchmarking' + - numpy>=1.25 ; extra == 'default' + - scipy>=1.11.2 ; extra == 'default' + - matplotlib>=3.8 ; extra == 'default' + - pandas>=2.0 ; extra == 'default' + - pre-commit>=4.1 ; extra == 'developer' + - mypy>=1.15 ; extra == 'developer' + - sphinx>=8.0 ; extra == 'doc' + - pydata-sphinx-theme>=0.16 ; extra == 'doc' + - sphinx-gallery>=0.18 ; extra == 'doc' + - numpydoc>=1.8.0 ; extra == 'doc' + - pillow>=10 ; extra == 'doc' + - texext>=0.6.7 ; extra == 'doc' + - myst-nb>=1.1 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - osmnx>=2.0.0 ; extra == 'example' + - momepy>=0.7.2 ; extra == 'example' + - contextily>=1.6 ; extra == 'example' + - seaborn>=0.13 ; extra == 'example' + - cairocffi>=1.7 ; extra == 'example' + - igraph>=0.11 ; extra == 'example' + - scikit-learn>=1.5 ; extra == 'example' + - iplotx>=0.9.0 ; extra == 'example' + - lxml>=4.6 ; extra == 'extra' + - pygraphviz>=1.14 ; extra == 'extra' + - pydot>=3.0.1 ; extra == 'extra' + - sympy>=1.10 ; extra == 'extra' + - build>=0.10 ; extra == 'release' + - twine>=4.0 ; extra == 'release' + - wheel>=0.40 ; extra == 'release' + - changelist==0.5 ; extra == 'release' + - pytest>=7.2 ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pytest-mpl ; extra == 'test-extras' + - pytest-randomly ; extra == 'test-extras' + requires_python: '>=3.11,!=3.14.1' - pypi: https://files.pythonhosted.org/packages/1b/0e/bf298920729f216adcb002acf7ea01b90842603d2e4e2ce9b900d9ee8fab/nh3-0.3.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: nh3 version: 0.3.5 @@ -2043,6 +2409,90 @@ packages: - pkg:pypi/numpy?source=hash-mapping size: 6993182 timestamp: 1773839150339 +- pypi: https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl + name: nvidia-cublas + version: 13.1.0.3 + sha256: ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171 + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl + name: nvidia-cuda-cupti + version: 13.0.85 + sha256: 4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8 + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl + name: nvidia-cuda-nvrtc + version: 13.0.88 + sha256: ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575 + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: nvidia-cuda-runtime + version: 13.0.96 + sha256: 7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548 + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl + name: nvidia-cudnn-cu13 + version: 9.19.0.56 + sha256: d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf + requires_dist: + - nvidia-cublas + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: nvidia-cufft + version: 12.0.0.61 + sha256: 6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3 + requires_dist: + - nvidia-nvjitlink + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: nvidia-cufile + version: 1.15.1.6 + sha256: 08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44 + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl + name: nvidia-curand + version: 10.4.0.35 + sha256: 1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl + name: nvidia-cusolver + version: 12.0.4.66 + sha256: 0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112 + requires_dist: + - nvidia-cublas + - nvidia-nvjitlink + - nvidia-cusparse + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: nvidia-cusparse + version: 12.6.3.3 + sha256: 2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b + requires_dist: + - nvidia-nvjitlink + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl + name: nvidia-cusparselt-cu13 + version: 0.8.0 + sha256: 25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd +- pypi: https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl + name: nvidia-nccl-cu13 + version: 2.28.9 + sha256: e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42 + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl + name: nvidia-nvjitlink + version: 13.0.88 + sha256: 13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: nvidia-nvshmem-cu13 + version: 3.4.5 + sha256: 290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80 + requires_python: '>=3' +- pypi: https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl + name: nvidia-nvtx + version: 13.0.85 + sha256: 4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4 + requires_python: '>=3' - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda sha256: c9f54d4e8212f313be7b02eb962d0cb13a8dae015683a403d3accd4add3e520e md5: ffffb341206dd0dab0c36053c048d621 @@ -2066,6 +2516,11 @@ packages: purls: [] size: 3106008 timestamp: 1775587972483 +- pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl + name: opt-einsum + version: 3.4.0 + sha256: 69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 md5: 58335b26c38bf4a20f399384c33cbcf9 @@ -2309,6 +2764,14 @@ packages: - pkg:pypi/pytest?source=compressed-mapping size: 299984 timestamp: 1775644472530 +- pypi: https://files.pythonhosted.org/packages/0c/b9/c3df11997d29e69b3f8edae1e903bf44eaf4774ccf4c5b6ddcebde88931c/pytest_markdown_docs-0.9.2-py3-none-any.whl + name: pytest-markdown-docs + version: 0.9.2 + sha256: 9c05a5bee48214cb36583d4a5131d3a23b327a8d82c92ae860106053fd7d1f9e + requires_dist: + - markdown-it-py>=2.2.0,<4.0 + - pytest>=7.0.0 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.5-hec9711d_102_cp313.conda build_number: 102 sha256: c2cdcc98ea3cbf78240624e4077e164dc9d5588eefb044b4097c3df54d24d504 @@ -2666,6 +3129,63 @@ packages: - cryptography>=2.0 - jeepney>=0.6 requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl + name: setuptools + version: 81.0.0 + sha256: fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6 + requires_dist: + - pytest>=6,!=8.1.* ; extra == 'test' + - virtualenv>=13.0.0 ; extra == 'test' + - wheel>=0.44.0 ; extra == 'test' + - pip>=19.1 ; extra == 'test' + - packaging>=24.2 ; extra == 'test' + - jaraco-envs>=2.2 ; extra == 'test' + - pytest-xdist>=3 ; extra == 'test' + - jaraco-path>=3.7.2 ; extra == 'test' + - build[virtualenv]>=1.0.3 ; extra == 'test' + - filelock>=3.4.0 ; extra == 'test' + - ini2toml[lite]>=0.14 ; extra == 'test' + - tomli-w>=1.0.0 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-perf ; sys_platform != 'cygwin' and extra == 'test' + - jaraco-develop>=7.21 ; python_full_version >= '3.9' and sys_platform != 'cygwin' and extra == 'test' + - pytest-home>=0.5 ; extra == 'test' + - pytest-subprocess ; extra == 'test' + - pyproject-hooks!=1.1 ; extra == 'test' + - jaraco-test>=5.5 ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pygments-github-lexers==0.0.5 ; extra == 'doc' + - sphinx-favicon ; extra == 'doc' + - sphinx-inline-tabs ; extra == 'doc' + - sphinx-reredirects ; extra == 'doc' + - sphinxcontrib-towncrier ; extra == 'doc' + - sphinx-notfound-page>=1,<2 ; extra == 'doc' + - pyproject-hooks!=1.1 ; extra == 'doc' + - towncrier<24.7 ; extra == 'doc' + - packaging>=24.2 ; extra == 'core' + - more-itertools>=8.8 ; extra == 'core' + - jaraco-text>=3.7 ; extra == 'core' + - importlib-metadata>=6 ; python_full_version < '3.10' and extra == 'core' + - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'core' + - wheel>=0.43.0 ; extra == 'core' + - platformdirs>=4.2.2 ; extra == 'core' + - jaraco-functools>=4 ; extra == 'core' + - more-itertools ; extra == 'core' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - ruff>=0.13.0 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=2.2 ; extra == 'enabler' + - pytest-mypy ; extra == 'type' + - mypy==1.18.* ; extra == 'type' + - importlib-metadata>=7.0.2 ; python_full_version < '3.10' and extra == 'type' + - jaraco-develop>=7.21 ; sys_platform != 'cygwin' and extra == 'type' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d md5: 3339e3b65d58accf4ca4fb8748ab16b3 @@ -2678,6 +3198,15 @@ packages: - pkg:pypi/six?source=hash-mapping size: 18455 timestamp: 1753199211006 +- pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl + name: sympy + version: 1.14.0 + sha256: e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5 + requires_dist: + - mpmath>=1.1.0,<1.4 + - pytest>=7.1.0 ; extra == 'dev' + - hypothesis>=6.70.0 ; extra == 'dev' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1 md5: a0116df4f4ed05c303811a837d5b39d8 @@ -2725,6 +3254,72 @@ packages: - pkg:pypi/tomli?source=hash-mapping size: 21561 timestamp: 1774492402955 +- pypi: https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl + name: torch + version: 2.11.0 + sha256: 01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7 + requires_dist: + - filelock + - typing-extensions>=4.10.0 + - setuptools<82 + - sympy>=1.13.3 + - networkx>=2.5.1 + - jinja2 + - fsspec>=0.8.5 + - cuda-toolkit[cublas,cudart,cufft,cufile,cupti,curand,cusolver,cusparse,nvjitlink,nvrtc,nvtx]==13.0.2 ; sys_platform == 'linux' + - cuda-bindings>=13.0.3,<14 ; sys_platform == 'linux' + - nvidia-cudnn-cu13==9.19.0.56 ; sys_platform == 'linux' + - nvidia-cusparselt-cu13==0.8.0 ; sys_platform == 'linux' + - nvidia-nccl-cu13==2.28.9 ; sys_platform == 'linux' + - nvidia-nvshmem-cu13==3.4.5 ; sys_platform == 'linux' + - triton==3.6.0 ; sys_platform == 'linux' + - optree>=0.13.0 ; extra == 'optree' + - opt-einsum>=3.3 ; extra == 'opt-einsum' + - pyyaml ; extra == 'pyyaml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl + name: torch + version: 2.11.0 + sha256: cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db + requires_dist: + - filelock + - typing-extensions>=4.10.0 + - setuptools<82 + - sympy>=1.13.3 + - networkx>=2.5.1 + - jinja2 + - fsspec>=0.8.5 + - cuda-toolkit[cublas,cudart,cufft,cufile,cupti,curand,cusolver,cusparse,nvjitlink,nvrtc,nvtx]==13.0.2 ; sys_platform == 'linux' + - cuda-bindings>=13.0.3,<14 ; sys_platform == 'linux' + - nvidia-cudnn-cu13==9.19.0.56 ; sys_platform == 'linux' + - nvidia-cusparselt-cu13==0.8.0 ; sys_platform == 'linux' + - nvidia-nccl-cu13==2.28.9 ; sys_platform == 'linux' + - nvidia-nvshmem-cu13==3.4.5 ; sys_platform == 'linux' + - triton==3.6.0 ; sys_platform == 'linux' + - optree>=0.13.0 ; extra == 'optree' + - opt-einsum>=3.3 ; extra == 'opt-einsum' + - pyyaml ; extra == 'pyyaml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: triton + version: 3.6.0 + sha256: 10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9 + requires_dist: + - importlib-metadata ; python_full_version < '3.10' + - cmake>=3.20,<4.0 ; extra == 'build' + - lit ; extra == 'build' + - autopep8 ; extra == 'tests' + - isort ; extra == 'tests' + - numpy ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-forked ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - scipy>=1.7.1 ; extra == 'tests' + - llnl-hatchet ; extra == 'tests' + - matplotlib ; extra == 'tutorials' + - pandas ; extra == 'tutorials' + - tabulate ; extra == 'tutorials' + requires_python: '>=3.10,<3.15' - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl name: twine version: 6.2.0 diff --git a/pyproject.toml b/pyproject.toml index b4fdd45..3166e81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,8 +109,14 @@ pytest = ">=8.4.1" array-api-strict = ">=2.4.1,<2.5" numpy = ">=1.22.0" +[tool.pixi.feature.tests.pypi-dependencies] +torch = ">=2.7.0" +jax = ">=0.7.0" +pytest-markdown-docs = "*" + [tool.pixi.feature.tests.tasks] tests = { cmd = "pytest -v", description = "Run tests" } +test-docs = { cmd = "pytest -v --markdown-docs --markdown-docs-syntax=superfences drone_controllers/ docs/ --ignore=docs/gen_ref_pages.py", env = { SCIPY_ARRAY_API = "1" }, description = "Run doctests for docstrings and documentation" } [tool.pixi.feature.docs.dependencies] mkdocs = ">=1.5.3"