Skip to content

Commit d1464d5

Browse files
authored
Port PR #77 - wave stretching fixes to refactored wave modules (#80)
* Port Wheeler stretching fixes from PR #77 (by tridelat) PR #77 was made before a major refactor - I've ported the changes over into the relevant new locations. * Improve robustness in wave kinematics utilities - Treat non-positive/very large/infinite water depth as “effectively infinite” in is_in_deep_water() (consistent with ComputeWaveNumber()), and clarify the 89.4 kh threshold is a numerical switch (tanh(kh)≈1). - Avoid Wheeler stretching when depth is non-physical/unbounded (≤0, inf, or very large), and guard against h + eta near zero to prevent division-by-zero. - Add checks and small‑kh limiting forms in water velocity/acceleration to avoid sinh(kh) division‑by‑zero / NaNs as k -> 0.
1 parent 3d6d454 commit d1464d5

5 files changed

Lines changed: 98 additions & 22 deletions

File tree

include/hydroc/waves/regular_wave.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class RegularWave : public WaveBase {
2626
double regular_wave_amplitude_;
2727
double regular_wave_omega_;
2828
double regular_wave_phase_ = 0.0;
29+
bool wave_stretching_ = true;
2930

3031
void AddH5Data(std::vector<HydroData::RegularWaveInfo>& reg_h5_data, HydroData::SimulationParameters& sim_data);
3132

src/hydro/waves/irregular_wave.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,7 @@ void IrregularWaves::AddH5Data(std::vector<HydroData::IrregularWaveInfo>& irreg_
110110
Eigen::Vector3d IrregularWaves::GetVelocity(const Eigen::Vector3d& position, double time) {
111111
auto position_stretched = position;
112112
if (params_.wave_stretching_) {
113-
auto eta = GetEtaIrregular(position, time, spectrum_frequencies_, spectral_densities_, spectral_widths_, wave_phases_, wavenumbers_);
114-
auto z_pos = position.z() - mwl_;
115-
position_stretched[2] = water_depth_ * (z_pos - eta) / (water_depth_ + eta);
113+
position_stretched = GetWheelerStretchedPosition(position, GetElevation(position, time), water_depth_, mwl_);
116114
}
117115

118116
return GetWaterVelocityIrregular(position_stretched,
@@ -129,9 +127,7 @@ Eigen::Vector3d IrregularWaves::GetVelocity(const Eigen::Vector3d& position, dou
129127
Eigen::Vector3d IrregularWaves::GetAcceleration(const Eigen::Vector3d& position, double time) {
130128
auto position_stretched = position;
131129
if (params_.wave_stretching_) {
132-
auto eta = GetEtaIrregular(position, time, spectrum_frequencies_, spectral_densities_, spectral_widths_, wave_phases_, wavenumbers_);
133-
auto z_pos = position.z() - mwl_;
134-
position_stretched[2] = water_depth_ * (z_pos - eta) / (water_depth_ + eta);
130+
position_stretched = GetWheelerStretchedPosition(position, GetElevation(position, time), water_depth_, mwl_);
135131
}
136132

137133
return GetWaterAccelerationIrregular(position_stretched,

src/hydro/waves/regular_wave.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ void RegularWave::AddH5Data(std::vector<HydroData::RegularWaveInfo>& reg_h5_data
4242
}
4343

4444
Eigen::Vector3d RegularWave::GetVelocity(const Eigen::Vector3d& position, double time) {
45-
return GetWaterVelocity(position,
45+
auto position_stretched = position;
46+
if (wave_stretching_) {
47+
position_stretched = GetWheelerStretchedPosition(position, GetElevation(position, time), water_depth_, mwl_);
48+
}
49+
return GetWaterVelocity(position_stretched,
4650
time,
4751
regular_wave_omega_,
4852
regular_wave_amplitude_,
@@ -53,7 +57,11 @@ Eigen::Vector3d RegularWave::GetVelocity(const Eigen::Vector3d& position, double
5357
}
5458

5559
Eigen::Vector3d RegularWave::GetAcceleration(const Eigen::Vector3d& position, double time) {
56-
return GetWaterAcceleration(position,
60+
auto position_stretched = position;
61+
if (wave_stretching_) {
62+
position_stretched = GetWheelerStretchedPosition(position, GetElevation(position, time), water_depth_, mwl_);
63+
}
64+
return GetWaterAcceleration(position_stretched,
5765
time,
5866
regular_wave_omega_,
5967
regular_wave_amplitude_,

src/hydro/waves/wave_utilities.cpp

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,43 @@
1515
#include <fstream>
1616
#include <iomanip>
1717
#include <iostream>
18+
#include <limits>
1819

1920
#include <hydroc/logging.h>
2021

22+
bool is_in_deep_water(double wavenumber, double water_depth) {
23+
// Keep behavior consistent with ComputeWaveNumber(), which treats non-positive depth, very large
24+
// depth, or infinite depth as "effectively infinite".
25+
constexpr double EFFECTIVE_INFINITE_DEPTH = 1000.0;
26+
if (water_depth <= 0.0 || water_depth > EFFECTIVE_INFINITE_DEPTH || std::isinf(water_depth)) {
27+
return true;
28+
}
29+
30+
// Numerical threshold for using deep-water asymptotic form (tanh(kh) ≈ 1).
31+
return (std::abs(wavenumber) * water_depth > 89.4);
32+
}
33+
34+
Eigen::Vector3d GetWheelerStretchedPosition(const Eigen::Vector3d& position, double eta, double water_depth, double mwl) {
35+
// Wheeler stretching assumes finite depth. If depth is being used as a sentinel for "infinite",
36+
// fall back to no stretching to avoid undefined behavior.
37+
if (water_depth <= 0.0 || std::isinf(water_depth)) {
38+
return position;
39+
}
40+
41+
// Position relative to mean water level
42+
double z_pos = position.z() - mwl;
43+
// Wheeler stretching
44+
double denom = water_depth + eta;
45+
const double scale = std::max(1.0, std::abs(water_depth));
46+
const double eps = std::max(1e-12 * scale, 100.0 * std::numeric_limits<double>::epsilon() * scale);
47+
if (std::abs(denom) < eps) {
48+
// Pathological case (e.g., eta ≈ -water_depth): avoid division-by-zero blow-up.
49+
return position;
50+
}
51+
double z_stretched = water_depth * (z_pos - eta) / denom;
52+
return Eigen::Vector3d(position.x(), position.y(), z_stretched + mwl);
53+
}
54+
2155
double GetEta(const Eigen::Vector3d& position,
2256
double time,
2357
double omega,
@@ -76,16 +110,31 @@ Eigen::Vector3d GetWaterVelocity(const Eigen::Vector3d& position,
76110
double z_pos = position.z() - mwl;
77111

78112
Eigen::Vector3d water_velocity(0.0, 0.0, 0.0);
79-
if (2 * M_PI / wavenumber > water_depth || wavenumber * water_depth > 500.0) {
113+
const double k_mag = std::abs(wavenumber);
114+
if (!std::isfinite(k_mag) || k_mag == 0.0 || !std::isfinite(omega) || omega == 0.0 || !std::isfinite(amplitude) || amplitude == 0.0) {
115+
return water_velocity;
116+
}
117+
118+
const double phase_arg = wavenumber * x_pos - omega * time + phase;
119+
120+
if (is_in_deep_water(wavenumber, water_depth)) {
80121
water_velocity[0] =
81-
omega * amplitude * std::exp(wavenumber * z_pos) * cos(wavenumber * x_pos - omega * time + phase);
122+
omega * amplitude * std::exp(k_mag * z_pos) * cos(phase_arg);
82123
water_velocity[2] =
83-
omega * amplitude * std::exp(wavenumber * z_pos) * sin(wavenumber * x_pos - omega * time + phase);
124+
omega * amplitude * std::exp(k_mag * z_pos) * sin(phase_arg);
84125
} else {
85-
water_velocity[0] = omega * amplitude * std::cosh(wavenumber * (z_pos + water_depth)) /
86-
std::sinh(wavenumber * water_depth) * cos(wavenumber * x_pos - omega * time + phase);
87-
water_velocity[2] = omega * amplitude * std::sinh(wavenumber * (z_pos + water_depth)) /
88-
std::sinh(wavenumber * water_depth) * sin(wavenumber * x_pos - omega * time + phase);
126+
const double kh = k_mag * water_depth;
127+
if (std::abs(kh) < 1e-8) {
128+
// Small-kh safe form (avoids sinh(kh)→0 leading to inf*0 -> NaN).
129+
// Uses the limiting behavior: cosh(k(z+h))/sinh(kh) ~ 1/(kh) and sinh(k(z+h))/sinh(kh) ~ (z+h)/h.
130+
const double omega_over_k = omega / k_mag;
131+
water_velocity[0] = omega_over_k * amplitude / water_depth * cos(phase_arg);
132+
water_velocity[2] = omega * amplitude * ((z_pos + water_depth) / water_depth) * sin(phase_arg);
133+
} else {
134+
const double denom = std::sinh(kh);
135+
water_velocity[0] = omega * amplitude * std::cosh(k_mag * (z_pos + water_depth)) / denom * cos(phase_arg);
136+
water_velocity[2] = omega * amplitude * std::sinh(k_mag * (z_pos + water_depth)) / denom * sin(phase_arg);
137+
}
89138
}
90139
return water_velocity;
91140
}
@@ -102,16 +151,30 @@ Eigen::Vector3d GetWaterAcceleration(const Eigen::Vector3d& position,
102151
double z_pos = position.z() - mwl;
103152

104153
Eigen::Vector3d water_acceleration(0.0, 0.0, 0.0);
105-
if (2 * M_PI / wavenumber > water_depth || wavenumber * water_depth > 500.0) {
154+
const double k_mag = std::abs(wavenumber);
155+
if (!std::isfinite(k_mag) || k_mag == 0.0 || !std::isfinite(omega) || omega == 0.0 || !std::isfinite(amplitude) || amplitude == 0.0) {
156+
return water_acceleration;
157+
}
158+
159+
const double phase_arg = wavenumber * x_pos - omega * time + phase;
160+
161+
if (is_in_deep_water(wavenumber, water_depth)) {
106162
water_acceleration[0] =
107-
omega * omega * amplitude * std::exp(wavenumber * z_pos) * sin(wavenumber * x_pos - omega * time + phase);
163+
omega * omega * amplitude * std::exp(k_mag * z_pos) * sin(phase_arg);
108164
water_acceleration[2] =
109-
-omega * omega * amplitude * std::exp(wavenumber * z_pos) * cos(wavenumber * x_pos - omega * time + phase);
165+
-omega * omega * amplitude * std::exp(k_mag * z_pos) * cos(phase_arg);
110166
} else {
111-
water_acceleration[0] = omega * omega * amplitude * std::cosh(wavenumber * (z_pos + water_depth)) /
112-
std::sinh(wavenumber * water_depth) * sin(wavenumber * x_pos - omega * time + phase);
113-
water_acceleration[2] = -omega * omega * amplitude * std::sinh(wavenumber * (z_pos + water_depth)) /
114-
std::sinh(wavenumber * water_depth) * cos(wavenumber * x_pos - omega * time + phase);
167+
const double kh = k_mag * water_depth;
168+
if (std::abs(kh) < 1e-8) {
169+
// Small-kh safe form (avoids sinh(kh)→0 leading to inf*0 -> NaN).
170+
const double omega_over_k = omega / k_mag;
171+
water_acceleration[0] = omega * omega_over_k * amplitude / water_depth * sin(phase_arg);
172+
water_acceleration[2] = -omega * omega * amplitude * ((z_pos + water_depth) / water_depth) * cos(phase_arg);
173+
} else {
174+
const double denom = std::sinh(kh);
175+
water_acceleration[0] = omega * omega * amplitude * std::cosh(k_mag * (z_pos + water_depth)) / denom * sin(phase_arg);
176+
water_acceleration[2] = -omega * omega * amplitude * std::sinh(k_mag * (z_pos + water_depth)) / denom * cos(phase_arg);
177+
}
115178
}
116179
return water_acceleration;
117180
}

src/hydro/waves/wave_utilities.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@
66
#ifndef HYDRO_WAVES_WAVE_UTILITIES_H
77
#define HYDRO_WAVES_WAVE_UTILITIES_H
88

9+
#include <cstddef>
910
#include <Eigen/Dense>
1011
#include <array>
1112
#include <string>
1213
#include <vector>
1314

15+
bool is_in_deep_water(double wavenumber, double water_depth);
16+
17+
Eigen::Vector3d GetWheelerStretchedPosition(const Eigen::Vector3d& position,
18+
double eta,
19+
double water_depth,
20+
double mwl);
21+
1422
double GetFreeSurfaceElevation(const Eigen::VectorXd& freqs_hz,
1523
const Eigen::VectorXd& spectral_densities,
1624
const Eigen::VectorXd& spectral_widths,

0 commit comments

Comments
 (0)