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
5992bool 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 ();
0 commit comments