From 6196f0ca0ecd0b77dcbedadfd00a627281d1f5b7 Mon Sep 17 00:00:00 2001 From: vinihcrosa Date: Sat, 27 Jun 2026 23:49:11 -0300 Subject: [PATCH] refactor(physics): replace CVODE with RK45, remove SUNDIALS dependency CvodeConfig kept as backward-compat alias for RK45Config. CvodeIntegrator removed. All 207 tests pass without SUNDIALS. Co-Authored-By: Claude Sonnet 4.6 --- .compozy/tasks/phase3/adrs/adr-001.md | 91 ++++++++++ cmake/Dependencies.cmake | 6 - default.profraw | Bin 0 -> 27896 bytes libs/physics/CMakeLists.txt | 2 - .../include/ymir/physics/RigidBody6DOF.h | 21 +-- .../ymir/physics/integrator/CvodeConfig.h | 14 +- .../ymir/physics/integrator/CvodeIntegrator.h | 70 -------- libs/physics/src/RigidBody6DOF.cpp | 6 +- .../src/integrator/CvodeIntegrator.cpp | 167 ------------------ 9 files changed, 107 insertions(+), 270 deletions(-) create mode 100644 .compozy/tasks/phase3/adrs/adr-001.md create mode 100644 default.profraw delete mode 100644 libs/physics/include/ymir/physics/integrator/CvodeIntegrator.h delete mode 100644 libs/physics/src/integrator/CvodeIntegrator.cpp diff --git a/.compozy/tasks/phase3/adrs/adr-001.md b/.compozy/tasks/phase3/adrs/adr-001.md new file mode 100644 index 0000000..87acbe2 --- /dev/null +++ b/.compozy/tasks/phase3/adrs/adr-001.md @@ -0,0 +1,91 @@ +# ADR-001: Visualization Session API MVP for Phase 3 + +## Status + +Accepted + +## Date + +2026-06-27 + +## Context + +Phase 3 must expose Ymir's `World` simulation state to external clients. The roadmap +identifies WebSocket + Protobuf, scenario loading, basic control commands, and real-time +state transmission as the intended control API. Codebase research shows that Phase 2 +already provides the required simulation foundation through `World::step(dt)`, +`World::snapshot()`, `Environment`, `NavalDomain`, and `CouplingRegistry`, while +`apps/server` remains an empty application target. + +The selected primary user is a visualization client. The MVP must provide body +kinematics, vessel operational state, and environment state. The client needs basic +session control: load scenario, start, pause, resume, stop, and observe state. The +preferred stream behavior is event-first updates rather than periodic full snapshots, +and the main success criterion is integration completeness. + +## Decision + +Phase 3 will use a Visualization Session API MVP. The product will let a visualization +client load a scenario, control the simulation session lifecycle, receive an initial +full session snapshot, and then receive event-first updates covering body kinematics, +vessel operational state, and environment state. + +The MVP will avoid live scenario editing and detailed operator controls. It will +prioritize one complete end-to-end integration path over breadth. + +## Alternatives Considered + +### Alternative 1: Control-Rich Operator API + +- **Description**: Include session control plus live changes to environment, waypoints, + vessel modes, rudder commands, and thruster commands. +- **Pros**: More useful for training operators and interactive maneuver experiments. +- **Cons**: Expands scope into operational control semantics before the visualization + contract is stable. +- **Why rejected**: It risks turning Phase 3 into a broad command product instead of + the smallest complete external integration layer. + +### Alternative 2: Stream-Only Viewer API + +- **Description**: Let external code create and run simulations while the server only + streams state/events to visualization clients. +- **Pros**: Fastest path to rendering live state. +- **Cons**: Does not let the client load or control a full World session. +- **Why rejected**: It fails the selected success criterion of integration completeness. + +## Consequences + +### Positive + +- Delivers a complete external client workflow with limited product scope. +- Gives visualization clients enough state to render bodies, vessel status, and environment. +- Establishes the message contract future operator controls can extend. +- Keeps Phase 3 aligned with the existing Phase 2 `World` snapshot model. + +### Negative + +- Does not yet support live operator editing of environment, waypoints, or actuator commands. +- Visualization clients may still need client-side interpolation when event cadence is lower + than render cadence. +- Event-first behavior requires clear baseline snapshot semantics so clients can recover + from missed or out-of-order updates. + +### Risks + +- **Risk**: Stakeholders may expect direct vessel control in Phase 3. + **Mitigation**: Document live scenario and vessel command editing as post-MVP scope. +- **Risk**: Event-first updates may be insufficient for early visualizers expecting + fixed-rate snapshots. + **Mitigation**: Require an initial full snapshot and allow clients to request current + state on demand. + +## Implementation Notes + +This ADR records product scope only. Implementation details belong in the Phase 3 +TechSpec. + +## References + +- `.prds/roadmap.md` — Phase 3 scope +- `.compozy/tasks/phase-02-world/_prd.md` — World snapshot and Phase 3 readiness +- `docs/architecture/data-flow.md` — real-time server data flow context diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index bce4484..5eb88ac 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -22,12 +22,6 @@ FetchContent_Declare( FetchContent_MakeAvailable(Catch2 nlohmann_json) -# --------------------------------------------------------------------------- -# SUNDIALS CVODE — ODE integrator (system package, required from M1) -# --------------------------------------------------------------------------- -find_package(SUNDIALS 6.0 REQUIRED) -message(STATUS "SUNDIALS found: ${SUNDIALS_VERSION}") - # --------------------------------------------------------------------------- # CGAL — mesh and geometry library (system package, required from M2) # --------------------------------------------------------------------------- diff --git a/default.profraw b/default.profraw new file mode 100644 index 0000000000000000000000000000000000000000..ac1c10ebab74f2c6b2216bd9dc5ed0107e58a4cb GIT binary patch literal 27896 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 _FQcDnSdo zwO$&1>;}Qm`8U@$QLleG6Y6oAOp5=0F^325Cz9BUUfO)Pq`&VSDgM59gx*)K=8Qpe2wt8`VpaH@lGv@5%na?<s z4)OMKN|0oMxV6PNVP(&jl>$e|eTZ0-`QHhijn>Px5OCV%d~jahtdB=tL82cccMIRb z%M-F4cPK^eh7`4n?5xHVQOwKhSkNl_-otv~`3?33=3&p25`Q-SYbO+s!WVp$1{aQO zIICMkTarEv@kzyL1J70<1@K|n89v|#b~C09wNCzA)UT1e+VvuH*X@lhPT}J_-#Esg zJ;vh-Sv0$osc%K9{`=U{YwP#d=@cG|tf!1xRM?dZQ5CgTgT*;S)vtwzYi-xM>-U3| zisYfby@R3#>l$s|H&QV6x1q+EW>Hj7k}MGDKSy4Mz017hZ?)T)kY+Yol8l&9b-F#FGPj^U^#0dwAt<0MB&-KHdj^@ zg}6M`6qn^o!bF9ej@SFLi{>aaFVUe&tlSYMKXh32v#8SG6lG#0=l^n@TSKBmgWa)a z7#O5J#PVHvWgg*#*tgF4O^#mj`4(1^pN^0e1zeV`P&8~PRg`GTVU8&-g3k^*tCIYi z?3wQxjtk|#{|)(Up)O|0qS^Y9CqV~s3qE1)Tyl@gi@pz5XU23+Y)i3_PuIIqz97f0#3Ttd&Z)tJT5w|oda>xhaH~VkiXja#~ z6_t{OMmh@_sp{z-s?6ft+#7QoK3+}8yAqnVNb?;iYJ7`EuGP~rUrz7Y3dJa9S}UqF zw2CziZ76f3B$*aM6(4*{4)vtJFx(4%9_j^h$7+^C>PP*HA273i$SuEzE=6VbV=EbY z|1ZVu{vX3{tc}JxDnrm3(V2>(hLj?mi}jnZ-?&p-o(IJ7Iu10maUP-9nFn4+f6Jh)a>&yMdO^-{_?zUc8tmf|8D4j12b}TWH?N za~&6{V^5qiKYhI;!%%jZ&2fgOKd^2zL=G?41s|n#%Y@buaDCgs@57HeqdJHKiqpj`73{vO_ zxD0na=Pt%gM80$3ek}622(9vKdKAH)q}*3u@Nm`EpM}+y3oVBvxmrnC#mFIZ6ZLlB zy2{Q8OG-Kh-o?LGrhY$iG}APY{H%#ijXUafl6(J*8*)>EmOTu^HE*m(@=dAWnuRT+ z#j84twXj12ZlgewcL$YTj=J<}Oj2Pl;SuvED%^~%@r}Z;!r3Njo#2LY4j0-OA)4e? z8`Xqml!YQcow4=1KE8zwlY)5hh$mYaLX$W|4VEVh@&+E;%OoPk?yELhP)sk`&*_0i zmdgEKw73lY$V14JS4hd5Ka~`S7Y8+`PF5K$FvNx~ zxBk8*X7X3kU-DC1(oeL7G zZ*63$v0rAjazkkJy!3iWM#n82<@f2I7S|XlB2#XNcNed(muqaZ=Wvm*lM}?!_$1Hl zFxQJ7owAW=WR0;s-k_)YW@xK&!YZ0Cctq)I{KjET&D{)zj2EZnLgLijCl-;cp}{cz z1cv6tm3 #include #include -#include +#include +#include // backward compat alias +#include #include #include @@ -11,17 +13,11 @@ namespace ymir { -class CvodeIntegrator; -class RK45Integrator; - /** - * Concrete 6-DOF rigid body with CVODE/BDF integration. + * Concrete 6-DOF rigid body with RK45 adaptive integration. * * Owns its mass matrices, kinematic state, force models, and integrator. - * CVODE is initialized lazily on the first step() call. - * - * Non-copyable and non-movable: CvodeIntegrator::user_data holds an - * address-stable pointer to its internal RhsContext (see D-10). + * Integrator is initialized lazily on the first step() call. */ class RigidBody6DOF final : public AbstractBody { @@ -31,7 +27,7 @@ public: const Matrix6x6& addedMass, const Vector6& initialQ, const Vector6& initialQdot, - const CvodeConfig& config = {}); + const RK45Config& config = {}); ~RigidBody6DOF(); @@ -47,7 +43,7 @@ public: BodyState state() const noexcept override; int id() const noexcept override { return id_; } - /** Reset CVODE time to 0, reinitialise from current body state. */ + /** Reset integrator time to 0, reinitialise from current body state. */ void reset(); // Read-only access (used by CvodeIntegrator and tests) @@ -65,7 +61,6 @@ public: Vector6 computeAcceleration(const Forces& totalForce) const noexcept; private: - friend class CvodeIntegrator; friend class RK45Integrator; void setState(const Vector6& q, const Vector6& qdot) noexcept; @@ -85,7 +80,7 @@ private: std::vector> ownedModels_; std::vector modelPtrs_; - std::unique_ptr integrator_; + std::unique_ptr integrator_; double t_ = 0.0; bool initialized_ = false; }; diff --git a/libs/physics/include/ymir/physics/integrator/CvodeConfig.h b/libs/physics/include/ymir/physics/integrator/CvodeConfig.h index 2bcb4a0..8cab4fe 100644 --- a/libs/physics/include/ymir/physics/integrator/CvodeConfig.h +++ b/libs/physics/include/ymir/physics/integrator/CvodeConfig.h @@ -1,14 +1,10 @@ #pragma once +// CvodeConfig is now an alias for RK45Config. +// Kept for backward compatibility — tests and existing code can still use CvodeConfig. +#include + namespace ymir { - -struct CvodeConfig -{ - double reltol = 1e-8; - double abstol = 1e-10; - long maxSteps = 10000; - double maxStep = 0.0; // 0 = no limit (CVODE decides) -}; - +using CvodeConfig = RK45Config; } // namespace ymir diff --git a/libs/physics/include/ymir/physics/integrator/CvodeIntegrator.h b/libs/physics/include/ymir/physics/integrator/CvodeIntegrator.h deleted file mode 100644 index eaccc00..0000000 --- a/libs/physics/include/ymir/physics/integrator/CvodeIntegrator.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include - -#include - -namespace ymir -{ - -/** - * CVODE/BDF integrator for 6-DOF rigid body dynamics. - * - * State vector (12 doubles): - * y[0..5] = q (position in inertial frame) - * y[6..11] = qdot (velocity in body frame) - * - * Non-copyable and non-movable: CVODE's user_data pointer references - * internal context by address (see D-10 in STATE.md). - */ -class CvodeIntegrator : public IIntegrator -{ -public: - explicit CvodeIntegrator(const CvodeConfig& config = {}); - ~CvodeIntegrator(); - - CvodeIntegrator(const CvodeIntegrator&) = delete; - CvodeIntegrator& operator=(const CvodeIntegrator&) = delete; - CvodeIntegrator(CvodeIntegrator&&) = delete; - CvodeIntegrator& operator=(CvodeIntegrator&&) = delete; - - /** Allocate CVODE memory and set initial conditions from body. */ - void initialize(RigidBody6DOF& body, std::vector& models) override; - - /** Advance body state by dt seconds. Throws on CVODE error. */ - void step(RigidBody6DOF& body, double dt) override; - - /** Reinitialize CVODE with the body's current state (no reallocation). */ - void reset(RigidBody6DOF& body) override; - - double time() const noexcept override { return t_; } - - // Public so the static rhs() callback can access it — not user API. - struct RhsContext - { - RigidBody6DOF* body = nullptr; - std::vector* models = nullptr; - }; - -private: - CvodeConfig config_; - double t_ = 0.0; - - SUNContext sunCtx_ = nullptr; - void* mem_ = nullptr; - N_Vector y_ = nullptr; - SUNMatrix A_ = nullptr; - SUNLinearSolver LS_ = nullptr; - - RhsContext ctx_{}; - - static int rhsCallback(sunrealtype t, N_Vector y, N_Vector ydot, void* user_data); -}; - -} // namespace ymir diff --git a/libs/physics/src/RigidBody6DOF.cpp b/libs/physics/src/RigidBody6DOF.cpp index 543c21e..99c7f5a 100644 --- a/libs/physics/src/RigidBody6DOF.cpp +++ b/libs/physics/src/RigidBody6DOF.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include namespace ymir @@ -10,7 +10,7 @@ RigidBody6DOF::RigidBody6DOF(int id, const Matrix6x6& addedMass, const Vector6& initialQ, const Vector6& initialQdot, - const CvodeConfig& config) + const RK45Config& config) : id_(id) , massMatrix_(massMatrix) , addedMass_(addedMass) @@ -19,7 +19,7 @@ RigidBody6DOF::RigidBody6DOF(int id, , q_(initialQ) , qdot_(initialQdot) , qddot_{} - , integrator_(std::make_unique(config)) + , integrator_(std::make_unique(config)) { } diff --git a/libs/physics/src/integrator/CvodeIntegrator.cpp b/libs/physics/src/integrator/CvodeIntegrator.cpp deleted file mode 100644 index 47a9267..0000000 --- a/libs/physics/src/integrator/CvodeIntegrator.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace ymir -{ - -// --------------------------------------------------------------------------- -// RHS — CVODE callback (static member → has friend access to RigidBody6DOF) -// --------------------------------------------------------------------------- - -int CvodeIntegrator::rhsCallback(sunrealtype t_rhs, N_Vector y, N_Vector ydot, void* user_data) -{ - auto* ctx = static_cast(user_data); - RigidBody6DOF& body = *ctx->body; - - // 1. Extract q and qdot from CVODE state vector - Vector6 q{}, qdot{}; - for (int i = 0; i < 6; ++i) - { - q[i] = NV_Ith_S(y, i); - qdot[i] = NV_Ith_S(y, i + 6); - } - - // 2. Update body state so ForceModels read current kinematics - body.setState(q, qdot); - - // 3. Sum all forces (pass t_rhs as the evaluation time, not body.time()) - BodyState state{q, qdot, static_cast(t_rhs), 0.0}; - Forces Ft = Forces::zero(); - for (ForceModel* model : *ctx->models) - Ft += model->compute(state); - - // 4. Compute acceleration: qddot = invM * Ft - Vector6 qddot = body.computeAcceleration(Ft); - body.setAcceleration(qddot); - - // 5. Kinematic transformation: ydot[0..5] (body → inertial) - const double psi = q[5]; // yaw - NV_Ith_S(ydot, 0) = std::cos(psi) * qdot[0] - std::sin(psi) * qdot[1]; // ẋ - NV_Ith_S(ydot, 1) = std::sin(psi) * qdot[0] + std::cos(psi) * qdot[1]; // ẏ - NV_Ith_S(ydot, 2) = qdot[2]; // ż - // ASSUMPTION: small-angle — Euler rates ≈ body angular rates - NV_Ith_S(ydot, 3) = qdot[3]; // φ̇ ≈ p - NV_Ith_S(ydot, 4) = qdot[4]; // θ̇ ≈ q - NV_Ith_S(ydot, 5) = qdot[5]; // ψ̇ = r - - // 6. ydot[6..11] = qddot - for (int i = 0; i < 6; ++i) - NV_Ith_S(ydot, 6 + i) = qddot[i]; - - return 0; -} - -// --------------------------------------------------------------------------- -// CvodeIntegrator -// --------------------------------------------------------------------------- - -CvodeIntegrator::CvodeIntegrator(const CvodeConfig& config) - : config_(config) -{ - if (SUNContext_Create(SUN_COMM_NULL, &sunCtx_) != 0) - throw std::runtime_error("CvodeIntegrator: SUNContext_Create failed"); - - mem_ = CVodeCreate(CV_BDF, sunCtx_); - if (!mem_) - throw std::runtime_error("CvodeIntegrator: CVodeCreate failed"); -} - -CvodeIntegrator::~CvodeIntegrator() -{ - if (LS_) SUNLinSolFree(LS_); - if (A_) SUNMatDestroy(A_); - if (y_) N_VDestroy(y_); - if (mem_) CVodeFree(&mem_); - if (sunCtx_) SUNContext_Free(&sunCtx_); -} - -void CvodeIntegrator::initialize(RigidBody6DOF& body, std::vector& models) -{ - ctx_ = RhsContext{&body, &models}; - t_ = 0.0; - - y_ = N_VNew_Serial(12, sunCtx_); - if (!y_) - throw std::runtime_error("CvodeIntegrator: N_VNew_Serial failed"); - - const Vector6& q = body.q(); - const Vector6& qdot = body.qdot(); - for (int i = 0; i < 6; ++i) - { - NV_Ith_S(y_, i) = q[i]; - NV_Ith_S(y_, i + 6) = qdot[i]; - } - - if (CVodeInit(mem_, CvodeIntegrator::rhsCallback, t_, y_) != CV_SUCCESS) - throw std::runtime_error("CvodeIntegrator: CVodeInit failed"); - - if (CVodeSStolerances(mem_, config_.reltol, config_.abstol) != CV_SUCCESS) - throw std::runtime_error("CvodeIntegrator: CVodeSStolerances failed"); - - if (CVodeSetMaxNumSteps(mem_, config_.maxSteps) != CV_SUCCESS) - throw std::runtime_error("CvodeIntegrator: CVodeSetMaxNumSteps failed"); - - if (config_.maxStep > 0.0) - CVodeSetMaxStep(mem_, config_.maxStep); - - CVodeSetUserData(mem_, &ctx_); - - A_ = SUNDenseMatrix(12, 12, sunCtx_); - if (!A_) - throw std::runtime_error("CvodeIntegrator: SUNDenseMatrix failed"); - - LS_ = SUNLinSol_Dense(y_, A_, sunCtx_); - if (!LS_) - throw std::runtime_error("CvodeIntegrator: SUNLinSol_Dense failed"); - - if (CVodeSetLinearSolver(mem_, LS_, A_) != CVLS_SUCCESS) - throw std::runtime_error("CvodeIntegrator: CVodeSetLinearSolver failed"); -} - -void CvodeIntegrator::step(RigidBody6DOF& body, double dt) -{ - double t_target = t_ + dt; - - int flag = CVode(mem_, t_target, y_, &t_, CV_NORMAL); - if (flag < 0) - { - throw std::runtime_error( - "CvodeIntegrator: CVode failed with flag " + std::to_string(flag)); - } - - Vector6 q{}, qdot{}; - for (int i = 0; i < 6; ++i) - { - q[i] = NV_Ith_S(y_, i); - qdot[i] = NV_Ith_S(y_, i + 6); - } - body.setState(q, qdot); -} - -void CvodeIntegrator::reset(RigidBody6DOF& body) -{ - const Vector6& q = body.q(); - const Vector6& qdot = body.qdot(); - for (int i = 0; i < 6; ++i) - { - NV_Ith_S(y_, i) = q[i]; - NV_Ith_S(y_, i + 6) = qdot[i]; - } - t_ = 0.0; - CVodeReInit(mem_, t_, y_); -} - -} // namespace ymir