Skip to content

Commit e87d652

Browse files
committed
include SS diagnostics in .h5 outputs
1 parent 5c86bc1 commit e87d652

4 files changed

Lines changed: 175 additions & 1 deletion

File tree

src/hydro/config/setup_from_yaml.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ std::unique_ptr<HydroSystem> SetupHydroFromYAML(
190190
ss_opts.r2_num_samples = hydro_data.ss_r2_num_samples;
191191
hydro_system->SetStateSpaceOptions(ss_opts);
192192

193+
// Enable kernel fit diagnostics if requested
194+
if (hydro_data.output_kernel_fit) {
195+
hydro_system->SetOutputKernelFit(true);
196+
hydroc::cli::LogInfo(hydroc::cli::CreateAlignedLine("", "Kernel Fit Diagnostics", "Enabled"));
197+
}
198+
193199
if (hydroc::debug::IsDebugEnabled()) {
194200
hydroc::cli::LogInfo(hydroc::cli::CreateAlignedLine("", "SS Max Order", std::to_string(ss_opts.max_order)));
195201
hydroc::cli::LogInfo(hydroc::cli::CreateAlignedLine("", "SS R² Threshold", std::to_string(ss_opts.r2_threshold)));

src/hydro/config/yaml_parser.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
362362
try { data.ss_max_hankel_size = std::stoi(value); } catch (...) {}
363363
} else if (key == "r2_num_samples") {
364364
try { data.ss_r2_num_samples = std::stoi(value); } catch (...) {}
365+
} else if (key == "output_kernel_fit") {
366+
data.output_kernel_fit = ParseBool(value, false);
365367
}
366368
} else if (in_convolution && indent == 4) {
367369
// Top level keys under convolution

src/hydro/hydro_system.cpp

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ std::unique_ptr<hydrochrono::hydro::IHydroForceComponent> HydroSystem::CreateRad
495495
if (radiation_method_ == hydrochrono::hydro::RadiationMethod::kStateSpace) {
496496
// State-space approximation: O(1) per timestep
497497
return std::make_unique<hydrochrono::hydro::RadiationStateSpaceComponent>(
498-
file_info_, num_bodies_, state_space_opts_);
498+
file_info_, num_bodies_, state_space_opts_, output_kernel_fit_);
499499
}
500500

501501
// Default: RIRF convolution
@@ -761,3 +761,60 @@ void HydroSystem::SetProfilingEnabled(bool enabled) {
761761
}
762762
}
763763

764+
bool HydroSystem::HasKernelFitDiagnostics() const {
765+
// Check both the dedicated radiation_component_ and the one in hydro_forces_
766+
if (radiation_method_ != hydrochrono::hydro::RadiationMethod::kStateSpace) {
767+
return false;
768+
}
769+
770+
// Try radiation_component_ first
771+
if (radiation_component_) {
772+
auto* ss_comp = dynamic_cast<hydrochrono::hydro::RadiationStateSpaceComponent*>(
773+
radiation_component_.get());
774+
if (ss_comp && ss_comp->HasDiagnostics()) {
775+
return true;
776+
}
777+
}
778+
779+
// Check components owned by hydro_forces_
780+
if (hydro_forces_) {
781+
for (const auto& comp : hydro_forces_->GetComponents()) {
782+
if (comp->Type() == hydrochrono::hydro::HydroComponentType::Radiation) {
783+
auto* ss_comp = dynamic_cast<hydrochrono::hydro::RadiationStateSpaceComponent*>(
784+
comp.get());
785+
if (ss_comp && ss_comp->HasDiagnostics()) {
786+
return true;
787+
}
788+
}
789+
}
790+
}
791+
792+
return false;
793+
}
794+
795+
std::vector<hydrochrono::hydro::KernelFitDiagnostics> HydroSystem::GetKernelFitDiagnostics() const {
796+
// Try radiation_component_ first
797+
if (radiation_component_) {
798+
auto* ss_comp = dynamic_cast<hydrochrono::hydro::RadiationStateSpaceComponent*>(
799+
radiation_component_.get());
800+
if (ss_comp && ss_comp->HasDiagnostics()) {
801+
return ss_comp->GetDiagnostics();
802+
}
803+
}
804+
805+
// Check components owned by hydro_forces_
806+
if (hydro_forces_) {
807+
for (const auto& comp : hydro_forces_->GetComponents()) {
808+
if (comp->Type() == hydrochrono::hydro::HydroComponentType::Radiation) {
809+
auto* ss_comp = dynamic_cast<hydrochrono::hydro::RadiationStateSpaceComponent*>(
810+
comp.get());
811+
if (ss_comp && ss_comp->HasDiagnostics()) {
812+
return ss_comp->GetDiagnostics();
813+
}
814+
}
815+
}
816+
}
817+
818+
return {};
819+
}
820+

src/hydro/io/simulation_export.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <hydroc/waves/wave_base.h>
99
#include <hydroc/waves/regular_wave.h>
1010
#include <hydroc/waves/irregular_wave.h>
11+
#include <hydroc/radiation/radiation_types.h>
1112
#include <hydroc/version.h>
1213
#include <hydroc/logging.h>
1314

@@ -985,6 +986,114 @@ void SimulationExporter::SetRunMetadata(const std::string& started_at_utc,
985986
impl_->options.run_time_final = time_final_s;
986987
}
987988

