Skip to content

Commit 210cb90

Browse files
committed
Merge branch 'main' into feature/chrono-fsi-interface
2 parents dd22eed + 2db832f commit 210cb90

11 files changed

Lines changed: 192 additions & 54 deletions

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ endif()
2020
# ===============================================================================
2121

2222
project(HydroChrono
23-
VERSION 0.3.1
23+
VERSION 0.3.2
2424
DESCRIPTION "Hydrodynamics for Project Chrono."
2525
LANGUAGES CXX
2626
)

include/hydroc/hydro_forces.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ class TestHydro {
218218
* @return 6N dimensional force for 6 DOF and N bodies in system (already Eigen type).
219219
*/
220220
Eigen::VectorXd ComputeForceWaves();
221+
// Expose the wave object (read-only) so callers can query inputs if needed
222+
std::shared_ptr<WaveBase> GetWave() const { return user_waves_; }
221223

222224
/**
223225
* @brief Fetches the RIRF value from the h5 file based on the provided indices.

include/hydroc/simulation_exporter.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,16 @@ class SimulationExporter {
133133
*/
134134
void Finalize();
135135

136+
/**
137+
* @brief Write irregular wave input arrays under /inputs/simulation/waves/irregular.
138+
*
139+
* All arrays are 1D and written with attributes documenting units.
140+
*/
141+
void WriteIrregularInputs(const std::vector<double>& frequencies_hz,
142+
const std::vector<double>& spectral_densities,
143+
const std::vector<double>& free_surface_time,
144+
const std::vector<double>& free_surface_eta);
145+
136146

137147

138148
// Runtime metadata (written into /meta/run at Finalize)

include/hydroc/wave_types.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,14 @@ class IrregularWaves : public WaveBase {
297297
void Initialize() override {}
298298

299299
void CreateSpectrum();
300+
// Returns spectral density values S(f) (one per frequency bin)
300301
std::vector<double> GetSpectrum();
302+
// Returns precomputed free-surface elevation eta(t) samples
301303
std::vector<double> GetFreeSurfaceElevation();
302-
std::vector<double> GetEtaTimeData();
304+
// Returns time samples corresponding to eta(t)
305+
std::vector<double> GetFreeSurfaceTime() const;
306+
// Returns the frequency bins (Hz)
307+
std::vector<double> GetFrequenciesHz() const;
303308

304309
Eigen::VectorXd GetForceAtTime(double t) override;
305310

src/hydro_forces.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ TestHydro::TestHydro(std::vector<std::shared_ptr<ChBody>> user_bodies,
236236
// Set up hydro inputs
237237
user_waves_ = waves;
238238
AddWaves(user_waves_);
239+
240+
// If irregular waves are active, publish spectrum and free-surface inputs into HDF5 if an exporter is present.
241+
// The exporter is managed by the runner; here we only expose getters via the waves object.
239242
}
240243

241244
void TestHydro::AddWaves(std::shared_ptr<WaveBase> waves) {

src/hydro_yaml_parser.cpp

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
178178
bool period_form_linspace = false;
179179
bool period_form_range = false;
180180

181+
// Wave convenience fields
182+
bool waves_amplitude_set = false;
183+
double waves_amplitude = 0.0;
184+
bool period_set_by_synonym = false; // scalar period given via t/p/tp
185+
181186
auto parse_inline_brace_kv = [](const std::string& v) -> std::vector<std::pair<std::string, std::string>> {
182187
// Expect something like: { start: 6.0, stop: 9.0, num: 4 }
183188
std::vector<std::pair<std::string, std::string>> out;
@@ -390,11 +395,18 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
390395
}
391396
} else if (in_waves) {
392397
// Parse wave properties
393-
if (!in_period_block && key == "type") {
398+
// normalize key for shorthand handling
399+
std::string key_lower = key;
400+
std::transform(key_lower.begin(), key_lower.end(), key_lower.begin(), ::tolower);
401+
402+
if (!in_period_block && key_lower == "type") {
394403
data.waves.type = value;
395-
} else if (!in_period_block && key == "height") {
404+
} else if (!in_period_block && (key_lower == "height" || key_lower == "h")) {
396405
data.waves.height = ParseDouble(value, 0.0);
397-
} else if (!in_period_block && key == "period") {
406+
} else if (!in_period_block && (key_lower == "amplitude" || key_lower == "a")) {
407+
waves_amplitude = ParseDouble(value, 0.0);
408+
waves_amplitude_set = true;
409+
} else if (!in_period_block && (key_lower == "period" || key_lower == "t" || key_lower == "tp" || key_lower == "p")) {
398410
period_block_seen = true;
399411
period_form_values = period_form_linspace = period_form_range = false;
400412
data.waves.period_values.clear();
@@ -403,6 +415,7 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
403415
if (!looks_structured) {
404416
data.waves.period = ParseDouble(value, 0.0);
405417
data.waves.period_values.push_back(data.waves.period);
418+
period_set_by_synonym = (key_lower != "period");
406419
} else {
407420
// Inline forms on same line
408421
// values inline list inside braces
@@ -509,13 +522,13 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
509522
throw std::runtime_error("waves.period: range produced no values");
510523
}
511524
data.waves.period = data.waves.period_values.front();
512-
} else if (!in_period_block && key == "direction") {
525+
} else if (!in_period_block && key_lower == "direction") {
513526
data.waves.direction = ParseDouble(value, 0.0);
514-
} else if (!in_period_block && key == "phase") {
527+
} else if (!in_period_block && key_lower == "phase") {
515528
data.waves.phase = ParseDouble(value, 0.0);
516-
} else if (!in_period_block && key == "spectrum") {
529+
} else if (!in_period_block && key_lower == "spectrum") {
517530
data.waves.spectrum = value;
518-
} else if (!in_period_block && key == "seed") {
531+
} else if (!in_period_block && key_lower == "seed") {
519532
try { data.waves.seed = std::stoi(value); } catch (...) { data.waves.seed = -1; }
520533
}
521534
}
@@ -556,6 +569,34 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
556569
}
557570
}
558571

572+
// Apply amplitude to height if provided
573+
if (waves_amplitude_set) {
574+
const double derived_height = 2.0 * waves_amplitude;
575+
if (data.waves.height > 0.0) {
576+
const double eps = 1e-9;
577+
if (std::abs(data.waves.height - derived_height) > eps) {
578+
throw std::runtime_error("waves: both height and amplitude provided but inconsistent (expected height = 2*amplitude)");
579+
}
580+
} else {
581+
data.waves.height = derived_height;
582+
}
583+
}
584+
585+
// Validation and helpful errors for regular waves
586+
{
587+
std::string type_lower = data.waves.type;
588+
std::transform(type_lower.begin(), type_lower.end(), type_lower.begin(), ::tolower);
589+
if (type_lower == "regular") {
590+
if (data.waves.height <= 0.0) {
591+
throw std::runtime_error("waves: regular requires wave height (use 'height' or 'h', or 'amplitude'/'a')");
592+
}
593+
bool has_period = (data.waves.period > 0.0) || !data.waves.period_values.empty();
594+
if (!has_period) {
595+
throw std::runtime_error("waves: regular requires wave period (use 'period' or shorthand 't', 'tp', or 'p')");
596+
}
597+
}
598+
}
599+
559600
// Validate that we found the required sections
560601
if (!in_hydrodynamics) {
561602
throw std::runtime_error("No 'hydrodynamics:' section found in hydro file: " + hydro_file_path);

src/hydrochrono_runner/run_hydrochrono_from_yaml.cpp

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "../hydro_yaml_parser.h"
77
#include <hydroc/hydro_forces.h>
88
#include <hydroc/simulation_exporter.h>
9+
#include <hydroc/wave_types.h>
910

1011
#include <chrono_parsers/yaml/ChParserMbsYAML.h>
1112
#include <chrono/physics/ChSystem.h>
@@ -24,6 +25,7 @@
2425
#include <sstream>
2526
#include <chrono>
2627
#include <iomanip>
28+
#include <cmath>
2729

2830
#ifdef _WIN32
2931
#include <windows.h>
@@ -443,8 +445,10 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
443445

444446
// Setup hydrodynamic forces
445447
hydroc::debug::LogDebug("Initializing TestHydro...");
446-
// Provide a neutral horizon (Chrono governs runtime via YAML/UI)
447-
test_hydro = SetupHydroFromYAML(hydro_data, bodies, loop_dt, /*time_end_hint*/ 0.0, 0.0);
448+
// Provide simulation horizon from YAML end_time (important for irregular waves spectrum)
449+
double sim_duration_hint = 0.0;
450+
TryFindYamlDouble(sim_file, "end_time", sim_duration_hint);
451+
test_hydro = SetupHydroFromYAML(hydro_data, bodies, loop_dt, sim_duration_hint, 0.0);
448452
hydroc::debug::LogDebug("Hydrodynamic forces initialized successfully");
449453

450454
// Inform location for diagnostics CSVs: write to hydro file directory
@@ -650,9 +654,23 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
650654
exporter = std::make_unique<hydroc::SimulationExporter>(exp_opts);
651655

652656
// Write static info and model before stepping
653-
exporter->WriteSimulationInfo(system.get(), std::string(""), std::filesystem::path(model_file).filename().generic_string(), loop_dt, /*duration_seconds*/ 0.0);
657+
double duration_hint = 0.0; TryFindYamlDouble(sim_file, "end_time", duration_hint);
658+
exporter->WriteSimulationInfo(system.get(), std::string(""), std::filesystem::path(model_file).filename().generic_string(), loop_dt, duration_hint);
654659
exporter->WriteModel(system.get());
655660
exporter->BeginResults(system.get(), /*expected_steps*/ 0);
661+
662+
// If irregular waves are configured, persist spectrum and eta(t) inputs to HDF5
663+
if (test_hydro) {
664+
auto wave_ptr = test_hydro->GetWave();
665+
if (wave_ptr && wave_ptr->GetWaveMode() == WaveMode::irregular) {
666+
auto irreg = std::static_pointer_cast<IrregularWaves>(wave_ptr);
667+
std::vector<double> f = irreg->GetFrequenciesHz();
668+
std::vector<double> S = irreg->GetSpectrum();
669+
std::vector<double> tvec = irreg->GetFreeSurfaceTime();
670+
std::vector<double> eta = irreg->GetFreeSurfaceElevation();
671+
exporter->WriteIrregularInputs(f, S, tvec, eta);
672+
}
673+
}
656674
} catch (const std::exception& e) {
657675
hydroc::cli::LogWarning(std::string("HDF5 exporter disabled: ") + e.what());
658676
exporter.reset();
@@ -691,6 +709,14 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
691709

692710
if (nogui) {
693711
const double end_time_bound = (yaml_end_time > 0.0) ? yaml_end_time : 40.0;
712+
// Estimate total steps for progress bar; ensure at least 1
713+
const double remaining_time = std::max(0.0, end_time_bound - initial_time);
714+
const size_t total_steps_est = static_cast<size_t>(std::max(1.0, std::ceil(remaining_time / std::max(1e-12, loop_dt))));
715+
size_t last_progress_step = 0;
716+
717+
// Initial progress line
718+
hydroc::cli::ShowProgress(0, total_steps_est, std::string("t=") + hydroc::FormatNumber(initial_time, 2) + " / " + hydroc::FormatNumber(end_time_bound, 2) + " s");
719+
694720
while (system->GetChTime() < end_time_bound) {
695721
double current_time = system->GetChTime();
696722
try {
@@ -703,18 +729,33 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
703729
exporter->RecordStep(system.get());
704730
if (profile_mode) { prof_export_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - t).count(); }
705731
}
732+
// Update progress periodically to reduce console churn
733+
if (step_count == 1 || step_count - static_cast<int>(last_progress_step) >= 25) {
734+
const size_t current_steps = static_cast<size_t>(std::min<double>(total_steps_est, std::ceil((system->GetChTime() - initial_time) / std::max(1e-12, loop_dt))));
735+
std::string msg = std::string("t=") + hydroc::FormatNumber(system->GetChTime(), 2) + " / " + hydroc::FormatNumber(end_time_bound, 2) + " s";
736+
hydroc::cli::ShowProgress(current_steps, total_steps_est, msg);
737+
last_progress_step = static_cast<size_t>(step_count);
738+
}
706739
previous_time = current_time;
707740
} catch (const std::exception& e) {
741+
hydroc::cli::StopProgress();
708742
hydroc::cli::LogError(std::string("🔥 Exception during DoStepDynamics at step ") + std::to_string(step_count) + ": " + e.what());
709743
hydroc::cli::LogError(std::string("Simulation time: ") + hydroc::FormatNumber(current_time, 6) + " s");
710744
hydroc::cli::LogError(std::string("Step size: ") + hydroc::FormatNumber(loop_dt, 6) + " s");
711745
break;
712746
} catch (...) {
747+
hydroc::cli::StopProgress();
713748
hydroc::cli::LogError(std::string("🔥 Unknown exception during DoStepDynamics at step ") + std::to_string(step_count));
714749
hydroc::cli::LogError(std::string("Simulation time: ") + hydroc::FormatNumber(current_time, 6) + " s");
715750
break;
716751
}
717752
}
753+
// Finalize progress line
754+
if (system->GetChTime() >= end_time_bound - 1e-9) {
755+
hydroc::cli::ShowProgress(total_steps_est, total_steps_est, "Completed");
756+
} else {
757+
hydroc::cli::StopProgress();
758+
}
718759
} else {
719760
// GUI-driven loop: respects pause via ui.simulationStarted and closes when window stops
720761
while (ui.IsRunning(loop_dt)) {

src/setup_hydro_from_yaml.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ std::shared_ptr<WaveBase> CreateWaveFromSettings(const WaveSettings& wave_settin
5656
params.ramp_duration_ = ramp_duration;
5757
params.wave_height_ = wave_settings.height;
5858
params.wave_period_ = wave_settings.period;
59-
params.seed_ = 1; // Default seed, could be made configurable
59+
// Use YAML-provided seed if available; fall back to a default deterministic seed
60+
params.seed_ = (wave_settings.seed > 0 ? wave_settings.seed : 1);
6061

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

src/simulation_exporter.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <hydroc/simulation_exporter.h>
77
#include <hydroc/h5_writer.h>
8+
#include <hydroc/wave_types.h>
89
#include <hydroc/version.h>
910
#include <hydroc/logging.h>
1011

@@ -81,6 +82,8 @@ struct SimulationExporter::Impl {
8182
H5Writer::Group g_results_model_tsdas;
8283
H5Writer::Group g_results_model_rsdas;
8384
H5Writer::Group g_results_model_joints;
85+
// irregular waves datasets (inputs)
86+
H5Writer::Group g_inputs_waves_irregular;
8487
// meta subtree
8588
H5Writer::Group g_meta;
8689

@@ -184,6 +187,7 @@ struct SimulationExporter::Impl {
184187
g_inputs_sim_time = writer.RequireGroup("/inputs/simulation/time");
185188
g_inputs_sim_env = writer.RequireGroup("/inputs/simulation/environment");
186189
g_inputs_sim_waves = writer.RequireGroup("/inputs/simulation/waves");
190+
g_inputs_waves_irregular = writer.RequireGroup("/inputs/simulation/waves/irregular");
187191
// results
188192
g_results = writer.RequireGroup("/results");
189193
g_results_model = writer.RequireGroup("/results/model");
@@ -347,6 +351,7 @@ void SimulationExporter::WriteSimulationInfo(chrono::ChSystem* system,
347351
g_waves.WriteAttribute("Hs", impl_->options.scenario_Hs);
348352
g_waves.WriteAttribute("Tp", impl_->options.scenario_Tp);
349353
if (impl_->options.scenario_seed >= 0) g_waves.WriteAttribute("seed", static_cast<double>(impl_->options.scenario_seed));
354+
// Group already reserved in ctor; datasets will be written later if available
350355
}
351356
}
352357

@@ -357,6 +362,36 @@ void SimulationExporter::WriteInitialConditions(chrono::ChSystem* ,
357362
double ) {
358363
// Deprecated in schema v0.3: handled in WriteSimulationInfo under inputs/simulation/*
359364
}
365+
void SimulationExporter::WriteIrregularInputs(const std::vector<double>& frequencies_hz,
366+
const std::vector<double>& spectral_densities,
367+
const std::vector<double>& free_surface_time,
368+
const std::vector<double>& free_surface_eta) {
369+
auto g = impl_->g_inputs_waves_irregular;
370+
// Write datasets if provided (skip empty vectors)
371+
if (!frequencies_hz.empty()) {
372+
std::array<hsize_t,1> d1 = {static_cast<hsize_t>(frequencies_hz.size())};
373+
g.WriteDataset("frequencies_hz", frequencies_hz, d1);
374+
g.WriteAttribute("frequencies_hz.units", std::string("Hz"));
375+
}
376+
if (!spectral_densities.empty()) {
377+
std::array<hsize_t,1> d1 = {static_cast<hsize_t>(spectral_densities.size())};
378+
g.WriteDataset("spectral_densities", spectral_densities, d1);
379+
g.WriteAttribute("spectral_densities.units", std::string("m^2/Hz"));
380+
g.WriteAttribute("spectral_densities.convention", std::string("JONSWAP (if gamma>1), else PM"));
381+
}
382+
if (!free_surface_time.empty()) {
383+
std::array<hsize_t,1> d1 = {static_cast<hsize_t>(free_surface_time.size())};
384+
g.WriteDataset("free_surface_time", free_surface_time, d1);
385+
g.WriteAttribute("free_surface_time.units", std::string("s"));
386+
}
387+
if (!free_surface_eta.empty()) {
388+
std::array<hsize_t,1> d1 = {static_cast<hsize_t>(free_surface_eta.size())};
389+
g.WriteDataset("free_surface_eta", free_surface_eta, d1);
390+
g.WriteAttribute("free_surface_eta.units", std::string("m"));
391+
g.WriteAttribute("free_surface_eta.location", std::string("x=0,y=0,z=0 (assumed)"));
392+
}
393+
}
394+
360395

361396
void SimulationExporter::WriteModel(chrono::ChSystem* system) {
362397
if (system == nullptr) {

0 commit comments

Comments
 (0)