Implements the 6-DOF rigid body dynamics core: - Types (Vector6, Matrix6x6), Forces, BodyState value type - ForceModel abstract interface (non-const compute, D-09) - Body: mass matrix inversion at construction, friend CvodeIntegrator (D-11) - CvodeIntegrator: BDF integrator via SUNDIALS CVODE 7.x, static rhsCallback - Simulation: orchestrates Body + CvodeIntegrator + ForceModel registry - Math utilities: LinearAlgebra (invert6x6, matVecProduct), AngleUtils, Interpolation - 29 tests, 100% passing (zero-force, constant-force, SHO period, time accumulation) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
37 lines
890 B
C++
37 lines
890 B
C++
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
|
|
#include <ymir/core/ForceModel.h>
|
|
|
|
using Catch::Matchers::WithinAbs;
|
|
using namespace ymir;
|
|
|
|
TEST_CASE("ZeroForceModel always returns zero forces", "[forcemodel]")
|
|
{
|
|
ZeroForceModel model;
|
|
|
|
BodyState state{Vector6{}, Vector6{}, 0.0, 0.1};
|
|
Forces f = model.compute(state);
|
|
|
|
for (int i = 0; i < 6; ++i)
|
|
REQUIRE_THAT(f.f[i], WithinAbs(0.0, 1e-15));
|
|
}
|
|
|
|
TEST_CASE("ZeroForceModel name is 'zero'", "[forcemodel]")
|
|
{
|
|
ZeroForceModel model;
|
|
REQUIRE(model.name() == "zero");
|
|
}
|
|
|
|
TEST_CASE("ForceModel default name is 'unnamed'", "[forcemodel]")
|
|
{
|
|
// Minimal concrete implementation
|
|
struct TestForce : ForceModel
|
|
{
|
|
Forces compute(const BodyState&) override { return Forces::zero(); }
|
|
};
|
|
|
|
TestForce f;
|
|
REQUIRE(f.name() == "unnamed");
|
|
}
|