diff --git a/Sim_Engine/Engine.vcxproj b/Sim_Engine/Engine.vcxproj index bdc17788..35777830 100644 --- a/Sim_Engine/Engine.vcxproj +++ b/Sim_Engine/Engine.vcxproj @@ -57,7 +57,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - $(SolutionDir)EngineLib;$(SolutionDir)EngineLib\include;$SolutionDir)External\GLFW;$(SolutionDir)External\GLAD;$(SolutionDir)External\GLM;$(SolutionDir)External\assimp;$(SolutionDir)External\MathLib\include;%(AdditionalIncludeDirectories) + $(SolutionDir)EngineLib;$(SolutionDir)EngineLib\include;$SolutionDir)External\GLFW;$(SolutionDir)External\GLAD;$(SolutionDir)External\GLM;$(SolutionDir)External\assimp;$(SolutionDir)External\MathLib\include;$(SolutionDir)Engine\include;$(SolutionDir)External\Eigen;%(AdditionalIncludeDirectories) stdcpp20 $(IntDir)vc$(PlatformToolsetVersion).pdb $(IntDir)%(FileName).obj @@ -87,7 +87,7 @@ echo D | xcopy /E /Y /D "$(ProjectDir)Engine\configs\*" "$(TargetDir)configs\" true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - $(SolutionDir)EngineLib;$(SolutionDir)EngineLib\include;$SolutionDir)External\GLFW;$(SolutionDir)External\GLAD;$(SolutionDir)External\GLM;$(SolutionDir)External\assimp;$(SolutionDir)External\MathLib\include;%(AdditionalIncludeDirectories) + $(SolutionDir)EngineLib;$(SolutionDir)EngineLib\include;$SolutionDir)External\GLFW;$(SolutionDir)External\GLAD;$(SolutionDir)External\GLM;$(SolutionDir)External\assimp;$(SolutionDir)External\MathLib\include;$(SolutionDir)Engine\include;$(SolutionDir)External\Eigen;%(AdditionalIncludeDirectories) stdcpp20 false $(IntDir)vc$(PlatformToolsetVersion).pdb @@ -111,6 +111,7 @@ echo D | xcopy /E /Y /D "$(ProjectDir)Engine\configs\*" "$(TargetDir)configs\" + @@ -138,6 +139,10 @@ echo D | xcopy /E /Y /D "$(ProjectDir)Engine\configs\*" "$(TargetDir)configs\" + + + + diff --git a/Sim_Engine/Engine.vcxproj.filters b/Sim_Engine/Engine.vcxproj.filters index a4e06fa2..c65e0d64 100644 --- a/Sim_Engine/Engine.vcxproj.filters +++ b/Sim_Engine/Engine.vcxproj.filters @@ -7,6 +7,9 @@ src + + src + @@ -15,6 +18,9 @@ {383daa6b-14af-40d3-b6df-31732d4d7a6a} + + {146d8cfd-be4d-41a3-bc59-546deb084dc0} + @@ -84,4 +90,12 @@ Shaders + + + include + + + include + + \ No newline at end of file diff --git a/Sim_Engine/Engine/include/BatchArgs.h b/Sim_Engine/Engine/include/BatchArgs.h new file mode 100644 index 00000000..ec49d476 --- /dev/null +++ b/Sim_Engine/Engine/include/BatchArgs.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include + +// Struct to hold command-line arguments for batch mode +struct BatchArgs { + std::string testPath; + // Base configuration for batch runs (if not provided, defaults will be used) + std::optional baseDtRaw; + std::optional baseDt; + std::optional baseInt; + // Lists for sweep parameters (if not provided, defaults will be used) + std::vector sweepDtRaw; + std::vector sweepDt; + std::vector sweepInt; + // Run name for output organisation + std::optional runName; +}; \ No newline at end of file diff --git a/Sim_Engine/Engine/include/BatchEntry.h b/Sim_Engine/Engine/include/BatchEntry.h new file mode 100644 index 00000000..4a24b60d --- /dev/null +++ b/Sim_Engine/Engine/include/BatchEntry.h @@ -0,0 +1,3 @@ +#pragma once +struct BatchArgs; +int runBatchMode(const BatchArgs& args); \ No newline at end of file diff --git a/Sim_Engine/Engine/src/BatchMain.cpp b/Sim_Engine/Engine/src/BatchMain.cpp new file mode 100644 index 00000000..4e9048f7 --- /dev/null +++ b/Sim_Engine/Engine/src/BatchMain.cpp @@ -0,0 +1,132 @@ +// BatchMain.cpp +#include "pch.h" +#include +#include "BatchEntry.h" +#include "BatchArgs.h" +#include "Platform/StudyRunner.h" +#include "Numerics/IntegrationMethods.h" +#include "Platform/Paths.h" + +#include +#include +#include +#include +#include + +// Integration method parser from string (throws if unknown) +static integration::eIntegrationMethod parseMethod(const std::string& name) { + if (name == "euler") return integration::eIntegrationMethod::Euler; + if (name == "midpoint") return integration::eIntegrationMethod::Midpoint; + if (name == "heun") return integration::eIntegrationMethod::Heun; + if (name == "ralston") return integration::eIntegrationMethod::Ralston; + if (name == "rk4") return integration::eIntegrationMethod::RK4; + if (name == "rk45") return integration::eIntegrationMethod::RK45; + throw std::runtime_error("Unknown integrator: " + name); +} + +// Helper to format a timestep (dt) as a string for use in tags and filenames +static std::string formatDtForFile(const std::string& raw) { + std::string out; + out.reserve(raw.size() + 4); + for (char c : raw) { + if (c == '.') { out += "p"; } + if (c == '/') { out += "over"; } + else { out += c; } + } + return out; +} + +// Core factory function for StudyRunner +static CorePtr makeCoreFactory() { + core::ISimulationCore* raw = CreateSimulationCore_v1(); + if (!raw) { return CorePtr(nullptr, [](core::ISimulationCore*) {}); } + return CorePtr(raw, [](core::ISimulationCore* p) { DestroySimulationCore(p); }); +} + +// Batch mode entry point +int runBatchMode(const BatchArgs& args) { + paths::init(); + fprintf(stdout, "BATCH MODE ENTERED\n"); + fflush(stdout); + // Basic validation and setup + try { + // Validate required baseline + if (!args.baseDt.has_value() || !args.baseInt.has_value()) { throw std::runtime_error("Baseline (--basedt and --baseint) required."); } + if (args.testPath.empty()) { throw std::runtime_error("No test script provided."); } + + // Load script + std::ifstream file(args.testPath); + if (!file) { throw std::runtime_error("Failed to open test file: " + args.testPath); } + std::ostringstream buffer; + buffer << file.rdbuf(); + std::string scriptText = buffer.str(); + + // Build configs + std::vector configs; + + // Baseline (always first) + { + StudyRunner::config base; + base.method = parseMethod(args.baseInt.value()); + base.dt = args.baseDt.value(); + base.len_min = 1.0; // Fixed simulation duration (consistent across runs) + base.tag = + (args.runName.has_value() ? args.runName.value() + "_" : "") + + "base_dt" + formatDtForFile(args.baseDtRaw.value()) + + "_int_" + args.baseInt.value(); + configs.push_back(base); + } + + // Sweep grid + for (size_t i = 0; i < args.sweepDt.size(); ++i) { + double dt = args.sweepDt[i]; + const std::string& dtRaw = args.sweepDtRaw[i]; + for (const auto& intName : args.sweepInt) { + StudyRunner::config c; + c.method = parseMethod(intName); + c.dt = dt; + c.len_min = 1.0; + c.tag = + (args.runName.has_value() ? args.runName.value() + "_" : "") + + "dt" + formatDtForFile(dtRaw) + + "_int_" + intName; + configs.push_back(c); + } + } + + // Worker configuration + size_t workers = 1; // stable mode (parallel logging disabled) + StudyRunner runner(makeCoreFactory, workers); + + // Execute + auto T0 = std::chrono::high_resolution_clock::now(); + auto results = runner.runStudies(configs, scriptText); + auto T1 = std::chrono::high_resolution_clock::now(); + + double total = std::chrono::duration(T1 - T0).count(); + fprintf(stderr, "TOTAL BATCH TIME = %.3f sec\n", total); + + // Sort results by tag + std::sort(results.begin(), results.end(), + [](const StudyResult& a, const StudyResult& b) { + return a.tag < b.tag; + }); + + // Print summary + for (const auto& r : results) { + std::cout + << "tag=" << r.tag + << " success=" << (r.success ? "yes" : "no") + << " int=" << r.intName + << " time=" << r.simTime + << " samples=" << r.samples + << "\n"; + } + } + // Catch any exceptions that escaped from the batch processing and log them + catch (const std::exception& e) { + std::cerr << "Batch mode exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Sim_Engine/Engine/src/Main.cpp b/Sim_Engine/Engine/src/Main.cpp index 86cd4cae..490db367 100644 --- a/Sim_Engine/Engine/src/Main.cpp +++ b/Sim_Engine/Engine/src/Main.cpp @@ -1,9 +1,166 @@ #include "pch.h" +// File: Main.cpp #include #include +#include "BatchEntry.h" +#include "BatchArgs.h" -int main() { - Application app("DSFE"); - app.run(); - return 0; +// Helper function to split a comma-separated string into a vector of strings, trimming whitespace +static std::vector splitComma(const std::string& input) { + std::vector result; + std::stringstream ss(input); + std::string item; + // Split the input string by commas and trim whitespace from each item + while (std::getline(ss, item, ',')) + if (!item.empty()) + result.push_back(item); + + return result; +} + +// Helper function to parse a timestep (dt) from a string, supporting both plain decimal and fractional formats (e.g., "1/180") +static double parseDt(const std::string& input) { + // Case: "1/180" + auto slashPos = input.find('/'); + if (slashPos != std::string::npos) { + double num = std::stod(input.substr(0, slashPos)); + double den = std::stod(input.substr(slashPos + 1)); + return num / den; + } + + // Case: plain decimal + return std::stod(input); +} + +// Main entry point for the application, handling both GUI and batch modes based on command-line arguments +int main(int argc, char** argv) { + bool batchMode = false; + + // First pass to check for batch mode flag + for (int i = 1; i < argc; ++i) { + std::string arg(argv[i]); + if (arg == "--batch" || arg == "-b") { + batchMode = true; + break; + } + } + + // Non-batch mode: simple pass to handle GUI-specific options + if (!batchMode) { + for (int i = 1; i < argc; ++i) { + std::string arg(argv[i]); + if (arg == "--about") { + std::cout << "DSFE - Dynamic Systems Framework Engine\n"; + return 0; + } + else if (arg == "--help" || arg == "-h") { + std::cout << "Batch mode usage:\n"; + std::cout << " --batch -t