Skip to content

Commit 130e58c

Browse files
committed
Decouple irregular wave model from simulation parameters; add frequency-domain excitation transfer function
Remove simulation_dt_ and simulation_duration_ from IrregularWaveParams, eliminating the dependency that prevented adaptive time integration. The excitation convolution is replaced by a pre-computed frequency-domain transfer function that evaluates in O(N_freq) per DOF per timestep instead of O(N_irf × N_freq). The eta-file-import path retains direct interpolation-based convolution. nfrequencies_ is now a required parameter (no fallback to simulation_duration_). A new ComputeElevationTimeSeries() utility replaces the old pre-computed arrays. Theory documentation updated with the derivation.
1 parent 525f8a9 commit 130e58c

10 files changed

Lines changed: 229 additions & 168 deletions

File tree

demos/sphere/demo_sphere_irreg_waves.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ int main(int argc, char* argv[]) {
103103
bodies.push_back(sphereBody);
104104

105105
IrregularWaveParams wave_inputs;
106-
wave_inputs.simulation_dt_ = timestep;
107-
wave_inputs.simulation_duration_ = simulationDuration;
108106
wave_inputs.ramp_duration_ = 60.0;
109107
wave_inputs.wave_height_ = 2.0;
110108
wave_inputs.wave_period_ = 12.0;

demos/sphere/demo_sphere_irreg_waves_eta_import.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,6 @@ int main(int argc, char* argv[]) {
106106
std::cout << "Defining irregular wave input parameters..." << std::endl;
107107
IrregularWaveParams params;
108108
std::cout << "bodies.size() = " << bodies.size() << std::endl;
109-
params.simulation_dt_ = timestep;
110-
params.simulation_duration_ = simulationDuration;
111109
params.ramp_duration_ = 0.0;
112110
params.eta_file_path_ = (DATADIR / "demos" / "sphere" / "eta" / "eta.txt").lexically_normal().generic_string();
113111
params.frequency_min_ = 0.001;

docs/_main_pages/theory.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,50 @@ In HydroChrono, the force is computed through trapezoidal integration at the tim
102102

103103
### Wave excitation force, \\( F_{exc}(t) \\)
104104

105-
The method to compute the wave excitation force involves convolution between the excitation Impulse Response Function (IRF) \\( K_{exc}(t) \\) and the wave elevation time sequence \\( \eta(t) \\):
105+
The wave excitation force is defined by the convolution of the excitation Impulse Response Function (IRF) \\( K_{exc}(\tau) \\) with the free-surface elevation \\( \eta(t) \\):
106106

107107
$$
108-
F_{exc}(t) = \int_{-\infty}^{+\infty} K_{exc}(\tau) \eta(x, y, t-\tau) d\tau
108+
F_{exc}(t) = \int_{-\infty}^{+\infty} K_{exc}(\tau) \, \eta(x, y, t-\tau) \, d\tau
109109
$$
110110

111111
By amalgamating these forces into the equation of motion, one can effectively model the behavior of a multibody oceanic system influenced by hydrodynamic forces.
112112

113-
In HydroChrono, the force is computed through trapezoidal integration by discretizing at the time values given by the excitation IRF time array relative to the current simulation time step. Linear interpolation is done for the free surface elevation if a given time value is between two values of the time series of the precomputed free surface elevation.
113+
#### Numerical evaluation
114+
115+
The continuous convolution integral is discretized using the quadrature points \\( \tau_j \\) and widths \\( \Delta\tau_j \\) provided by the excitation IRF time array from the BEM data:
116+
117+
$$
118+
F_{exc,d}(t) = \sum_j K_{exc,d}(\tau_j) \, \eta(t - \tau_j) \, \Delta\tau_j
119+
$$
120+
121+
where \\( d \\) denotes a particular degree of freedom.
122+
123+
HydroChrono supports two evaluation strategies, depending on how the sea state is defined.
124+
125+
**Frequency-domain transfer function (spectrum-based waves).** When the irregular sea state is represented as a linear superposition of \\( N_f \\) wave components,
126+
127+
$$
128+
\eta(t) = \sum_{i=1}^{N_f} A_i \cos(\varphi_i - \omega_i \, t)
129+
$$
130+
131+
the double summation over IRF points and frequency components can be factored by swapping the summation order and applying the angle-sum identity. This yields:
132+
133+
$$
134+
F_{exc,d}(t) = \sum_{i=1}^{N_f} A_i \left[ C_{d,i} \cos(\varphi_i - \omega_i \, t) \;-\; S_{d,i} \sin(\varphi_i - \omega_i \, t) \right]
135+
$$
136+
137+
where the *excitation transfer function* coefficients are pre-computed once from the IRF and the spectrum:
138+
139+
$$
140+
C_{d,i} = \sum_j K_{exc,d}(\tau_j) \, \Delta\tau_j \, \cos(\omega_i \, \tau_j), \qquad
141+
S_{d,i} = \sum_j K_{exc,d}(\tau_j) \, \Delta\tau_j \, \sin(\omega_i \, \tau_j)
142+
$$
143+
144+
This reduces the per-timestep cost from \\( \mathcal{O}(N_{irf} \times N_f) \\) to \\( \mathcal{O}(N_f) \\) — the same cost as a single free-surface elevation evaluation — while producing results identical to the time-domain convolution to machine precision. Crucially, because the wave elevation is evaluated analytically, this formulation has no dependency on the simulation time step or duration, enabling the use of adaptive time integrators.
145+
146+
During the ramp-up period (when a cosine or linear ramp modifies \\( \eta \\)), the frequency-domain factoring does not apply. HydroChrono falls back to a batched matrix evaluation that computes \\( \eta \\) at all IRF quadrature points simultaneously via pre-computed trigonometric matrices, keeping the ramp startup efficient.
147+
148+
**Interpolation-based evaluation (imported elevation time series).** When the free-surface elevation is provided as an external time series (e.g., from a wave tank measurement or a coupled model), the convolution is evaluated directly by linearly interpolating the stored \\( \eta \\) data at each quadrature point \\( t - \tau_j \\). This path retains the traditional \\( \mathcal{O}(N_{irf}) \\) per-timestep cost.
114149

115150
## Links to related open-source software:
116151

include/hydroc/waves/irregular_wave.h

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,17 @@
1313
#include <vector>
1414

1515
struct IrregularWaveParams {
16-
double simulation_dt_ = 0.0;
17-
double simulation_duration_ = 0.0;
18-
double ramp_duration_ = 0.0;
19-
std::string eta_file_path_;
2016
double wave_height_ = 0.0;
2117
double wave_period_ = 0.0;
2218
double frequency_min_ = 0.001;
2319
double frequency_max_ = 1.0;
24-
double nfrequencies_ = 0;
20+
int nfrequencies_ = 0;
2521
double peak_enhancement_factor_ = 1.0;
26-
bool is_normalized_ = false;
27-
int seed_ = 1;
28-
bool wave_stretching_ = true;
22+
bool is_normalized_ = false;
23+
int seed_ = 1;
24+
bool wave_stretching_ = true;
25+
double ramp_duration_ = 0.0;
26+
std::string eta_file_path_;
2927
};
3028

3129
class IrregularWaves : public WaveBase {
@@ -39,13 +37,18 @@ class IrregularWaves : public WaveBase {
3937
std::vector<double> GetFreeSurfaceTime() const;
4038
std::vector<double> GetFrequenciesHz() const;
4139

40+
std::pair<std::vector<double>, std::vector<double>>
41+
ComputeElevationTimeSeries(double t_start, double t_end, double dt) const;
42+
4243
Eigen::VectorXd GetForceAtTime(double t) override;
4344
WaveMode GetWaveMode() override { return mode_; }
4445

4546
Eigen::VectorXd SetSpectrumFrequencies(double start, double end, int num_steps);
4647

4748
void AddH5Data(std::vector<HydroData::IrregularWaveInfo>& irreg_h5_data, HydroData::SimulationParameters& sim_data);
4849

50+
double GetIRFTimeMax() const { return irf_time_max_; }
51+
4952
double GetElevation(const Eigen::Vector3d& position, double time) override;
5053
Eigen::Vector3d GetVelocity(const Eigen::Vector3d& position, double time, double elevation) override;
5154
Eigen::Vector3d GetAcceleration(const Eigen::Vector3d& position, double time, double elevation) override;
@@ -62,10 +65,8 @@ class IrregularWaves : public WaveBase {
6265
private:
6366
IrregularWaveParams params_;
6467
std::vector<double> spectrum_;
65-
std::vector<double> time_data_;
66-
std::vector<double> free_surface_elevation_sampled_;
67-
std::vector<double> free_surface_time_sampled_;
6868
bool spectrumCreated_ = false;
69+
bool use_eta_from_file_ = false;
6970

7071
const WaveMode mode_ = WaveMode::irregular;
7172
std::vector<HydroData::IrregularWaveInfo> wave_info_;
@@ -78,31 +79,37 @@ class IrregularWaves : public WaveBase {
7879
Eigen::VectorXd wavenumbers_;
7980
Eigen::VectorXd wave_phases_;
8081

81-
// -------------------------------------------------------------------------
82-
// Pre-computed arrays for fast elevation calculation.
83-
// These are computed once from the spectrum and reused for every GetElevation() call.
84-
// This optimization is critical for real-time visualization where GetElevation()
85-
// may be called thousands of times per frame (once per water surface grid point).
86-
// -------------------------------------------------------------------------
87-
88-
/// Wave amplitude for each frequency component [m].
89-
/// Computed as: A_i = sqrt(2 * S(f_i) * df_i), where S is the spectral density.
82+
// Stored eta data — only populated when importing from file.
83+
std::vector<double> time_data_;
84+
std::vector<double> free_surface_elevation_sampled_;
85+
std::vector<double> free_surface_time_sampled_;
86+
87+
// Pre-computed arrays for fast elevation calculation (SIMD-optimized).
88+
// Computed once from the spectrum and reused for every GetElevation() call.
9089
Eigen::VectorXd amplitudes_;
91-
92-
/// Angular frequency for each component [rad/s].
93-
/// Computed as: omega_i = 2 * pi * f_i
9490
Eigen::VectorXd angular_freqs_;
9591

92+
// Pre-computed excitation transfer function coefficients.
93+
// Converts the O(N_irf * N_freq) time-domain convolution into an
94+
// O(N_freq) frequency-domain evaluation per DOF per timestep:
95+
// F_ex(dof, t) = sum_i A_i * [C(dof,i)*cos(theta_i) - S(dof,i)*sin(theta_i)]
96+
// where theta_i = phi_i - omega_i * t.
97+
std::vector<Eigen::MatrixXd> ex_transfer_cos_; // [body](6, N_freq)
98+
std::vector<Eigen::MatrixXd> ex_transfer_sin_; // [body](6, N_freq)
99+
100+
// Pre-computed trig matrices for batch eta evaluation during ramp period.
101+
// irf_cos_wt_[b](j, i) = cos(omega_i * tau_j), similarly for sin.
102+
// Allows computing eta at all IRF points via matrix-vector product.
103+
std::vector<Eigen::MatrixXd> irf_cos_wt_; // [body](N_irf, N_freq)
104+
std::vector<Eigen::MatrixXd> irf_sin_wt_; // [body](N_irf, N_freq)
105+
double irf_time_max_ = 0.0;
106+
96107
void InitializeIRFVectors();
97-
98-
/// Pre-compute amplitudes_ and angular_freqs_ arrays from the spectrum.
99-
/// Called automatically after CreateSpectrum(). Must be called before GetElevation().
100108
void PrecomputeAmplitudes();
109+
void PrecomputeExcitationTransfer();
101110
void ReadEtaFromFile();
102-
void CreateFreeSurfaceElevation();
103111

104112
Eigen::MatrixXd GetExcitationIRF(int b) const;
105-
void ResampleIRF(double dt);
106113
void CalculateWidthIRF();
107114
double ExcitationConvolution(int body, int dof, double time);
108115
};

src/hydro/config/setup_from_yaml.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,10 @@ std::shared_ptr<WaveBase> CreateWaveFromSettings(const WaveSettings& wave_settin
6060

6161
} else if (type == "irregular") {
6262
IrregularWaveParams params;
63-
params.simulation_dt_ = timestep;
64-
params.simulation_duration_ = sim_duration;
6563
params.ramp_duration_ = ramp_duration;
6664
params.wave_height_ = wave_settings.height;
6765
params.wave_period_ = wave_settings.period;
66+
params.nfrequencies_ = 1000;
6867
params.seed_ = (wave_settings.seed > 0 ? wave_settings.seed : 1);
6968

7069
auto irregular_wave = std::make_shared<IrregularWaves>(params);

src/hydro/runner/run_from_yaml.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,8 +748,7 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
748748
auto irreg = std::static_pointer_cast<IrregularWaves>(wave_ptr);
749749
std::vector<double> f = irreg->GetFrequenciesHz();
750750
std::vector<double> S = irreg->GetSpectrum();
751-
std::vector<double> tvec = irreg->GetFreeSurfaceTime();
752-
std::vector<double> eta = irreg->GetFreeSurfaceElevation();
751+
auto [tvec, eta] = irreg->ComputeElevationTimeSeries(0.0, duration_hint, loop_dt);
753752
exporter->WriteIrregularInputs(f, S, tvec, eta);
754753
}
755754
}

0 commit comments

Comments
 (0)