Skip to content

Commit 3b4bbd5

Browse files
committed
Fix output directory creating and simulation duration
1 parent 9234362 commit 3b4bbd5

4 files changed

Lines changed: 136 additions & 37 deletions

File tree

src/hydrochrono_runner/run_hydrochrono_from_yaml.cpp

Lines changed: 131 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "../setup_hydro_from_yaml.h"
55
#include "../hydro_yaml_parser.h"
66
#include <hydroc/hydro_forces.h>
7+
#include <hydroc/simulation_exporter.h>
78

89
#include <chrono_parsers/yaml/ChParserMbsYAML.h>
910
#include <chrono/physics/ChSystem.h>
@@ -55,6 +56,38 @@ static std::string FindFirstFile(const std::filesystem::path& directory, const s
5556
return "";
5657
}
5758

59+
// -----------------------------------------------------------------------------
60+
// Best-effort YAML probe: find a scalar double value for a given key (e.g., end_time)
61+
// Used only for CLI display; does not control simulation.
62+
// -----------------------------------------------------------------------------
63+
static bool TryFindYamlDouble(const std::string& yaml_path, const std::string& key, double& out_value) {
64+
std::ifstream in(yaml_path);
65+
if (!in.is_open()) {
66+
return false;
67+
}
68+
auto ltrim = [](std::string& s) { s.erase(0, s.find_first_not_of(" \t\r\n")); };
69+
auto rtrim = [](std::string& s) { size_t p = s.find_last_not_of(" \t\r\n"); if (p == std::string::npos) s.clear(); else s.erase(p + 1); };
70+
std::string line;
71+
while (std::getline(in, line)) {
72+
ltrim(line); rtrim(line);
73+
if (line.empty() || line[0] == '#') continue;
74+
size_t pos = line.find(':');
75+
if (pos == std::string::npos) continue;
76+
std::string k = line.substr(0, pos);
77+
std::string v = line.substr(pos + 1);
78+
ltrim(k); rtrim(k); ltrim(v); rtrim(v);
79+
if (k == key) {
80+
try {
81+
out_value = std::stod(v);
82+
return true;
83+
} catch (...) {
84+
return false;
85+
}
86+
}
87+
}
88+
return false;
89+
}
90+
5891
// Helper functions for clean separation of concerns
5992
bool ResolveInputFiles(const std::filesystem::path& input_dir,
6093
const std::string& model_file_arg,
@@ -186,8 +219,11 @@ void DisplaySimulationSummary(const std::string& input_directory,
186219
const YAMLHydroData* hydro_data = nullptr) {
187220

188221
// Get simulation parameters
189-
double timestep = system->GetStep();
190-
double time_end = 40.0; // Default simulation duration
222+
double timestep = 0.0;
223+
// Prefer YAML-declared time_step for display; fallback to system value
224+
if (!TryFindYamlDouble(sim_file, "time_step", timestep) || timestep <= 0.0) {
225+
timestep = system->GetStep();
226+
}
191227
int num_bodies = system->GetBodies().size();
192228
int num_constraints = system->GetLinks().size();
193229
int num_hydro_bodies = hydro_data ? hydro_data->bodies.size() : 0;
@@ -215,7 +251,10 @@ void DisplaySimulationSummary(const std::string& input_directory,
215251
summary_content.push_back(hydroc::cli::CreateAlignedLine("🌊", "Hydro Bodies", std::to_string(num_hydro_bodies)));
216252
}
217253
summary_content.push_back(hydroc::cli::CreateAlignedLine("🔗", "Constraints", std::to_string(num_constraints)));
218-
summary_content.push_back(hydroc::cli::CreateAlignedLine("⏱️", "Duration", hydroc::FormatNumber(time_end, 1) + " s"));
254+
double simulation_duration = 0.0;
255+
if (TryFindYamlDouble(sim_file, "end_time", simulation_duration) && simulation_duration > 0.0) {
256+
summary_content.push_back(hydroc::cli::CreateAlignedLine("⏱️", "Simulation Duration", hydroc::FormatNumber(simulation_duration, 1) + " s"));
257+
}
219258
summary_content.push_back(hydroc::cli::CreateAlignedLine("⏱️", "Time Step", hydroc::FormatNumber(timestep, 3) + " s"));
220259
summary_content.push_back(hydroc::cli::CreateAlignedLine("🖥️", "GUI", nogui ? "Disabled" : "Enabled"));
221260

@@ -349,8 +388,14 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
349388
// ---------------------------------------------------------------------
350389
std::unique_ptr<TestHydro> test_hydro;
351390
YAMLHydroData hydro_data;
352-
double timestep = system->GetStep();
353-
double time_end = 40.0; // Default simulation duration
391+
// Prefer YAML-declared time_step for integration; fallback to system's step
392+
double loop_dt = system->GetStep();
393+
{
394+
double yaml_dt = 0.0;
395+
if (TryFindYamlDouble(sim_file, "time_step", yaml_dt) && yaml_dt > 0.0) {
396+
loop_dt = yaml_dt;
397+
}
398+
}
354399

355400
if (setup_config.has_hydro_file) {
356401
std::filesystem::path hydro_file = std::filesystem::path(input_directory) / setup_config.hydro_file;
@@ -372,7 +417,8 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
372417

373418
// Setup hydrodynamic forces
374419
hydroc::debug::LogDebug("Initializing TestHydro...");
375-
test_hydro = SetupHydroFromYAML(hydro_data, bodies, timestep, time_end, 0.0);
420+
// Provide a neutral horizon (Chrono governs runtime via YAML/UI)
421+
test_hydro = SetupHydroFromYAML(hydro_data, bodies, loop_dt, /*time_end_hint*/ 0.0, 0.0);
376422
hydroc::debug::LogDebug("Hydrodynamic forces initialized successfully");
377423

378424
// Display wave information to CLI with enhanced formatting
@@ -502,7 +548,7 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
502548
std::vector<std::string> system_info_lines;
503549
system_info_lines.push_back(hydroc::cli::CreateAlignedLine("🔗", "Bodies", std::to_string(system->GetBodies().size())));
504550
system_info_lines.push_back(hydroc::cli::CreateAlignedLine("⚙️", "Constraints", std::to_string(system->GetLinks().size())));
505-
system_info_lines.push_back(hydroc::cli::CreateAlignedLine("⏱️", "Time Step", hydroc::FormatNumber(timestep, 4) + " s"));
551+
system_info_lines.push_back(hydroc::cli::CreateAlignedLine("⏱️", "Time Step", hydroc::FormatNumber(loop_dt, 4) + " s"));
506552

507553
// Calculate approximate DOF (6 * num_bodies - constraint equations)
508554
int num_bodies = system->GetBodies().size();
@@ -542,13 +588,47 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
542588
hydroc::cli::ShowSectionBox("Solver Configuration", solver_info_lines);
543589
}
544590

591+
// ---------------------------------------------------------------------
592+
// 6.9. Optional HDF5 exporter: write outputs under setup-configured folder
593+
// ---------------------------------------------------------------------
594+
std::unique_ptr<hydroc::SimulationExporter> exporter;
595+
std::filesystem::path resolved_output_dir;
596+
if (setup_config.has_output_directory && !setup_config.output_directory.empty()) {
597+
try {
598+
resolved_output_dir = std::filesystem::path(input_directory) / setup_config.output_directory;
599+
std::error_code ec;
600+
std::filesystem::create_directories(resolved_output_dir, ec);
601+
602+
// Name output file based on wave type for predictability
603+
std::string wave_type = hydro_data.waves.type.empty() ? std::string("still") : hydro_data.waves.type;
604+
std::filesystem::path output_h5 = resolved_output_dir / (std::string("results.") + wave_type + ".h5");
605+
606+
hydroc::SimulationExporter::Options exp_opts;
607+
exp_opts.output_path = output_h5.generic_string();
608+
exp_opts.input_model_file = model_file;
609+
exp_opts.input_simulation_file = sim_file;
610+
if (setup_config.has_hydro_file) {
611+
exp_opts.input_hydro_file = (std::filesystem::path(input_directory) / setup_config.hydro_file).generic_string();
612+
}
613+
exp_opts.output_directory = resolved_output_dir.generic_string();
614+
exp_opts.scenario_type = wave_type;
615+
exporter = std::make_unique<hydroc::SimulationExporter>(exp_opts);
616+
617+
// Write static info and model before stepping
618+
exporter->WriteSimulationInfo(system.get(), std::string(""), std::filesystem::path(model_file).filename().generic_string(), loop_dt, /*duration_seconds*/ 0.0);
619+
exporter->WriteModel(system.get());
620+
exporter->BeginResults(system.get(), /*expected_steps*/ 0);
621+
} catch (const std::exception& e) {
622+
hydroc::cli::LogWarning(std::string("HDF5 exporter disabled: ") + e.what());
623+
exporter.reset();
624+
}
625+
}
626+
545627
// ---------------------------------------------------------------------
546628
// 7. Run simulation
547629
// ---------------------------------------------------------------------
548630
auto start_time = std::chrono::steady_clock::now();
549631

550-
// Initialize real-time timer for consistent pacing (matches demo behavior)
551-
chrono::ChRealtimeStepTimer realtime_timer;
552632

553633
// Log simulation loop entry
554634
hydroc::cli::LogInfo("🕒 Entering simulation loop...");
@@ -563,19 +643,37 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
563643
first_body = system->GetBodies()[0];
564644
}
565645

566-
// Simulation loop - follows pattern from working demos (e.g., demo_sphere_decay.cpp)
567-
// Respects GUI pause button via ui.simulationStarted flag
568-
while (system->GetChTime() < time_end) {
569-
// Update visualization first
570-
if (!nogui) {
571-
if (!ui.IsRunning(timestep)) {
572-
hydroc::cli::LogWarning("UI stopped, breaking loop");
646+
// Determine planned end time for headless runs (nogui)
647+
double yaml_end_time = 0.0;
648+
TryFindYamlDouble(sim_file, "end_time", yaml_end_time);
649+
650+
if (nogui) {
651+
const double end_time_bound = (yaml_end_time > 0.0) ? yaml_end_time : 40.0;
652+
while (system->GetChTime() < end_time_bound) {
653+
double current_time = system->GetChTime();
654+
try {
655+
system->DoStepDynamics(loop_dt);
656+
step_count++;
657+
if (exporter) {
658+
exporter->RecordStep(system.get());
659+
}
660+
previous_time = current_time;
661+
} catch (const std::exception& e) {
662+
hydroc::cli::LogError(std::string("🔥 Exception during DoStepDynamics at step ") + std::to_string(step_count) + ": " + e.what());
663+
hydroc::cli::LogError(std::string("Simulation time: ") + hydroc::FormatNumber(current_time, 6) + " s");
664+
hydroc::cli::LogError(std::string("Step size: ") + hydroc::FormatNumber(loop_dt, 6) + " s");
665+
break;
666+
} catch (...) {
667+
hydroc::cli::LogError(std::string("🔥 Unknown exception during DoStepDynamics at step ") + std::to_string(step_count));
668+
hydroc::cli::LogError(std::string("Simulation time: ") + hydroc::FormatNumber(current_time, 6) + " s");
573669
break;
574670
}
575671
}
576-
577-
// Only step simulation if not paused (matches demo pattern)
578-
if (ui.simulationStarted) {
672+
} else {
673+
// GUI-driven loop: respects pause via ui.simulationStarted and closes when window stops
674+
while (ui.IsRunning(loop_dt)) {
675+
// Only step simulation if not paused (matches demo pattern)
676+
if (ui.simulationStarted) {
579677
double current_time = system->GetChTime();
580678

581679
// 🔁 Step-level diagnostics (every step when in trace mode, conditional otherwise)
@@ -603,8 +701,11 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
603701

604702
try {
605703
// 🧯 Scoped try/catch around DoStepDynamics
606-
system->DoStepDynamics(timestep);
704+
system->DoStepDynamics(loop_dt);
607705
step_count++;
706+
if (exporter) {
707+
exporter->RecordStep(system.get());
708+
}
608709

609710
// 🧯 After first step - check if simulation time advanced
610711
if (first_step) {
@@ -719,7 +820,7 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
719820
// Enhanced exception handling with more diagnostics
720821
hydroc::cli::LogError(std::string("🔥 Exception during DoStepDynamics at step ") + std::to_string(step_count) + ": " + e.what());
721822
hydroc::cli::LogError(std::string("Simulation time: ") + hydroc::FormatNumber(current_time, 6) + " s");
722-
hydroc::cli::LogError(std::string("Step size: ") + hydroc::FormatNumber(timestep, 6) + " s");
823+
hydroc::cli::LogError(std::string("Step size: ") + hydroc::FormatNumber(loop_dt, 6) + " s");
723824

724825
if (debug_mode && first_body) {
725826
auto pos = first_body->GetPos();
@@ -740,17 +841,22 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
740841
hydroc::cli::LogError("This indicates a serious system error");
741842
break; // Exit simulation loop on exception
742843
}
743-
744-
// Enforce real-time pacing (prevents running as fast as possible)
745-
realtime_timer.Spin(timestep);
746844
}
747845
}
846+
}
748847

749848
auto end_time = std::chrono::steady_clock::now();
750849
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
751850

752851
// Final results display (CLI visible)
753-
hydroc::cli::ShowSimulationResults(system->GetChTime(), static_cast<int>(system->GetChTime() / timestep), duration.count() / 1000.0);
852+
hydroc::cli::ShowSimulationResults(system->GetChTime(), static_cast<int>(system->GetChTime() / loop_dt), duration.count() / 1000.0);
853+
854+
// Finalize HDF5 output with runtime metadata
855+
if (exporter) {
856+
double wall_s = std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time).count();
857+
exporter->SetRunMetadata(std::string(""), std::string(""), wall_s, step_count, loop_dt, system->GetChTime());
858+
exporter->Finalize();
859+
}
754860

755861
// Display warnings section if any warnings were collected
756862
hydroc::cli::DisplayWarnings();

tests/regression/run_hydrochrono/f3of/decay_dt3/inputs/f3of_decay.simulation.yaml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,12 @@ simulation:
44
contact_method: SMC
55

66
time_step: 0.02
7-
end_time: 150.0
7+
end_time: 40.0
88
enforce_realtime: false
99
gravity: [0, 0, -9.81]
1010

11-
# integrator:
12-
# type: HHT
13-
# rel_tolerance: 1e-4
14-
# abs_tolerance_states: 1e-4
15-
# abs_tolerance_multipliers: 1e2
16-
# max_iterations: 50
17-
# use_stepsize_control: false
18-
# use_modified_newton: false
11+
integrator:
12+
type: EULER_IMPLICIT_LINEARIZED
1913

2014
solver:
2115
type: SPARSE_QR

tests/regression/run_hydrochrono/f3of/decay_dt3/inputs/f3of_decay_dt3.setup.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ model_file: f3of_decay_test_3.model.yaml
22
simulation_file: f3of_decay.simulation.yaml
33
hydro_file: f3of_decay.hydro.yaml
44

5-
output_directory: ../outputs
6-
5+
output_directory: ../outputs

tests/regression/run_hydrochrono/f3of/decay_dt3/inputs/f3of_decay_test_3.model.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ model:
4040

4141
# Aft flap (body3), no initial pitch
4242
- name: body3
43-
location: [12.5, 0.0, -5.553173865]
43+
location: [12.5, 0.0, -5.5]
4444
orientation: [0.0, 0.0, 0.0]
4545
mass: 179250.0
4646
fixed: false

0 commit comments

Comments
 (0)