Skip to content

Commit 8c1a04b

Browse files
committed
Integrate MoorDyn mooring forces into HydroSystem
1 parent c7e3825 commit 8c1a04b

13 files changed

Lines changed: 515 additions & 4 deletions

File tree

include/hydroc/config/hydro_config.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ struct YAMLHydroData {
9393
// Diagnostics
9494
// ─────────────────────────────────────────────────────────────────────────
9595
bool output_kernel_fit = false; // write kernel fit diagnostics to HDF5
96+
97+
// ─────────────────────────────────────────────────────────────────────────
98+
// MoorDyn mooring coupling (optional)
99+
// ─────────────────────────────────────────────────────────────────────────
100+
bool moordyn_enabled = false;
101+
std::string moordyn_input_file;
102+
std::vector<std::string> moordyn_body_names; // e.g. ["body1"]
96103
};
97104

98105
#endif // HYDROC_CONFIG_HYDRO_CONFIG_H
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*********************************************************************
2+
* @file moordyn_config.h
3+
* @brief Configuration struct for MoorDyn mooring coupling.
4+
*********************************************************************/
5+
6+
#ifndef HYDROC_CONFIG_MOORDYN_CONFIG_H
7+
#define HYDROC_CONFIG_MOORDYN_CONFIG_H
8+
9+
#include <string>
10+
#include <vector>
11+
12+
struct MoorDynConfig {
13+
bool enabled = false;
14+
std::string input_file;
15+
/// 0-based indices into the HydroChrono body list that correspond to
16+
/// MoorDyn coupled bodies, in the order they appear in the MoorDyn
17+
/// input file's Body List.
18+
std::vector<int> coupled_body_indices;
19+
};
20+
21+
#endif // HYDROC_CONFIG_MOORDYN_CONFIG_H