989+
void SimulationExporter::WriteRadiationDiagnostics(
990+
const std::vector<hydrochrono::hydro::KernelFitDiagnostics>& diagnostics) {
991+
992+
if (diagnostics.empty()) {
993+
return;
994+
}
995+
996+
// Create diagnostics group hierarchy
997+
auto g_diag = impl_->writer.RequireGroup("/diagnostics");
998+
auto g_rad = impl_->writer.RequireGroup("/diagnostics/radiation");
999+
1000+
// Write metadata
1001+
g_rad.WriteAttribute("description", std::string("State-space radiation kernel fit diagnostics"));
1002+
g_rad.WriteAttribute("num_bodies", static_cast<double>(diagnostics.size()));
1003+
1004+
// DOF names for reference
1005+
static const std::vector<std::string> dof_names = {
1006+
"surge", "sway", "heave", "roll", "pitch", "yaw"
1007+
};
1008+
1009+
for (const auto& diag : diagnostics) {
1010+
// Create group for this body
1011+
auto g_body = g_rad.CreateGroup(diag.body_name);
1012+
1013+
// Write body metadata
1014+
g_body.WriteAttribute("body_index", static_cast<double>(diag.body_index));
1015+
g_body.WriteAttribute("num_dofs", static_cast<double>(diag.num_dofs));
1016+
g_body.WriteAttribute("min_r2", diag.min_r2);
1017+
g_body.WriteAttribute("max_r2", diag.max_r2);
1018+
g_body.WriteAttribute("mean_r2", diag.mean_r2);
1019+
g_body.WriteAttribute("total_modes", static_cast<double>(diag.total_modes));
1020+
1021+
// Write time vector
1022+
const hsize_t n_times = static_cast<hsize_t>(diag.time.size());
1023+
if (n_times > 0) {
1024+
std::vector<double> time_vec(diag.time.data(), diag.time.data() + n_times);
1025+
std::array<hsize_t, 1> dims_1d = {n_times};
1026+
g_body.WriteDataset("time", time_vec, dims_1d);
1027+
g_body.WriteAttribute("time_units", std::string("s"));
1028+
}
1029+
1030+
// Write K_actual and K_fit matrices
1031+
const hsize_t n_dofs = static_cast<hsize_t>(diag.K_actual.cols());
1032+
if (n_times > 0 && n_dofs > 0) {
1033+
// Convert Eigen matrices to row-major vectors for HDF5
1034+
// K_actual: [n_times, n_dofs]
1035+
std::vector<double> K_actual_vec(n_times * n_dofs);
1036+
std::vector<double> K_fit_vec(n_times * n_dofs);
1037+
1038+
for (hsize_t t = 0; t < n_times; ++t) {
1039+
for (hsize_t d = 0; d < n_dofs; ++d) {
1040+
K_actual_vec[t * n_dofs + d] = diag.K_actual(t, d);
1041+
K_fit_vec[t * n_dofs + d] = diag.K_fit(t, d);
1042+
}
1043+
}
1044+
1045+
std::array<hsize_t, 2> dims_2d = {n_times, n_dofs};
1046+
g_body.WriteDataset("K_actual", K_actual_vec, dims_2d);
1047+
g_body.WriteDataset("K_fit", K_fit_vec, dims_2d);
1048+
g_body.WriteAttribute("K_units", std::string("kg/s^2"));
1049+
g_body.WriteAttribute("K_description",
1050+
std::string("RIRF kernel values: K_actual=original, K_fit=state-space approximation"));
1051+
}
1052+
1053+
// Write per-DOF diagnostics
1054+
if (!diag.dof_pairs.empty()) {
1055+
const hsize_t n_pairs = static_cast<hsize_t>(diag.dof_pairs.size());
1056+
1057+
std::vector<double> r_squared(n_pairs);
1058+
std::vector<double> state_order(n_pairs);
1059+
std::vector<double> num_exp_modes(n_pairs);
1060+
std::vector<double> num_osc_modes(n_pairs);
1061+
std::vector<double> dof_i(n_pairs);
1062+
std::vector<double> dof_j(n_pairs);
1063+
1064+
for (hsize_t k = 0; k < n_pairs; ++k) {
1065+
const auto& dp = diag.dof_pairs[k];
1066+
r_squared[k] = dp.r_squared;
1067+
state_order[k] = static_cast<double>(dp.state_order);
1068+
num_exp_modes[k] = static_cast<double>(dp.num_exp_modes);
1069+
num_osc_modes[k] = static_cast<double>(dp.num_osc_modes);
1070+
dof_i[k] = static_cast<double>(dp.dof_i);
1071+
dof_j[k] = static_cast<double>(dp.dof_j);
1072+
}
1073+
1074+
std::array<hsize_t, 1> dims_pairs = {n_pairs};
1075+
g_body.WriteDataset("r_squared", r_squared, dims_pairs);
1076+
g_body.WriteDataset("state_order", state_order, dims_pairs);
1077+
g_body.WriteDataset("num_exp_modes", num_exp_modes, dims_pairs);
1078+
g_body.WriteDataset("num_osc_modes", num_osc_modes, dims_pairs);
1079+
g_body.WriteDataset("dof_i", dof_i, dims_pairs);
1080+
g_body.WriteDataset("dof_j", dof_j, dims_pairs);
1081+
1082+
// Write DOF names for reference
1083+
std::vector<std::string> dof_labels(n_pairs);
1084+
for (hsize_t k = 0; k < n_pairs; ++k) {
1085+
int local_dof_i = static_cast<int>(dof_i[k]) % 6;
1086+
int local_dof_j = static_cast<int>(dof_j[k]) % 6;
1087+
dof_labels[k] = dof_names[local_dof_i] + "_from_" + dof_names[local_dof_j];
1088+
}
1089+
g_body.WriteStringArray("dof_labels", dof_labels);
1090+
}
1091+
}
1092+
1093+
hydroc::cli::LogInfo(std::string("HDF5: wrote radiation diagnostics for ") +
1094+
std::to_string(diagnostics.size()) + " body(ies)");
1095+
}
1096+
9881097
} // namespace hydroc
9891098

9901099

0 commit comments

Comments
 (0)