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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install package with dev dependencies
run: pip install -e ".[dev]"

- name: Lint with ruff
run: ruff check .

- name: Test with pytest
run: pytest tests/ -v
env:
MPLBACKEND: Agg # headless — no display server in CI
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ MANIFEST

# Jupyter
.ipynb_checkpoints/

# ruff
.ruff_cache/
12 changes: 6 additions & 6 deletions examples/01_imu_ekf_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
and runs the 10-state EKF to fuse accel/gyro/mag data.
"""

import numpy as np
import matplotlib.pyplot as plt
import numpy as np

from drones_sim.trajectory import generate_hover_accel_cruise
from drones_sim.estimation import ExtendedKalmanFilter
from drones_sim.models import load_drone_urdf
from drones_sim.sensors import IMUSimulator
from drones_sim.sensors.imu import IMUConfig
from drones_sim.estimation import ExtendedKalmanFilter
from drones_sim.trajectory import generate_hover_accel_cruise
from drones_sim.visualization.plots import plot_ekf_results
from drones_sim.models import load_drone_urdf


def main():
# Load drone URDF model (physics + geometry description)
urdf_model = load_drone_urdf()
print(urdf_model)
print(f" Total mass : {urdf_model.total_mass:.3f} kg")
print(f" Links : {[l.name for l in urdf_model.links]}")
print(f" Links : {[link.name for link in urdf_model.links]}")
print(f" Joints : {[j.name for j in urdf_model.joints]}")

# 1. Generate trajectory
Expand Down Expand Up @@ -56,7 +56,7 @@ def main():
filt_quat[i] = state["quaternion"]

# 4. Plot
fig = plot_ekf_results(
plot_ekf_results(
traj.t,
traj.position, traj.velocity, traj.orientation_quat,
filt_pos, filt_vel, filt_quat,
Expand Down
12 changes: 6 additions & 6 deletions examples/02_ekf_adaptive.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
sensor models.
"""

import numpy as np
import matplotlib.pyplot as plt
import numpy as np

from drones_sim.trajectory import generate_hover_accel_cruise
from drones_sim.estimation.ekf import AdaptiveEKF
from drones_sim.models import load_drone_urdf
from drones_sim.sensors import IMUSimulator
from drones_sim.sensors.imu import IMUConfig
from drones_sim.estimation.ekf import AdaptiveEKF
from drones_sim.trajectory import generate_hover_accel_cruise
from drones_sim.visualization.plots import plot_ekf_results
from drones_sim.models import load_drone_urdf


def main():
# Load drone URDF model (physics + geometry description)
urdf_model = load_drone_urdf()
print(urdf_model)
print(f" Total mass : {urdf_model.total_mass:.3f} kg")
print(f" Links : {[l.name for l in urdf_model.links]}")
print(f" Links : {[link.name for link in urdf_model.links]}")
print(f" Joints : {[j.name for j in urdf_model.joints]}")

# 1. Trajectory
Expand Down Expand Up @@ -62,7 +62,7 @@ def main():
filt_quat[i] = state["quaternion"]