include/hydroc/core/force_component.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ namespace hydrochrono::hydro {
2525
enum class HydroComponentType {
2626
Hydrostatics, ///< Hydrostatic restoring forces and buoyancy
2727
Radiation, ///< Radiation damping (RIRF convolution)
28-
Excitation ///< Wave excitation forces
28+
Excitation, ///< Wave excitation forces
29+
Mooring ///< Mooring forces (e.g. MoorDyn)
2930
};
3031

3132
/**

include/hydroc/core/hydro_forces.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ struct HydroForcesProfileStats {
2929
double hydrostatics_seconds = 0.0;
3030
double radiation_seconds = 0.0;
3131
double excitation_seconds = 0.0;
32+
double mooring_seconds = 0.0;
3233
int hydrostatics_calls = 0;
3334
int radiation_calls = 0;
3435
int excitation_calls = 0;
36+
int mooring_calls = 0;
3537

3638
/// Reset all counters to zero
3739
void Reset() {
38-
hydrostatics_seconds = radiation_seconds = excitation_seconds = 0.0;
39-
hydrostatics_calls = radiation_calls = excitation_calls = 0;
40+
hydrostatics_seconds = radiation_seconds = excitation_seconds = mooring_seconds = 0.0;
41+
hydrostatics_calls = radiation_calls = excitation_calls = mooring_calls = 0;
4042
}
4143
};
4244

include/hydroc/hydro_system.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@
6666
// Force component interface (for radiation component)
6767
#include <hydroc/core/force_component.h>
6868

69+
// MoorDyn mooring configuration
70+
#ifdef HYDROCHRONO_HAVE_MOORDYN
71+
#include <hydroc/config/moordyn_config.h>
72+
#endif
73+
6974
namespace hydrochrono::hydro {
7075
// Forward declarations
7176
class HydrostaticsComponent;
@@ -439,6 +444,18 @@ class HydroSystem {
439444
InvalidateRadiationComponent(); // Invalidate component to recreate with new settings
440445
}
441446

447+
#ifdef HYDROCHRONO_HAVE_MOORDYN
448+
/**
449+
* @brief Set MoorDyn mooring configuration.
450+
*
451+
* Must be called before the first DoStepDynamics() so that the mooring
452+
* component is included in the initial HydroForces construction.
453+
*/
454+
void SetMoorDynConfig(const MoorDynConfig& config) {
455+
moordyn_config_ = config;
456+
}
457+
#endif
458+
442459
/**
443460
* @brief Calculates or retrieves the total force on a specific body in a particular degree of freedom.
444461
*
@@ -596,6 +613,10 @@ class HydroSystem {
596613
// Internal helper: constructs hydro_forces_ and chrono_coupler_ once.
597614
// Subsequent calls are no-ops. Called automatically by CoordinateFuncForBody().
598615
void EnsureHydroForcesAndCoupler();
616+
617+
#ifdef HYDROCHRONO_HAVE_MOORDYN
618+
MoorDynConfig moordyn_config_;
619+
#endif
599620
};
600621

601622
// ─────────────────────────────────────────────────────────────────────────────

src/hydro/config/setup_from_yaml.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include "setup_from_yaml.h"
1010
#include "config_loader.h"
1111
#include <hydroc/hydro_system.h> // For HydroSystem
12+
#ifdef HYDROCHRONO_HAVE_MOORDYN
13+
#include <hydroc/config/moordyn_config.h>
14+
#endif
1215
#include <hydroc/waves/wave_base.h>
1316
#include <hydroc/waves/regular_wave.h>
1417
#include <hydroc/waves/irregular_wave.h>
@@ -243,7 +246,46 @@ std::unique_ptr<HydroSystem> SetupHydroFromYAML(
243246
hydroc::debug::LogDebug("Radiation convolution mode: Baseline");
244247
hydroc::cli::LogInfo(hydroc::cli::CreateAlignedLine("", "Convolution Mode", "Baseline"));
245248
}
246-
249+
250+
// ─────────────────────────────────────────────────────────────────────────
251+
// MoorDyn mooring coupling (optional)
252+
// ─────────────────────────────────────────────────────────────────────────
253+
#ifdef HYDROCHRONO_HAVE_MOORDYN
254+
if (hydro_data.moordyn_enabled && !hydro_data.moordyn_input_file.empty()) {
255+
MoorDynConfig md_cfg;
256+
md_cfg.enabled = true;
257+
md_cfg.input_file = hydro_data.moordyn_input_file;
258+
259+
// Resolve body names to 0-based indices in matched_bodies
260+
for (const auto& bname : hydro_data.moordyn_body_names) {
261+
bool found = false;
262+
for (size_t i = 0; i < matched_bodies.size(); ++i) {
263+
if (matched_bodies[i]->GetName() == bname) {
264+
md_cfg.coupled_body_indices.push_back(static_cast<int>(i));
265+
found = true;
266+
break;
267+
}
268+
}
269+
if (!found) {
270+
throw std::runtime_error(
271+
"MoorDyn config references body '" + bname +
272+
"' which was not found among hydrodynamic bodies");
273+
}
274+
}
275+
276+
hydro_system->SetMoorDynConfig(md_cfg);
277+
hydroc::cli::LogInfo(hydroc::cli::CreateAlignedLine(
278+
"+", "MoorDyn", hydro_data.moordyn_input_file +
279+
" (" + std::to_string(md_cfg.coupled_body_indices.size()) + " bodies)"));
280+
}
281+
#else
282+
if (hydro_data.moordyn_enabled) {
283+
hydroc::cli::LogWarning(
284+
"MoorDyn is enabled in YAML but HydroChrono was built without "
285+
"HYDROCHRONO_ENABLE_MOORDYN. Mooring forces will be ignored.");
286+
}
287+
#endif
288+
247289
return hydro_system;
248290
}
249291

src/hydro/config/yaml_parser.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
170170
bool in_conv_taper = false;
171171
bool in_conv_diag = false;
172172
bool in_body = false;
173+
bool in_moordyn = false;
173174
HydroBody current_body;
174175
int line_number = 0;
175176

@@ -294,6 +295,22 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
294295
in_bodies = false;
295296
in_waves = false;
296297
in_body = false;
298+
in_moordyn = false;
299+
in_conv_smoothing = in_conv_taper = in_conv_diag = false;
300+
continue;
301+
}
302+
303+
if (indent == 2 && trimmed == "moordyn:") {
304+
if (in_body && !current_body.name.empty()) {
305+
data.bodies.push_back(current_body);
306+
}
307+
in_moordyn = true;
308+
in_bodies = false;
309+
in_waves = false;
310+
in_radiation = false;
311+
in_radiation_state_space = false;
312+
in_convolution = false;
313+
in_body = false;
297314
in_conv_smoothing = in_conv_taper = in_conv_diag = false;
298315
continue;
299316
}
@@ -577,6 +594,35 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
577594
} else if (!in_period_block && key_lower == "seed") {
578595
try { data.waves.seed = std::stoi(value); } catch (...) { data.waves.seed = -1; }
579596
}
597+
} else if (in_moordyn) {
598+
std::string key_lower = key;
599+
std::transform(key_lower.begin(), key_lower.end(), key_lower.begin(), ::tolower);
600+
if (key_lower == "enabled") {
601+
std::string val_lower = value;
602+
std::transform(val_lower.begin(), val_lower.end(), val_lower.begin(), ::tolower);
603+
data.moordyn_enabled = (val_lower == "true" || val_lower == "1" || val_lower == "yes");
604+
} else if (key_lower == "input_file") {
605+
data.moordyn_input_file = value;
606+
} else if (key_lower == "bodies") {
607+
// Parse [body1, body2] list
608+
std::string list_str = value;
609+
// Strip brackets
610+
size_t lb = list_str.find('[');
611+
size_t rb = list_str.find(']');
612+
if (lb != std::string::npos && rb != std::string::npos && rb > lb) {
613+
list_str = list_str.substr(lb + 1, rb - lb - 1);
614+
}
615+
// Split by commas
616+
std::istringstream iss(list_str);
617+
std::string token;
618+
while (std::getline(iss, token, ',')) {
619+
token.erase(0, token.find_first_not_of(" \t"));
620+
token.erase(token.find_last_not_of(" \t") + 1);
621+
if (!token.empty()) {
622+
data.moordyn_body_names.push_back(token);
623+
}
624+
}
625+
}
580626
}
581627
}
582628
}

src/hydro/core/hydro_forces.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ BodyForces HydroForces::Evaluate(const SystemState& state, double time) {
6161
profile_stats_.excitation_seconds += elapsed;
6262
profile_stats_.excitation_calls++;
6363
break;
64+
case HydroComponentType::Mooring:
65+
profile_stats_.mooring_seconds += elapsed;
66+
profile_stats_.mooring_calls++;
67+
break;
6468
}
6569
} else {
6670
// Fast path: no timing overhead
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*********************************************************************
2+
* @file mooring_component.cpp
3+
* @brief Implementation of MooringComponent.
4+
*********************************************************************/
5+
6+
#include "mooring_component.h"
7+
#include "../mooring/moordyn_wrapper.h"
8+
9+
#include <cassert>
10+
#include <cmath>
11+
12+
namespace hydrochrono::hydro {
13+
14+
MooringComponent::MooringComponent(std::unique_ptr<MoorDynWrapper> wrapper)
15+
: wrapper_(std::move(wrapper)) {
16+
assert(wrapper_ && "MooringComponent requires a non-null MoorDynWrapper");
17+
}
18+
19+
void MooringComponent::Compute(const SystemState& state,
20+
double time,
21+
BodyForces& inout_forces) {
22+
if (!wrapper_ || !wrapper_->IsInitialized()) {
23+
return;
24+
}
25+
26+
// Tolerance for detecting "same time" (HHT Newton re-evaluations).
27+
constexpr double kTimeTol = 1.0e-12;
28+
const bool same_time = has_cached_forces_ &&
29+
std::abs(time - last_time_) < kTimeTol;
30+
31+
if (same_time) {
32+
ApplyCachedForces(inout_forces);
33+
return;
34+
}
35+
36+
double dt = 0.0;
37+
if (last_time_ < 0.0) {
38+
dt = 1.0e-6;
39+
} else {
40+
dt = time - last_time_;
41+
}
42+
last_time_ = time;
43+
44+
if (dt <= 0.0) {
45+
return;
46+
}
47+
48+
// Step MoorDyn into a temporary buffer so we can cache the pure
49+
// mooring contribution separately from the accumulated inout_forces.
50+
cached_forces_.assign(inout_forces.size(), GeneralizedForce::Zero(6));
51+
wrapper_->Step(state, time, dt, cached_forces_);
52+
has_cached_forces_ = true;
53+
54+
ApplyCachedForces(inout_forces);
55+
}
56+
57+
void MooringComponent::ApplyCachedForces(BodyForces& inout_forces) const {
58+
for (size_t i = 0; i < cached_forces_.size(); ++i) {
59+
if (cached_forces_[i].size() == 0) continue;
60+
if (inout_forces[i].size() < cached_forces_[i].size()) {
61+
inout_forces[i].setZero(cached_forces_[i].size());
62+
}
63+
inout_forces[i] += cached_forces_[i];
64+
}
65+
}
66+
67+
} // namespace hydrochrono::hydro
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*********************************************************************
2+
* @file mooring_component.h
3+
* @brief Mooring force component backed by MoorDyn.
4+
*********************************************************************/
5+
6+
#ifndef HYDRO_FORCE_COMPONENTS_MOORING_COMPONENT_H
7+
#define HYDRO_FORCE_COMPONENTS_MOORING_COMPONENT_H
8+
9+
#include <hydroc/core/force_component.h>
10+
#include <hydroc/core/system_state.h>
11+
#include <memory>
12+
#include <vector>
13+
14+
namespace hydrochrono::hydro {
15+
16+
class MoorDynWrapper;
17+
18+
/**
19+
* @brief IHydroForceComponent implementation that delegates to MoorDyn
20+
* via MoorDynWrapper for mooring line force computation.
21+
*
22+
* Caches the most recent MoorDyn forces so that repeated evaluations
23+
* at the same time (e.g. HHT Newton iterations) return consistent
24+
* values without re-stepping MoorDyn.
25+
*/
26+
class MooringComponent : public IHydroForceComponent {
27+
public:
28+
explicit MooringComponent(std::unique_ptr<MoorDynWrapper> wrapper);
29+
30+
HydroComponentType Type() const override {
31+
return HydroComponentType::Mooring;
32+
}
33+
34+
void Compute(const SystemState& state,
35+
double time,
36+
BodyForces& inout_forces) override;
37+
38+
private:
39+
void ApplyCachedForces(BodyForces& inout_forces) const;
40+
41+
std::unique_ptr<MoorDynWrapper> wrapper_;
42+
double last_time_ = -1.0;
43+
BodyForces cached_forces_;
44+
bool has_cached_forces_ = false;
45+
};
46+
47+
} // namespace hydrochrono::hydro
48+
49+
#endif // HYDRO_FORCE_COMPONENTS_MOORING_COMPONENT_H

0 commit comments

Comments
 (0)