diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 94c7251..203f3c8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,4 +3,4 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" \ No newline at end of file + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2be5ff..393ef3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,9 +31,12 @@ jobs: python-version: ${{ matrix.python-version }} - run: python -m pip install crocoddyl[build] proxsuite[build] - run: echo "CMAKE_PREFIX_PATH=$(cmeel cmake)" >> $GITHUB_ENV + # TODO: remove this after the next crocoddyl release + # ref. https://github.com/loco-3d/crocoddyl/pull/1440 + - run: sed -i '1i \#include ' $CMAKE_PREFIX_PATH/include/crocoddyl/core/utils/conversions.hpp - run: cmake -B build -S . - run: cmake --build build -j 4 - - run: python -m pip install "osqp<1.0.0" + - run: python -m pip install osqp - run: echo "LD_LIBRARY_PATH=$(cmeel lib)" >> $GITHUB_ENV - run: cmake --build build -t test diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index f59f756..fdcdc9e 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -1,18 +1,16 @@ name: "CI - Nix" - on: push: branches: - - main - - devel + - master + - main pull_request: branches: - - main - - devel - + - devel + - master + - main jobs: - tests: - name: "Nix build on ${{ matrix.os }}" + nix: runs-on: "${{ matrix.os }}-latest" strategy: matrix: @@ -24,4 +22,14 @@ jobs: with: name: gepetto authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - - run: nix build -L + - run: nix flake check -L + check: + if: always() + name: check-macos-linux-nix + runs-on: ubuntu-latest + needs: + - nix + steps: + - uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/update-flake-lock.yml b/.github/workflows/update-flake-lock.yml index d24fa68..95e4cfb 100644 --- a/.github/workflows/update-flake-lock.yml +++ b/.github/workflows/update-flake-lock.yml @@ -16,4 +16,4 @@ jobs: - name: Update flake.lock uses: DeterminateSystems/update-flake-lock@main with: - token: ${{ secrets.GH_TOKEN_FOR_UPDATES }} \ No newline at end of file + token: ${{ secrets.GH_TOKEN_FOR_UPDATES }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1c42ea9..15620f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.5 + rev: v0.14.0 hooks: - id: ruff args: @@ -12,18 +12,18 @@ repos: hooks: - id: cmake-format - repo: https://github.com/pappasam/toml-sort - rev: v0.24.2 + rev: v0.24.3 hooks: - id: toml-sort-fix - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v19.1.7 + rev: v21.1.2 hooks: - id: clang-format args: - --style=Google exclude: '^.*\.ipynb$' - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-ast diff --git a/CHANGELOG.md b/CHANGELOG.md index 1780a2b..a6f72cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,7 +74,7 @@ Releases are available on the [github repository](https://github.com/machines-in - Added vectorization and malloc check options in CMakeLists - Added Github CI -### Changed +### Changed - Code optimization - Now checking stopping criteria for QP every 25 iterations in SolverCSQP (as a result, it is now more efficient to use SolverSQP for unconstrained problems) diff --git a/flake.lock b/flake.lock index 80a93b2..5a3b06c 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1741352980, - "narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=", + "lastModified": 1762440070, + "narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9", + "rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8", "type": "github" }, "original": { @@ -18,29 +18,126 @@ "type": "github" } }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gepetto": { + "inputs": { + "flake-parts": "flake-parts", + "nix-ros-overlay": "nix-ros-overlay", + "nix-system-graphics": "nix-system-graphics", + "nixpkgs": [ + "gepetto", + "nix-ros-overlay", + "nixpkgs" + ], + "src-agimus-controller": "src-agimus-controller", + "src-agimus-msgs": "src-agimus-msgs", + "src-franka-description": "src-franka-description", + "src-odri-control-interface": "src-odri-control-interface", + "src-odri-masterboard-sdk": "src-odri-masterboard-sdk", + "system-manager": "system-manager", + "systems": [ + "gepetto", + "nix-ros-overlay", + "flake-utils", + "systems" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1762792495, + "narHash": "sha256-rhs3zT1eavw6kjJ3wZSY+FR4jikS1NU6MFWBYNfpQUU=", + "owner": "gepetto", + "repo": "nix", + "rev": "6dccd759d955686bdf6e63006993219478de90df", + "type": "github" + }, + "original": { + "owner": "gepetto", + "repo": "nix", + "type": "github" + } + }, + "nix-ros-overlay": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1762593451, + "narHash": "sha256-Mmo6HuDXoYIt1Qez5ytc5ejdhUSoej9SqW5hhvNmEeg=", + "owner": "lopsided98", + "repo": "nix-ros-overlay", + "rev": "1622b7a0adbd01006800210bc7b298fd430a2d34", + "type": "github" + }, + "original": { + "owner": "lopsided98", + "ref": "develop", + "repo": "nix-ros-overlay", + "type": "github" + } + }, + "nix-system-graphics": { + "inputs": { + "nixpkgs": [ + "gepetto", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1737457219, + "narHash": "sha256-nX9dxoATDCSQgWw/iv6BngXDJEyHVYYEvHEVQ7Ig3fI=", + "owner": "soupglasses", + "repo": "nix-system-graphics", + "rev": "9c875e0c56cf2eb272b9102a4f3e24e4e31629fd", + "type": "github" + }, + "original": { + "owner": "soupglasses", + "repo": "nix-system-graphics", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1742422364, - "narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=", + "lastModified": 1759381078, + "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", "type": "github" }, "original": { - "owner": "NixOS", - "ref": "nixos-unstable", + "owner": "lopsided98", + "ref": "nix-ros", "repo": "nixpkgs", "type": "github" } }, "nixpkgs-lib": { "locked": { - "lastModified": 1740877520, - "narHash": "sha256-oiwv/ZK/2FhGxrCkQkB83i7GnWXPPLzoqFHpDD3uYpk=", + "lastModified": 1761765539, + "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "147dee35aab2193b174e4c0868bd80ead5ce755c", + "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc", "type": "github" }, "original": { @@ -51,8 +148,166 @@ }, "root": { "inputs": { - "flake-parts": "flake-parts", - "nixpkgs": "nixpkgs" + "flake-parts": [ + "gepetto", + "flake-parts" + ], + "gepetto": "gepetto", + "nix-ros-overlay": [ + "gepetto", + "nix-ros-overlay" + ], + "nixpkgs": [ + "gepetto", + "nixpkgs" + ], + "systems": [ + "gepetto", + "systems" + ], + "treefmt-nix": [ + "gepetto", + "treefmt-nix" + ] + } + }, + "src-agimus-controller": { + "flake": false, + "locked": { + "lastModified": 1762444174, + "narHash": "sha256-K05RaiRLm+EbCJKPw36c9YwSyhOTOvtDUx1I/HoBx6Q=", + "owner": "agimus-project", + "repo": "agimus_controller", + "rev": "09a30869d70c6c2689a2700c83eb73b6541d96f2", + "type": "github" + }, + "original": { + "owner": "agimus-project", + "repo": "agimus_controller", + "type": "github" + } + }, + "src-agimus-msgs": { + "flake": false, + "locked": { + "lastModified": 1759994370, + "narHash": "sha256-QuYtUR7VTOOtRBrYCxDjSLUP77wh0NlHbMtxZ1nSJFM=", + "owner": "agimus-project", + "repo": "agimus_msgs", + "rev": "e8e48c8c7b942cc2d2feba01f5e2d319c7915816", + "type": "github" + }, + "original": { + "owner": "agimus-project", + "repo": "agimus_msgs", + "type": "github" + } + }, + "src-franka-description": { + "flake": false, + "locked": { + "lastModified": 1762271824, + "narHash": "sha256-JySeYjg77n6SHstTqyy6PaYBzQcA38vkM6gjTs4Iwt0=", + "owner": "agimus-project", + "repo": "franka_description", + "rev": "fa4b70f76908c30d08fc9ae4bb77c45d67109311", + "type": "github" + }, + "original": { + "owner": "agimus-project", + "repo": "franka_description", + "type": "github" + } + }, + "src-odri-control-interface": { + "flake": false, + "locked": { + "lastModified": 1749138911, + "narHash": "sha256-+x+1NxiTwyg5Pwd1oBUMG3Z+eIj+VtVIRFvdPpXStU8=", + "owner": "gwennlbh", + "repo": "odri_control_interface", + "rev": "1137873714fd326fd3ab86cc5632bae72f965866", + "type": "github" + }, + "original": { + "owner": "gwennlbh", + "ref": "nix", + "repo": "odri_control_interface", + "type": "github" + } + }, + "src-odri-masterboard-sdk": { + "flake": false, + "locked": { + "lastModified": 1749026399, + "narHash": "sha256-0VhxOdC2cQwAJfAdHfeHovXrj9jrWb56F35rPN/u+eA=", + "owner": "gwennlbh", + "repo": "master-board", + "rev": "facc7f954294523a0f4b8389ac89fabad4c536db", + "type": "github" + }, + "original": { + "owner": "gwennlbh", + "ref": "nix", + "repo": "master-board", + "type": "github" + } + }, + "system-manager": { + "inputs": { + "nixpkgs": [ + "gepetto", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1761809512, + "narHash": "sha256-eax/ncPr9rw0qkcb7JKTyL9Un8BzsEe3Pnor8kG9AcQ=", + "owner": "numtide", + "repo": "system-manager", + "rev": "26fb1d50067e63c39be557e4fcedbd1aeec64c2e", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "system-manager", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "gepetto", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762410071, + "narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "97a30861b13c3731a84e09405414398fbf3e109f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index e871878..acec37c 100644 --- a/flake.nix +++ b/flake.nix @@ -2,69 +2,55 @@ description = "Implementation of numerical solvers used in the Machines in Motion Laboratory"; inputs = { - flake-parts.url = "github:hercules-ci/flake-parts"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + gepetto.url = "github:gepetto/nix"; + flake-parts.follows = "gepetto/flake-parts"; + nixpkgs.follows = "gepetto/nixpkgs"; + nix-ros-overlay.follows = "gepetto/nix-ros-overlay"; + systems.follows = "gepetto/systems"; + treefmt-nix.follows = "gepetto/treefmt-nix"; }; outputs = inputs: - inputs.flake-parts.lib.mkFlake { inherit inputs; } { - systems = inputs.nixpkgs.lib.systems.flakeExposed; - perSystem = - { - lib, - pkgs, - self', - ... - }: - { - apps.default = { - type = "app"; - program = pkgs.python3.withPackages (_: [ self'.packages.default ]); - }; - packages = { - default = self'.packages.py-mim-solvers; - mim-solvers = pkgs.mim-solvers.overrideAttrs { - src = lib.fileset.toSource { - root = ./.; - fileset = lib.fileset.unions [ - ./benchmarks - ./bindings - ./examples - ./include - ./python - ./src - ./tests - ./CMakeLists.txt - ./package.xml - ]; - }; + inputs.flake-parts.lib.mkFlake { inherit inputs; } ( + { lib, self, ... }: + { + systems = inputs.nixpkgs.lib.systems.flakeExposed; + imports = [ + inputs.gepetto.flakeModule + { gepetto-pkgs.overlays = [ self.overlays.default ]; } + ]; + flake.overlays.default = _final: prev: { + mim-solvers = prev.mim-solvers.overrideAttrs { + patches = [ ]; + src = lib.fileset.toSource { + root = ./.; + fileset = lib.fileset.unions [ + ./benchmarks + ./bindings + ./examples + ./include + ./python + ./src + ./tests + ./CMakeLists.txt + ./package.xml + ]; }; - py-mim-solvers = pkgs.python3Packages.toPythonModule ( - self'.packages.mim-solvers.overrideAttrs (super: { - pname = "py-${super.pname}"; - postPatch = ""; - cmakeFlags = super.cmakeFlags ++ [ - (lib.cmakeBool "BUILD_PYTHON_INTERFACE" true) - (lib.cmakeBool "BUILD_STANDALONE_PYTHON_INTERFACE" true) - ]; - nativeBuildInputs = super.nativeBuildInputs ++ [ - pkgs.python3Packages.python - ]; - propagatedBuildInputs = [ - pkgs.python3Packages.crocoddyl - pkgs.python3Packages.osqp - pkgs.python3Packages.proxsuite - pkgs.python3Packages.scipy - self'.packages.mim-solvers - ] - ++ super.propagatedBuildInputs; - nativeCheckInputs = [ - pkgs.python3Packages.pythonImportsCheckHook - ]; - }) - ); }; }; - }; + perSystem = + { pkgs, self', ... }: + { + apps.default = { + type = "app"; + program = pkgs.python3.withPackages (_: [ self'.packages.default ]); + }; + packages = { + default = self'.packages.mim-solvers; + mim-solvers = pkgs.python3Packages.mim-solvers.override { standalone = false; }; + }; + }; + } + ); } diff --git a/package.xml b/package.xml index 835d0aa..d222df0 100644 --- a/package.xml +++ b/package.xml @@ -5,22 +5,20 @@ Numerical solvers of the Machines in Motion Laboratory. Sébastien Kleff Sébastien Kleff - BSD + BSD-3-Clause https://github.com/machines-in-motion/mim_solvers git doxygen - - catkin - ament_cmake - python - python3 + ament_cmake + python3 boost crocoddyl eigenpy example-robot-data + jrl-cmakemodules cmake diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c36c543..9026ef5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,7 +29,8 @@ if(BUILD_PYTHON_INTERFACE) "tests/python/test_clqr_stagewise_admm.py" bindings) add_python_unit_test("py-test-lqr-sqp" "tests/python/test_lqr_problem.py" bindings) - add_python_unit_test("py-test-sqp-no-reg" "tests/python/test_sqp.py" bindings) + # TODO: ActuationModelMultiCopterBase was removed in crocoddyl 3.1.0 + # add_python_unit_test("py-test-sqp-no-reg" "tests/python/test_sqp.py" bindings) add_python_unit_test("py-test-sqp-taichi-convergence" "tests/python/test_sqp_taichi_convergence.py" bindings) add_python_unit_test("py-test-sqp-ur5-convergence" diff --git a/tests/factory/point-mass.hpp b/tests/factory/point-mass.hpp index e82c977..c2e93af 100644 --- a/tests/factory/point-mass.hpp +++ b/tests/factory/point-mass.hpp @@ -60,6 +60,17 @@ class DAMPointMass1D : public crocoddyl::DifferentialActionModelAbstract { // Destructor virtual ~DAMPointMass1D(); + // Explicit template instanciation + std::shared_ptr cloneAsDouble() + const override { + return std::make_shared(*this); + } + + std::shared_ptr cloneAsFloat() + const override { + return std::make_shared(*this); + } + // Cost & dynamics void calc(const std::shared_ptr& data, const Eigen::Ref& x, @@ -135,6 +146,17 @@ class DAMPointMass2D : public crocoddyl::DifferentialActionModelAbstract { // Destructor virtual ~DAMPointMass2D(); + // Explicit template instanciation + std::shared_ptr cloneAsDouble() + const override { + return std::make_shared(*this); + } + + std::shared_ptr cloneAsFloat() + const override { + return std::make_shared(*this); + } + // Cost & dynamics void calc(const std::shared_ptr& data, const Eigen::Ref& x, diff --git a/tests/python/test_clqr_osqp.py b/tests/python/test_clqr_osqp.py index 6fc02f7..2619e0d 100644 --- a/tests/python/test_clqr_osqp.py +++ b/tests/python/test_clqr_osqp.py @@ -39,31 +39,30 @@ def setUp(self): self.ddp1.eps_abs = eps_abs self.ddp2.eps_abs = eps_abs - # @unittest.skip("Skipping this test as it seems that the \"OSQP\" solver does not converge as of 12/09/2025.") def test_osqp_match(self): self.ddp1.solve(self.xs_init, self.us_init, 1) self.ddp2.solve(self.xs_init, self.us_init, 1) set_tol = 1e-8 self.assertEqual(self.ddp1.qp_iters, self.ddp2.qp_iters) - self.assertTrue( - np.linalg.norm(np.array(self.ddp1.xs) - - np.array(self.ddp2.xs)) < set_tol, + self.assertLess( + np.linalg.norm(np.array(self.ddp1.xs) - np.array(self.ddp2.xs)), + set_tol, "Test failed: xs mismatch", ) - self.assertTrue( - np.linalg.norm(np.array(self.ddp1.us) - - np.array(self.ddp2.us)) < set_tol, + self.assertLess( + np.linalg.norm(np.array(self.ddp1.us) - np.array(self.ddp2.us)), + set_tol, "Test failed: us mismatch", ) - self.assertTrue( - np.linalg.norm(np.array(self.ddp1.lag_mul) - - np.array(self.ddp2.lag_mul)) - < set_tol, + self.assertLess( + np.linalg.norm(np.array(self.ddp1.lag_mul) - np.array(self.ddp2.lag_mul)), + set_tol, "Test failed: lag_mul mismatch", ) for t in range(len(self.ddp1.y)): - self.assertTrue( - np.linalg.norm(self.ddp1.y[t] - self.ddp2.y[t]) < set_tol, + self.assertLess( + np.linalg.norm(self.ddp1.y[t] - self.ddp2.y[t]), + set_tol, f"Test failed: y mismatch at t={t}", )