Files
Ymir/tests/naval/TestWaveSpectrum.cpp
T
vinihcrosaandClaude Sonnet 4.6 dd4e79cbd6 feat(naval): add naval domain module (M2)
NavalForceModel bridge pattern keeps core::ForceModel interface intact
while providing NavalContext (environment, speedToWater, speedToWind,
EMA q_avg) to all naval models via bindContext.

Force models: Inertial (Coriolis), Restoring (hydrostatic spring),
Damping (potential + linear/quadratic), Squat (ICORELS shallow-water),
Wind (REGULAR/ACSINKAGE), Current (OBOKATA section-integration and
REGULAR with Van der Pol VIM RK4), Thrust (open-water Kt), Rudder
(Prandtl lift), Tug (PUSH/PULL/ESCORTING with bilinear table).

Wave engine: JONSWAP/PIERSON/REGULAR spectrum, Mitsuyasu cos-2S
directional spreading, WAMIT RAO interpolation in WaveForces.

NavalSimulation wraps core::Simulation, rebuilds NavalContext each
step, maintains EMA (tau=16.5s). 63/63 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 01:14:23 -03:00

80 lines
2.4 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <ymir/naval/wave/WaveSpectrum.h>
using Catch::Approx;
using namespace ymir::naval;
TEST_CASE("WaveSpectrum JONSWAP constructs without crash")
{
WaveSpectrum::Config cfg{};
cfg.spectrumType = WaveSpectrumType::JONSWAP;
cfg.significantWaveHeight = 2.0;
cfg.peakPeriod = 8.0;
cfg.gamma = 3.3;
cfg.seed = 42;
cfg.n_frequencies = 20;
cfg.n_directions = 8;
cfg.spreadingS = 2.0;
cfg.mainDirection_deg = 0.0;
WaveSpectrum spectrum(cfg);
REQUIRE(spectrum.numComponents() > 0);
}
TEST_CASE("WaveSpectrum PIERSON constructs")
{
WaveSpectrum::Config cfg{};
cfg.spectrumType = WaveSpectrumType::PIERSON;
cfg.significantWaveHeight = 1.5;
cfg.peakPeriod = 6.0;
cfg.seed = 123;
cfg.n_frequencies = 10;
cfg.n_directions = 4;
cfg.spreadingS = 1.0;
cfg.mainDirection_deg = 45.0;
WaveSpectrum spectrum(cfg);
REQUIRE(spectrum.numComponents() > 0);
}
TEST_CASE("WaveSpectrum REGULAR has single component")
{
WaveSpectrum::Config cfg{};
cfg.spectrumType = WaveSpectrumType::REGULAR;
cfg.significantWaveHeight = 1.0;
cfg.peakPeriod = 8.0;
cfg.mainDirection_deg = 0.0;
cfg.seed = 0;
cfg.n_frequencies = 1;
cfg.n_directions = 1;
WaveSpectrum spectrum(cfg);
REQUIRE(spectrum.numComponents() == 1);
}
TEST_CASE("WaveSpectrum total variance matches Hs estimate")
{
WaveSpectrum::Config cfg{};
cfg.spectrumType = WaveSpectrumType::JONSWAP;
cfg.significantWaveHeight = 3.0;
cfg.peakPeriod = 10.0;
cfg.gamma = 3.3;
cfg.seed = 7;
cfg.n_frequencies = 64;
cfg.n_directions = 18;
cfg.spreadingS = 2.0;
cfg.mainDirection_deg = 0.0;
WaveSpectrum spectrum(cfg);
// Total wave variance = sum of (a_i^2 / 2)
double m0 = 0.0;
for (const auto& comp : spectrum.components())
m0 += comp.amplitude * comp.amplitude / 2.0;
double Hs_computed = 4.0 * std::sqrt(m0);
// Should be within 20% of target Hs
REQUIRE(Hs_computed == Approx(cfg.significantWaveHeight).epsilon(0.20));
}