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
106 changes: 87 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,90 @@ Repository-specific notes for working in `MarinhoLab/working-needlemanipulation`

## Building the C++ extension (`_core`)

`pip install . --no-build-isolation` requires `cmake`, `ninja` and `g++` on
`PATH`. In this sandbox the venv's `bin` (e.g. `/tmp/venv-test/bin`) holds
`cmake`/`ninja` but is not on `PATH`; export it first:
`_core` is a pybind11 module built by CMake — `setup.py` drives CMake through
`setuptools`, and `CMakeLists.txt` compiles `src/core.cpp` +
`src/M3_SerialManipulatorSimulatorFriendly.cpp`.

Building requires:

- `cmake` (≥3.15), `ninja` and `g++` on `PATH`.
- **Eigen3** — `CMakeLists.txt` runs `find_package(Eigen3 REQUIRED)` and links
`Eigen3::Eigen`. Debian/Ubuntu: `sudo apt-get install libeigen3-dev` (the CI
build job does this; on Windows it is pulled in via vcpkg).
- **Initialised git submodules** — `submodules/pybind11` (v3.0) and
`submodules/dqrobotics/cpp` are pulled in with `add_subdirectory`. A plain
clone has empty submodule dirs, so run:

```
git submodule update --init --recursive
```

- The **version is taken from git tags** (`setuptools-git-versioning`,
`dynamic = ["version"]` in `pyproject.toml`), so an untagged checkout
produces a dev version.

Then install with:

```
export PATH="/tmp/venv-test/bin:$PATH"
pip install . --no-build-isolation
```

## dqrobotics solver classes (important)
On **aarch64/arm64** builds, `setup.py` appends `-ffp-contract=off` so the
floating-point results match the reference MATLAB behaviour — do not strip
that flag.

## QP solver (`marinholab-solvers-qpoases`)

The controllers solve the velocity QP with
`marinholab.solvers.qpoases.Solver` — the **`marinholab-solvers-qpoases`**
package (a qpOASES wrapper that ships prebuilt binaries). It is a **declared
runtime dependency** (`pyproject.toml`) and is installed alongside the package.

`dqrobotics.solvers.DQ_QuadprogSolver` (the concrete solver used by
`icra2019_controller.py`) is a thin Python wrapper around the `quadprog`
package, and dqrobotics imports it inside a **bare `try/except: pass`**.
Without `quadprog` installed, `DQ_QuadprogSolver` silently does not exist and
only the abstract `DQ_QuadraticProgrammingSolver` (pybind11, "pure virtual
function") is available.
`icra2019_controller.py` does `from marinholab.solvers.qpoases import Solver`
and stores it as `self.qp_solver`. Its
`solve_quadratic_program(H, f, A, b, Aeq, beq)` accepts `A=None`/`Aeq=None`
and returns the solution `x`, so the controller call sites are unchanged.

`quadprog` is a **declared runtime dependency** of this package
(`pyproject.toml`), and the `build` job in
`.github/workflows/python-publish.yml` installs it on purpose. If the
controller raises "pure virtual function" at `DQ_QuadprogSolver()`,
`quadprog` is missing in the environment.
## Constraint debug output (`_debug` and `verbose`)

All per-constraint debug printing lives in `marinholab/working/needlemanipulation/_debug.py`.
It centralises the messages with a single grammar: a fixed-width category tag
(e.g. `[radius #0]`) followed by `key=value` fields in a fixed numeric
format, and red `VIOLATION:` lines for breached constraints.

The available categories are `radius`, `plane`, `orientation`, `insertion` and
`rcm` (see `CONSTRAINT_CATEGORIES`). The public API accepts a single
`verbose` kwarg (type `Verbose = bool | dict[str, bool]`):

- `True` — print every category;
- `False` — print nothing (the default);
- `{"rcm": True, ...}` — select categories by name.

Both `ICRA19TaskSpaceController` and `NeedleController` accept `verbose=` and
normalise it via `normalize_verbose` at the controller boundary, so the
per-constraint helpers (`debug_radius`, `debug_plane`, `debug_orientation`,
`debug_insertion`, `debug_rcm`) each gate on their own category. To add a new
category: extend `CONSTRAINT_CATEGORIES` and add a matching `debug_*` helper.
Do not reintroduce per-constraint `verbose_*` kwargs — the single `verbose`
setting is the intended interface.

## Simulation scripts (`saul/`)

`saul/` holds the live end-to-end scripts for the pediatric-simulator
scenario (e.g. `insertion_1.py`, `needle_driving_*.py`). They are **not** part
of the installed package and are excluded from the type-checker; they run
against a live `PedriatricSimulator` process over TCP (`127.0.0.1`) and need
it on `PYTHONPATH` plus a running instance. Use them as references for how the
controllers are driven in a closed loop, not as a test suite.

## Tests

There is **no repository test suite** — `tests/` was removed together with
the `simulator_tests` merge (the old `conftest.py` mocked `_core` and is no
longer present). There is also no dedicated CI `test` job; the workflow only
runs `build` (matrix wheel builds) and `publish`. To smoke-test a change
locally, build the package in a venv and exercise the controller API:
locally, build the package (see the build section) and exercise the controller
API:

```
python -c "
Expand All @@ -49,6 +103,11 @@ print(W.shape)
"
```

> **Run it from outside the repo root** (e.g. `cd /tmp`). Running it from the
> repo root makes `import marinholab` resolve to the **source** tree, which
> does not contain the compiled `_core` extension, so the import fails. Run it
> from the repo root only after building `_core` in-place.

## CI build job: do not cache `build/`

The C++ build configures CMake with its binary directory inside `build/`, so
Expand Down Expand Up @@ -78,7 +137,16 @@ stricter than the symbols allow makes `repair` fail the build.
## Type checking with pyright (and the `stubs/` package)

The `marinholab` package is checked with **pyright** in `standard` mode
(`[tool.pyright]` in `pyproject.toml`). It runs clean: `0 errors, 0 warnings`.
(`[tool.pyright]` in `pyproject.toml`). The invariant is **0 errors**.
`dqrobotics` ships neither a `py.typed` marker nor `.pyi` stubs (its core is a
compiled extension), so the repo keeps its own typed stubs under
`stubs/dqrobotics` and points pyright at them via `stubPath` — type
information comes from those stubs, not the installed package. If the runtime
dependencies are not installed, pyright can additionally report
`reportMissingModuleSource` / `reportMissingImports` for the `dqrobotics`
imports; those are an environment issue, not a regression. Run the check with
the dependencies installed (see the build section); any *other* warning or any
error is a regression to fix.

The package depends on the third-party **`dqrobotics`** library, which is a
compiled pybind11 extension that ships **no `py.typed` marker and no `.pyi`
Expand All @@ -95,7 +163,7 @@ stubs/dqrobotics/
__init__.pyi # DQ + math helpers (i_, j_, k_, E_, conj, dot, ...)
robot_modeling/__init__.pyi # DQ_SerialManipulator, DQ_Kinematics
utils/__init__.pyi # DQ_Geometry
solvers/__init__.pyi # DQ_QuadraticProgrammingSolver, DQ_QuadprogSolver
solvers/__init__.pyi # DQ_QuadraticProgrammingSolver
```

pyright is pointed at it via `stubPath = "stubs"` in `pyproject.toml`, so it
Expand Down
Loading
Loading