Console and CSV output as pugins

This commit is contained in:
ParkSuMin committed 2026-09-19 20:53:16 +03:00
1 parent 8400abab18
commit fd2e4f265c
7 files changed
+300 -97

No files matched your search

+17 -5
View File
@@ -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)
+21 -6
View File
@@ -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";
+7 -3
View File
@@ -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);
+45
View File
@@ -0,0 +1,45 @@
#include "api.hpp"
#include <cstddef>
#include <cstdint>
#include <iomanip>
#include <iostream>
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;
}
+47
View File
@@ -0,0 +1,47 @@
#include "api.hpp"
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <iomanip>
#include <locale>
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;
}
+7 -3
View File
@@ -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<double>(index) * parameters->step;
+156 -80
View File
@@ -1,15 +1,10 @@
#include "api.hpp"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <locale>
#include <stdexcept>
#include <string>
#include <vector>
@@ -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<fs::path> 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<fs::path> 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<double>(max_point_count - 1))
if (!std::isfinite(step_count) ||
step_count > static_cast<double>(max_point_count - 1))
{
throw std::runtime_error("the selected h creates too many solution points");
}
return static_cast<size_t>(step_count) + 1;
}
@@ -141,7 +147,9 @@ void validate_solution(const std::vector<SolutionPoint>& 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<double>(index) * parameters.step;
@@ -152,72 +160,20 @@ void validate_solution(const std::vector<SolutionPoint>& solution,
}
}
void write_csv(const fs::path& path, const std::vector<SolutionPoint>& 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<plugin_api_version_fn>(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<plugin_text_fn>(SYMBOL_NAME);
const auto description = library.require<plugin_text_fn>(SYMBOL_DESCRIPTION);
const auto function = library.require<function_run_fn>(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<SolutionPoint> solution(point_count, SolutionPoint{0, 0, 0, 0});
function(&parameters, 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<PluginInfo> 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<plugin_api_version_fn>(SYMBOL_API_VERSION);
const auto type =
library.require<plugin_type_fn>(SYMBOL_TYPE);
const auto name =
library.require<plugin_text_fn>(SYMBOL_NAME);
const auto description =
library.require<plugin_text_fn>(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<solver_run_fn>(SYMBOL_TRANSFORM);
std::vector<SolutionPoint> solution(
point_count,
SolutionPoint{0, 0, 0, 0});
solve(&parameters, 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<output_write_fn>(
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;
}