From fd2e4f265ccf1bc5e0d73c0b01b5c79a74adceae Mon Sep 17 00:00:00 2001 From: ParkSuMin Date: Sat, 19 Sep 2026 20:53:16 +0300 Subject: [PATCH] Console and CSV output as pugins --- CMakeLists.txt | 22 ++- include/api.hpp | 27 +++- plugins/adams_bashforth_plugin.cpp | 10 +- plugins/console_output_plugin.cpp | 45 ++++++ plugins/csv_output_plugin.cpp | 47 ++++++ plugins/runge_kutta_plugin.cpp | 10 +- src/main.cpp | 236 +++++++++++++++++++---------- 7 files changed, 300 insertions(+), 97 deletions(-) create mode 100644 plugins/console_output_plugin.cpp create mode 100644 plugins/csv_output_plugin.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 54b4b8d..07447dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,8 @@ set_target_properties(mmps_host PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" ) -# Функция создаёт один плагин-решатель из переданного исходного файла. -function(add_mmps_solver_plugin target source) +# Функция создаёт один плагин из переданного исходного файла. +function(add_mmps_plugin target source) # MODULE означает динамически загружаемый модуль: # DLL в Windows, SO в Linux и DYLIB в macOS. add_library(${target} MODULE ${source}) @@ -52,22 +52,34 @@ function(add_mmps_solver_plugin target source) ) endfunction() -# Каждый метод собирается в отдельную динамическую библиотеку. -add_mmps_solver_plugin( +# Каждый решатель и способ вывода собирается в отдельную библиотеку. +add_mmps_plugin( runge_kutta_plugin plugins/runge_kutta_plugin.cpp ) -add_mmps_solver_plugin( +add_mmps_plugin( adams_bashforth_plugin plugins/adams_bashforth_plugin.cpp ) +add_mmps_plugin( + console_output_plugin + plugins/console_output_plugin.cpp +) + +add_mmps_plugin( + csv_output_plugin + plugins/csv_output_plugin.cpp +) + # ЧТО ПРОИСХОДИТ ПРИ СБОРКЕ # src/main.cpp -> mmps_host.exe # plugins/runge_kutta_plugin.cpp -> runge_kutta_plugin.dll # plugins/adams_bashforth_plugin.cpp -> adams_bashforth_plugin.dll +# plugins/console_output_plugin.cpp -> console_output_plugin.dll +# plugins/csv_output_plugin.cpp -> csv_output_plugin.dll # # Хост и плагины собираются независимо. Здесь намеренно нет команд: # target_link_libraries(mmps_host PRIVATE runge_kutta_plugin) diff --git a/include/api.hpp b/include/api.hpp index 9b36712..b7e4436 100644 --- a/include/api.hpp +++ b/include/api.hpp @@ -18,7 +18,19 @@ struct SolutionPoint{ double x3; }; -inline constexpr std::uint32_t PLUGIN_API_VERSION = 1; +enum PluginType{ + Solver, + Output +}; + +struct OutputParameters{ + const char* solver_name; + const char* destination_path; + const SolutionPoint* points; + size_t point_count; +}; + +inline constexpr std::uint32_t PLUGIN_API_VERSION = 2; #if defined(_WIN32) && defined(PLUGIN_BUILD) #define PLUGIN_EXPORT extern "C" __declspec(dllexport) @@ -29,17 +41,20 @@ inline constexpr std::uint32_t PLUGIN_API_VERSION = 1; #endif using plugin_api_version_fn = std::uint32_t (*)(); +using plugin_type_fn = PluginType (*)(); using plugin_text_fn = const char* (*)(); -using function_run_fn = void (*)( +using solver_run_fn = void (*)( const SolverParameters*, SolutionPoint*, - std::size_t + size_t ); +using output_write_fn = int (*)(const OutputParameters*); inline constexpr const char* SYMBOL_API_VERSION = "plugin_api_version"; +inline constexpr const char* SYMBOL_TYPE = "plugin_type"; inline constexpr const char* SYMBOL_NAME = "plugin_name"; -inline constexpr const char* SYMBOL_DESCRIPTION = - "plugin_description"; -inline constexpr const char* SYMBOL_FUNCTION = "func"; +inline constexpr const char* SYMBOL_DESCRIPTION = "plugin_description"; +inline constexpr const char* SYMBOL_TRANSFORM = "transform"; +inline constexpr const char* SYMBOL_WRITE_OUTPUT = "write_output"; diff --git a/plugins/adams_bashforth_plugin.cpp b/plugins/adams_bashforth_plugin.cpp index 5b02df7..66343eb 100644 --- a/plugins/adams_bashforth_plugin.cpp +++ b/plugins/adams_bashforth_plugin.cpp @@ -7,6 +7,10 @@ PLUGIN_EXPORT std::uint32_t plugin_api_version(){ return PLUGIN_API_VERSION; } +PLUGIN_EXPORT PluginType plugin_type(){ + return PluginType::Solver; +} + PLUGIN_EXPORT const char* plugin_name(){ return "Adams-Bashforth 2"; } @@ -15,9 +19,9 @@ PLUGIN_EXPORT const char* plugin_description(){ return "Explicit second-order Adams-Bashforth method with an RK4 start"; } -PLUGIN_EXPORT void func(const SolverParameters* parameters, +PLUGIN_EXPORT void transform(const SolverParameters* parameters, SolutionPoint* output, - std::size_t output_size){ + size_t output_size){ if (parameters == nullptr || output == nullptr || output_size == 0) return; @@ -30,7 +34,7 @@ PLUGIN_EXPORT void func(const SolverParameters* parameters, State first_state = runge_kutta_step(initial_state, *parameters); output[1] = make_step(parameters->t_begin + parameters->step, first_state); - for (std::size_t index = 1; index + 1 < output_size; ++index){ + for (size_t index = 1; index + 1 < output_size; ++index){ State current = state_from_point(output[index]); State previous = state_from_point(output[index - 1]); State current_derivative = derivative(current, *parameters); diff --git a/plugins/console_output_plugin.cpp b/plugins/console_output_plugin.cpp new file mode 100644 index 0000000..064019b --- /dev/null +++ b/plugins/console_output_plugin.cpp @@ -0,0 +1,45 @@ +#include "api.hpp" + +#include +#include +#include +#include + +PLUGIN_EXPORT std::uint32_t plugin_api_version(){ + return PLUGIN_API_VERSION; +} + +PLUGIN_EXPORT PluginType plugin_type(){ + return PluginType::Output; +} + +PLUGIN_EXPORT const char* plugin_name(){ + return "Console output"; +} + +PLUGIN_EXPORT const char* plugin_description(){ + return "Prints calculated solution points to the console"; +} + +PLUGIN_EXPORT int write_output(const OutputParameters* parameters){ + if (parameters == nullptr || parameters->solver_name == nullptr || parameters->points == nullptr) + return 1; + + std::cout << "\nSolver: " << parameters->solver_name << '\n' + << std::setw(12) << "t" + << std::setw(20) << "x1" + << std::setw(20) << "x2" + << std::setw(20) << "x3" << '\n'; + + std::cout << std::setprecision(10); + for (size_t index = 0; index < parameters->point_count; ++index){ + const SolutionPoint& point = parameters->points[index]; + std::cout << std::setw(12) << point.t + << std::setw(20) << point.x1 + << std::setw(20) << point.x2 + << std::setw(20) << point.x3 << '\n'; + } + + std::cout.flush(); + return std::cout ? 0 : 2; +} diff --git a/plugins/csv_output_plugin.cpp b/plugins/csv_output_plugin.cpp new file mode 100644 index 0000000..173d042 --- /dev/null +++ b/plugins/csv_output_plugin.cpp @@ -0,0 +1,47 @@ +#include "api.hpp" + +#include +#include +#include +#include +#include + +PLUGIN_EXPORT std::uint32_t plugin_api_version(){ + return PLUGIN_API_VERSION; +} + +PLUGIN_EXPORT PluginType plugin_type(){ + return PluginType::Output; +} + +PLUGIN_EXPORT const char* plugin_name(){ + return "CSV output"; +} + +PLUGIN_EXPORT const char* plugin_description(){ + return "Writes calculated solution points to a CSV file"; +} + +PLUGIN_EXPORT int write_output(const OutputParameters* parameters){ + if (parameters == nullptr || parameters->destination_path == nullptr || + parameters->points == nullptr) + { + return 1; + } + + std::ofstream stream(parameters->destination_path); + if (!stream) + return 2; + + stream.imbue(std::locale::classic()); + stream << std::setprecision(17); + stream << "t,x1,x2,x3\n"; + + for (size_t index = 0; index < parameters->point_count; ++index){ + const SolutionPoint& point = parameters->points[index]; + stream << point.t << ',' << point.x1 << ',' + << point.x2 << ',' << point.x3 << '\n'; + } + + return stream ? 0 : 3; +} diff --git a/plugins/runge_kutta_plugin.cpp b/plugins/runge_kutta_plugin.cpp index 4877898..703918a 100644 --- a/plugins/runge_kutta_plugin.cpp +++ b/plugins/runge_kutta_plugin.cpp @@ -7,6 +7,10 @@ PLUGIN_EXPORT std::uint32_t plugin_api_version(){ return PLUGIN_API_VERSION; } +PLUGIN_EXPORT PluginType plugin_type(){ + return PluginType::Solver; +} + PLUGIN_EXPORT const char* plugin_name(){ return "Runge-Kutta 4"; } @@ -15,16 +19,16 @@ PLUGIN_EXPORT const char* plugin_description(){ return "Explicit fourth-order Runge-Kutta method"; } -PLUGIN_EXPORT void func(const SolverParameters* parameters, +PLUGIN_EXPORT void transform(const SolverParameters* parameters, SolutionPoint* output, - std::size_t output_size){ + size_t output_size){ if (parameters == nullptr || output == nullptr || output_size == 0) return; State state{1, 0, 0}; output[0] = make_step(parameters->t_begin, state); - for (std::size_t index = 1; index < output_size; ++index){ + for (size_t index = 1; index < output_size; ++index){ state = runge_kutta_step(state, *parameters); const double t = parameters->t_begin + static_cast(index) * parameters->step; diff --git a/src/main.cpp b/src/main.cpp index 1e8aed9..d11dd72 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,10 @@ #include "api.hpp" #include -#include #include #include #include -#include -#include #include -#include -#include #include #include #include @@ -33,7 +28,8 @@ public: { #if defined(_WIN32) handle_ = LoadLibraryW(path.c_str()); - if (handle_ == nullptr){ + if (handle_ == nullptr) + { throw std::runtime_error( "LoadLibraryW failed with error " + std::to_string(GetLastError())); @@ -90,6 +86,12 @@ private: #endif }; +struct PluginInfo{ + fs::path path; + PluginType type; + std::string name; +}; + bool is_library_candidate(const fs::path& path) { std::string extension = path.extension().string(); @@ -113,7 +115,8 @@ std::vector find_candidates(const fs::path& directory){ if (!fs::exists(directory) || !fs::is_directory(directory)) return candidates; - for (const auto& entry : fs::directory_iterator(directory)){ + for (const auto& entry : fs::directory_iterator(directory)) + { if (entry.is_regular_file() && is_library_candidate(entry.path())) candidates.push_back(fs::absolute(entry.path())); } @@ -123,12 +126,15 @@ std::vector find_candidates(const fs::path& directory){ } size_t calculate_point_count(const SolverParameters& parameters){ - const size_t max_point_count = 1000000; - double interval = parameters.t_end - parameters.t_begin; - double step_count = std::floor(interval / parameters.step + 1e-12); + constexpr size_t max_point_count = 1000000; + const double interval = parameters.t_end - parameters.t_begin; + const double step_count = std::floor(interval / parameters.step + 1e-12); - if (!std::isfinite(step_count) || step_count > static_cast(max_point_count - 1)) + if (!std::isfinite(step_count) || + step_count > static_cast(max_point_count - 1)) + { throw std::runtime_error("the selected h creates too many solution points"); + } return static_cast(step_count) + 1; } @@ -141,7 +147,9 @@ void validate_solution(const std::vector& solution, const SolutionPoint& point = solution[index]; if (!std::isfinite(point.t) || !std::isfinite(point.x1) || !std::isfinite(point.x2) || !std::isfinite(point.x3)) - throw std::runtime_error("solver returned a non-finite value"); + { + throw std::runtime_error("solver returned a non-finite value"); + } const double expected_time = parameters.t_begin + static_cast(index) * parameters.step; @@ -152,72 +160,20 @@ void validate_solution(const std::vector& solution, } } -void write_csv(const fs::path& path, const std::vector& solution){ - std::ofstream stream(path); - if (!stream) - throw std::runtime_error("cannot create CSV file: " + path_text(path)); - - stream << std::setprecision(4); - stream << "t,x1,x2,x3\n"; - - for (const SolutionPoint& point : solution){ - stream << point.t << ',' << point.x1 << ',' - << point.x2 << ',' << point.x3 << '\n'; - } -} - -void inspect_and_run(const fs::path& library_path, - const SolverParameters& parameters, - size_t point_count, - const fs::path& result_directory){ - SharedLibrary library(library_path); - - const auto api_version = library.require(SYMBOL_API_VERSION); - if (api_version() != PLUGIN_API_VERSION) - throw std::runtime_error("unsupported API version " + std::to_string(api_version())); - - const auto name = library.require(SYMBOL_NAME); - const auto description = library.require(SYMBOL_DESCRIPTION); - const auto function = library.require(SYMBOL_FUNCTION); - - const char* plugin_name = name(); - const char* plugin_description = description(); - if (plugin_name == nullptr || plugin_description == nullptr) - throw std::runtime_error("plugin returned null metadata"); - - std::vector solution(point_count, SolutionPoint{0, 0, 0, 0}); - - function(¶meters, solution.data(), solution.size()); - validate_solution(solution, parameters); - - fs::path csv_filename = library_path.stem(); - csv_filename += ".csv"; - const fs::path csv_path = result_directory / csv_filename; - write_csv(csv_path, solution); - - std::cout << "[accepted] " << path_text(library_path.filename()) << '\n' - << " name: " << plugin_name << '\n' - << " description: " << plugin_description << '\n' - << " points: " << solution.size() << '\n' - << " CSV: " << path_text(csv_path) << '\n'; -} - -int main() -{ - SolverParameters parameters{}; - std::string plugin_directory_text; - parameters.t_begin = 0.0; - parameters.t_end = 10.0; - +int main() { try { + std::string plugin_directory_text; std::cout << "Enter plugin directory: "; if (!std::getline(std::cin, plugin_directory_text) || plugin_directory_text.empty()) - throw std::runtime_error("failed to read plugin directory"); + throw std::runtime_error("failed to read plugin directory"); const fs::path plugin_directory = fs::path(plugin_directory_text); - std::cout << "Enter k1 [1e-5, 1e2], k2 [1e-2, 1] and h: "; + SolverParameters parameters{}; + parameters.t_begin = 0.0; + parameters.t_end = 10.0; + std::cout << "Enter k1 [1e-5, 1e2], k2 [1e-2, 1] and h: "; if (!(std::cin >> parameters.k1 >> parameters.k2 >> parameters.step)) throw std::runtime_error("failed to read k1, k2 and h"); @@ -244,28 +200,148 @@ int main() return 0; } - const fs::path result_directory = fs::current_path() / "results"; - fs::create_directories(result_directory); - - size_t accepted = 0; + std::vector plugins; for (const fs::path& candidate : candidates){ try { - inspect_and_run(candidate, parameters, point_count, - result_directory); - accepted++; + SharedLibrary library(candidate); + + const auto api_version = + library.require(SYMBOL_API_VERSION); + const auto type = + library.require(SYMBOL_TYPE); + const auto name = + library.require(SYMBOL_NAME); + const auto description = + library.require(SYMBOL_DESCRIPTION); + + if (api_version() != PLUGIN_API_VERSION){ + throw std::runtime_error( + "unsupported API version " + + std::to_string(api_version())); + } + + const PluginType plugin_type = type(); + const char* plugin_name = name(); + const char* plugin_description = description(); + + if (plugin_type != PluginType::Solver && + plugin_type != PluginType::Output) + { + throw std::runtime_error("unknown plugin type"); + } + if (plugin_name == nullptr || plugin_description == nullptr) + throw std::runtime_error("plugin returned null metadata"); + + plugins.push_back({candidate, plugin_type, plugin_name}); + + std::cout << "[accepted] " + << path_text(candidate.filename()) << '\n' + << " type: " + << (plugin_type == PluginType::Solver + ? "solver" + : "output") << '\n' + << " name: " << plugin_name << '\n' + << " description: " << plugin_description << '\n'; } catch (const std::exception& error){ std::cerr << "[rejected] " << path_text(candidate.filename()) << ": " << error.what() << '\n'; } } + size_t solver_count = 0, output_count = 0; + for (auto it = plugins.begin(); it != plugins.end(); it++) { + switch (it->type) + { + case Solver: + solver_count++; + break; + case Output: + output_count++; + break; + default: + break; + } + } - std::cout << "Accepted solver plugins: " << accepted << " of " << candidates.size() << '\n'; + std::cout << "Accepted plugins: " << plugins.size() + << " of " << candidates.size() << '\n' + << "Solver plugins: " << solver_count << '\n' + << "Output plugins: " << output_count << '\n'; - if (accepted == 0){ + if (solver_count == 0){ std::cout << "No available solver methods. Calculation is impossible.\n"; return 0; } + if (output_count == 0){ + std::cout << "No available output plugins. Results cannot be presented.\n"; + return 0; + } + + std::cout.flush(); + + const fs::path result_directory = "results"; + fs::create_directories(result_directory); + + for (const PluginInfo& solver : plugins){ + if (solver.type != PluginType::Solver) + continue; + + try { + SharedLibrary solver_library(solver.path); + const auto solve = + solver_library.require(SYMBOL_TRANSFORM); + + std::vector solution( + point_count, + SolutionPoint{0, 0, 0, 0}); + + solve(¶meters, solution.data(), solution.size()); + validate_solution(solution, parameters); + + fs::path csv_filename = solver.path.stem(); + csv_filename += ".csv"; + const fs::path destination_path = + result_directory / csv_filename; + const std::string destination = destination_path.string(); + + OutputParameters output_parameters{ + solver.name.c_str(), + destination.c_str(), + solution.data(), + solution.size() + }; + + for (const PluginInfo& output : plugins){ + if (output.type != PluginType::Output) + continue; + + try { + SharedLibrary output_library(output.path); + const auto write = + output_library.require( + SYMBOL_WRITE_OUTPUT); + + const int status = write(&output_parameters); + if (status != 0){ + std::cerr << "[output error] " << output.name + << " returned code " << status << '\n'; + continue; + } + + std::cout << "Output completed: " << solver.name + << " -> " << output.name << '\n'; + } + catch (const std::exception& error){ + std::cerr << "[output error] " << output.name + << ": " << error.what() << '\n'; + } + } + } + catch (const std::exception& error){ + std::cerr << "[solver error] " << solver.name + << ": " << error.what() << '\n'; + } + } return 0; }