# 4. Plot
fig = plot_ekf_results(
plot_ekf_results(
traj.t,
traj.position, traj.velocity, traj.orientation_quat,
filt_pos, filt_vel, filt_quat,
Expand Down
8 changes: 4 additions & 4 deletions examples/03_waypoint_nav.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@
acceleration, and jerk) rather than a step-function target jump.
"""

import numpy as np
import matplotlib.pyplot as plt
import numpy as np

from drones_sim.dynamics import QuadcopterDynamics
from drones_sim.control import QuadcopterController
from drones_sim.dynamics import QuadcopterDynamics
from drones_sim.models import load_drone_urdf
from drones_sim.trajectory import generate_minimum_snap
from drones_sim.visualization.plots import plot_quadcopter_results
from drones_sim.models import load_drone_urdf


def main():
# Load drone URDF model (physics + geometry description)
urdf_model = load_drone_urdf()
print(urdf_model)
print(f" Total mass : {urdf_model.total_mass:.3f} kg")
print(f" Links : {[l.name for l in urdf_model.links]}")
print(f" Links : {[link.name for link in urdf_model.links]}")
print(f" Joints : {[j.name for j in urdf_model.joints]}")

quad = QuadcopterDynamics()
Expand Down
4 changes: 2 additions & 2 deletions examples/04_viser_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import numpy as np

from drones_sim.dynamics import QuadcopterDynamics
from drones_sim.control import QuadcopterController
from drones_sim.dynamics import QuadcopterDynamics
from drones_sim.math_utils import euler_to_rotation_matrix
from drones_sim.visualization.viewer import DroneViewer
from drones_sim.models import load_drone_urdf
from drones_sim.visualization.viewer import DroneViewer


def main():
Expand Down
14 changes: 7 additions & 7 deletions examples/05_full_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@
"""

import os
import numpy as np

import matplotlib.pyplot as plt
import numpy as np

from drones_sim.dynamics import QuadcopterDynamics
from drones_sim.control.pid import PIDController
from drones_sim.sensors.models import SensorNoiseModel
from drones_sim.sensors import GPSSimulator, GPSConfig
from drones_sim.dynamics import QuadcopterDynamics
from drones_sim.estimation import ExtendedKalmanFilter
from drones_sim.math_utils import (
euler_to_rotation_matrix,
quat_from_euler,
quat_to_rotation_matrix,
)
from drones_sim.visualization.viewer import DroneViewer
from drones_sim.models import load_drone_urdf
from drones_sim.sensors import GPSConfig, GPSSimulator
from drones_sim.sensors.models import SensorNoiseModel
from drones_sim.visualization.viewer import DroneViewer


def main():
Expand Down Expand Up @@ -107,7 +107,7 @@ def main():
# Accelerometer measures specific force = R^T * (a_linear - g_world)
# which in hover (a_linear ≈ 0) is just R^T * g (the reaction to gravity).
# We approximate linear accel from the previous dynamics step.
lin_accel_world = np.array([0.0, 0.0, quad.g]) - np.array(
np.array([0.0, 0.0, quad.g]) - np.array(
[0.0, 0.0, quad.g]
)
# Proper: rotate net force / mass back to body frame as specific force
Expand Down
10 changes: 5 additions & 5 deletions examples/06_trajectory_following.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@

import numpy as np

from drones_sim.dynamics import QuadcopterDynamics
from drones_sim.control import QuadcopterController
from drones_sim.sensors.models import SensorNoiseModel
from drones_sim.sensors import GPSSimulator, GPSConfig
from drones_sim.dynamics import QuadcopterDynamics
from drones_sim.estimation import ExtendedKalmanFilter
from drones_sim.trajectory import generate_circular, generate_minimum_snap
from drones_sim.math_utils import euler_to_rotation_matrix
from drones_sim.visualization.viewer import DroneViewer
from drones_sim.models import load_drone_urdf
from drones_sim.sensors import GPSConfig, GPSSimulator
from drones_sim.sensors.models import SensorNoiseModel
from drones_sim.trajectory import generate_circular, generate_minimum_snap
from drones_sim.visualization.viewer import DroneViewer

# ---------------------------------------------------------------------------
# Module-level constants
Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,20 @@ dev = ["pytest>=7.0", "ruff"]
[tool.setuptools.packages.find]
where = ["src"]

[tool.ruff]
line-length = 100
target-version = "py310"
src = ["src", "tests", "examples"]
extend-exclude = [".venv"]

[tool.ruff.lint]
select = ["E", "F", "W", "I", "UP"]
ignore = ["E501"] # long lines — not worth a mass reformat

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q"
markers = ["slow: marks tests as slow (deselect with '-m \"not slow\"')"]

[tool.setuptools.package-data]
"drones_sim.models" = ["*.urdf"]
6 changes: 3 additions & 3 deletions src/drones_sim/control/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .pid import PIDController
from .cascaded import QuadcopterController
from .lqr import LQRController
from .cascaded import QuadcopterController # noqa: F401
from .lqr import LQRController # noqa: F401
from .pid import PIDController # noqa: F401
2 changes: 1 addition & 1 deletion src/drones_sim/dynamics/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .quadcopter import QuadcopterDynamics
from .quadcopter import QuadcopterDynamics # noqa: F401
2 changes: 1 addition & 1 deletion src/drones_sim/dynamics/quadcopter.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
from numpy.typing import NDArray

from ..math_utils import (
quat_derivative,
quat_from_euler,
quat_normalize,
quat_to_euler,
quat_to_rotation_matrix,
quat_derivative,
)


Expand Down
4 changes: 2 additions & 2 deletions src/drones_sim/estimation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .ekf import ExtendedKalmanFilter
from .ahrs import AHRS
from .ahrs import AHRS # noqa: F401
from .ekf import ExtendedKalmanFilter # noqa: F401
2 changes: 1 addition & 1 deletion src/drones_sim/estimation/ahrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import numpy as np
from numpy.typing import NDArray

from ..math_utils import quat_normalize, quat_multiply, quat_to_rotation_matrix
from ..math_utils import quat_normalize, quat_to_rotation_matrix


class AHRS:
Expand Down
7 changes: 3 additions & 4 deletions src/drones_sim/estimation/ekf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@
from numpy.typing import NDArray

from ..math_utils import (
quat_derivative,
quat_angular_velocity_jacobian,
quat_derivative,
quat_normalize,
quat_to_rotation_matrix,
)
from .ahrs import AHRS


# ---------------------------------------------------------------------------
# Full-state EKF (10-state) — from imu_ekf_simulation.py
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -482,8 +481,8 @@ def correct(self, accel: NDArray, adaptive_factor: float = 1.0) -> None:
self.x += K @ innovation

# Joseph form for numerical stability
I = np.eye(self.n)
IKH = I - K @ H
I_mat = np.eye(self.n)
IKH = I_mat - K @ H
self.P = IKH @ self.P @ IKH.T + K @ self.R_accel @ K.T

# -- state access ------------------------------------------------------
Expand Down
1 change: 0 additions & 1 deletion src/drones_sim/math_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import numpy as np
from numpy.typing import NDArray


# ---------------------------------------------------------------------------
# Quaternion operations
# ---------------------------------------------------------------------------
Expand Down
7 changes: 3 additions & 4 deletions src/drones_sim/models/urdf_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import numpy as np
from numpy.typing import NDArray


# ---------------------------------------------------------------------------
# Data structures
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -83,7 +82,7 @@ class DroneURDFModel:
total_inertia: NDArray = field(default_factory=lambda: np.zeros((3, 3)))

def __repr__(self) -> str:
link_names = [l.name for l in self.links]
link_names = [link.name for link in self.links]
return (
f"DroneURDFModel(name={self.name!r}, "
f"links={link_names}, "
Expand Down Expand Up @@ -275,8 +274,8 @@ def load_drone_urdf(urdf_path: str | Path | None = None) -> DroneURDFModel:
model.joints.append(joint)

# --- aggregates ---
model.total_mass = sum(l.mass for l in model.links)
model.total_inertia = sum(l.inertia for l in model.links) # type: ignore[assignment]
model.total_mass = sum(link.mass for link in model.links)
model.total_inertia = sum(link.inertia for link in model.links) # type: ignore[assignment]

return model

Expand Down
6 changes: 3 additions & 3 deletions src/drones_sim/sensors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .imu import IMUSimulator
from .models import SensorNoiseModel, TemperatureModel
from .gps import GPSSimulator, GPSConfig, GPSData
from .gps import GPSConfig, GPSData, GPSSimulator # noqa: F401
from .imu import IMUSimulator # noqa: F401
from .models import SensorNoiseModel, TemperatureModel # noqa: F401
3 changes: 1 addition & 2 deletions src/drones_sim/sensors/gps.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional
from dataclasses import dataclass

import numpy as np
from numpy.typing import NDArray
Expand Down
3 changes: 1 addition & 2 deletions src/drones_sim/sensors/imu.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional

import numpy as np
from numpy.typing import NDArray
Expand Down Expand Up @@ -46,7 +45,7 @@ class IMUData:
accel: NDArray # (N, 3)
gyro: NDArray # (N, 3)
mag: NDArray # (N, 3)
temperature: Optional[NDArray] = None # (N,) if temperature model enabled
temperature: NDArray | None = None # (N,) if temperature model enabled

# Ground-truth biases for validation
accel_bias: NDArray = field(default_factory=lambda: np.zeros(3))
Expand Down
6 changes: 3 additions & 3 deletions src/drones_sim/trajectory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

from __future__ import annotations

from dataclasses import dataclass, field
from dataclasses import dataclass
from math import factorial
from typing import Literal

import numpy as np
from numpy.polynomial.polynomial import polyder
from numpy.polynomial.polynomial import polyval as polyval1d
from numpy.typing import NDArray
from numpy.polynomial.polynomial import polyval as polyval1d, polyder
from scipy.linalg import lstsq
from scipy.spatial.transform import Rotation as R

Expand Down
4 changes: 2 additions & 2 deletions src/drones_sim/visualization/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .plots import plot_ekf_results, plot_quadcopter_results
from .viewer import DroneViewer
from .plots import plot_ekf_results, plot_quadcopter_results # noqa: F401
from .viewer import DroneViewer # noqa: F401
Loading
Loading