From 9061f4d7f37a911a3486ff547fd8df71bdd665ab Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:22:56 +0200 Subject: [PATCH 01/21] move utils/base_dir.h to io/directories.h, move the (unused) DATA_DIR to directories.h, add a new example_results_dir function, make data_dir and base_dir configurable from cmake --- cpp/CMakeLists.txt | 24 ++++++++++++--- .../flow_simulation_ode_secirvvs.cpp | 2 +- cpp/benchmarks/flow_simulation_ode_seir.cpp | 2 +- cpp/benchmarks/graph_simulation.cpp | 2 +- cpp/benchmarks/integrator_step.cpp | 2 +- cpp/benchmarks/simulation.cpp | 2 +- cpp/examples/CMakeLists.txt | 26 +++++++---------- cpp/examples/data_dir.h.in | 25 ---------------- cpp/memilio/CMakeLists.txt | 17 +++++------ cpp/memilio/config_internal.h.in | 4 +++ .../{utils/base_dir.h => io/directories.h} | 29 +++++++++++++++---- cpp/tests/test_utils.cpp | 2 +- 12 files changed, 73 insertions(+), 64 deletions(-) delete mode 100644 cpp/examples/data_dir.h.in rename cpp/memilio/{utils/base_dir.h => io/directories.h} (53%) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 79a0485eca..891a2992ec 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -27,8 +27,18 @@ option(MEMILIO_ENABLE_WARNINGS_AS_ERRORS "Build memilio with warnings as errors. option(MEMILIO_ENABLE_IPOPT "Enable numerical optimization with Ipopt, requires a Fortran compiler." OFF) option(MEMILIO_ENABLE_PROFILING "Enable runtime performance profiling of memilio." OFF) option(MEMILIO_ENABLE_LIKWID_MARKER "Enable performance measuring with likwid markers." OFF) - -mark_as_advanced(MEMILIO_USE_BUNDLED_SPDLOG MEMILIO_SANITIZE_ADDRESS MEMILIO_SANITIZE_UNDEFINED) +option(MEMILIO_BASE_DIR "Path to the MEmilio project root, used for file I/O. Set to overwrite default." "") +option(MEMILIO_DATA_DIR "Path to the MEmilio data directory. Set to overwrite default." "") + +mark_as_advanced( + MEMILIO_USE_BUNDLED_SPDLOG + MEMILIO_SANITIZE_ADDRESS + MEMILIO_SANITIZE_UNDEFINED + MEMILIO_BUILD_SHARED_LIBS + MEMILIO_BUILD_STATIC_LIBS + MEMILIO_BASE_DIR + MEMILIO_DATA_DIR + ) # try to treat AppleClang as Clang, but warn about missing support if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") @@ -76,8 +86,14 @@ set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set(CMAKE_INSTALL_RPATH "${CMAKE_BINARY_DIR}/lib" "${CMAKE_BINARY_DIR}/bin") -# sets MEMILIO_BASE_DIR to the directory containing cpp (i.e., the root of the git repo) -cmake_path(CONVERT "${PROJECT_SOURCE_DIR}/.." TO_CMAKE_PATH_LIST MEMILIO_BASE_DIR NORMALIZE) +# if undefined, set MEMILIO_BASE_DIR to the directory containing cpp (i.e., the root of the git repo) +if(MEMILIO_BASE_DIR STREQUAL "") + cmake_path(CONVERT "${PROJECT_SOURCE_DIR}/.." TO_CMAKE_PATH_LIST MEMILIO_BASE_DIR NORMALIZE) +endif() +# if undefined, set MEMILIO_DATA_DIR to the projects data directory +if(MEMILIO_DATA_DIR STREQUAL "") + cmake_path(CONVERT "${PROJECT_SOURCE_DIR}/../data" TO_CMAKE_PATH_LIST MEMILIO_DATA_DIR NORMALIZE) +endif() # code coverage analysis # Note: this only works under linux and with make diff --git a/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp b/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp index e6bbeefd6d..b43b5e7f0e 100644 --- a/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp +++ b/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp @@ -21,7 +21,7 @@ #include "benchmarks/flow_simulation_ode_secirvvs.h" #include "memilio/compartments/flow_simulation.h" #include "memilio/compartments/simulation.h" -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" #include "ode_secirvvs/model.h" #include diff --git a/cpp/benchmarks/flow_simulation_ode_seir.cpp b/cpp/benchmarks/flow_simulation_ode_seir.cpp index d50b16f0f8..f73d4c7f70 100644 --- a/cpp/benchmarks/flow_simulation_ode_seir.cpp +++ b/cpp/benchmarks/flow_simulation_ode_seir.cpp @@ -20,7 +20,7 @@ #include "benchmarks/simulation.h" #include "memilio/compartments/flow_simulation.h" #include "memilio/compartments/simulation.h" -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" #include "ode_seir/model.h" #include diff --git a/cpp/benchmarks/graph_simulation.cpp b/cpp/benchmarks/graph_simulation.cpp index 1b19b4eb40..d18e378490 100644 --- a/cpp/benchmarks/graph_simulation.cpp +++ b/cpp/benchmarks/graph_simulation.cpp @@ -22,7 +22,7 @@ #include "memilio/compartments/simulation.h" #include "memilio/math/adapt_rk.h" #include "memilio/mobility/metapopulation_mobility_instant.h" -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" #include "ode_secirvvs/model.h" #include diff --git a/cpp/benchmarks/integrator_step.cpp b/cpp/benchmarks/integrator_step.cpp index c179476f70..5b39a89c1d 100644 --- a/cpp/benchmarks/integrator_step.cpp +++ b/cpp/benchmarks/integrator_step.cpp @@ -22,7 +22,7 @@ #include "memilio/math/adapt_rk.h" #include "memilio/math/stepper_wrapper.h" -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" template void integrator_step(::benchmark::State& state) diff --git a/cpp/benchmarks/simulation.cpp b/cpp/benchmarks/simulation.cpp index 5205e6d2d9..cba7060f4b 100644 --- a/cpp/benchmarks/simulation.cpp +++ b/cpp/benchmarks/simulation.cpp @@ -22,7 +22,7 @@ #include "memilio/math/adapt_rk.h" #include "memilio/math/stepper_wrapper.h" -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" template void simulation(::benchmark::State& state) diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index e63da90ac1..8fab08eb2e 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -1,7 +1,3 @@ -# configure directory that contains the data files used by examples -file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../data" MEMILIO_DATA_DIR) -configure_file(data_dir.h.in data_dir.h) - add_executable(euler_example euler_test.cpp) target_link_libraries(euler_example PRIVATE memilio) target_compile_options(euler_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) @@ -18,17 +14,17 @@ add_executable(adapt_rk_example adapt_rk_test.cpp) target_link_libraries(adapt_rk_example PRIVATE memilio) target_compile_options(adapt_rk_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) -add_executable(ode_seir_example ode_seir.cpp) -target_link_libraries(ode_seir_example PRIVATE memilio ode_seir) -target_compile_options(ode_seir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) - -add_executable(ode_seirdb_example ode_seirdb.cpp) -target_link_libraries(ode_seirdb_example PRIVATE memilio ode_seirdb) -target_compile_options(ode_seirdb_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) - -add_executable(ode_seir_ageres_example ode_seir_ageres.cpp) -target_link_libraries(ode_seir_ageres_example PRIVATE memilio ode_seir) -target_compile_options(ode_seir_ageres_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) +add_executable(ode_seir_example ode_seir.cpp) +target_link_libraries(ode_seir_example PRIVATE memilio ode_seir) +target_compile_options(ode_seir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) + +add_executable(ode_seirdb_example ode_seirdb.cpp) +target_link_libraries(ode_seirdb_example PRIVATE memilio ode_seirdb) +target_compile_options(ode_seirdb_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) + +add_executable(ode_seir_ageres_example ode_seir_ageres.cpp) +target_link_libraries(ode_seir_ageres_example PRIVATE memilio ode_seir) +target_compile_options(ode_seir_ageres_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) add_executable(ode_sir_example ode_sir.cpp) target_link_libraries(ode_sir_example PRIVATE memilio ode_sir) diff --git a/cpp/examples/data_dir.h.in b/cpp/examples/data_dir.h.in deleted file mode 100644 index 22c9f1159b..0000000000 --- a/cpp/examples/data_dir.h.in +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (C) 2020-2026 MEmilio -* -* Authors: Daniel Abele -* -* Contact: Martin J. Kuehn -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -#ifndef DATA_DIR_H -#define DATA_DIR_H - -const char* const DATA_DIR = "${MEMILIO_DATA_DIR}"; - -#endif //DATA_DIR_H diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index 658733a7f8..7b09a55030 100644 --- a/cpp/memilio/CMakeLists.txt +++ b/cpp/memilio/CMakeLists.txt @@ -35,15 +35,20 @@ add_library(memilio compartments/stochastic_simulation.h compartments/stochastic_model.h compartments/parameter_studies.h + io/binary_serializer.h + io/binary_serializer.cpp + io/cli.cpp + io/cli.h io/default_serialize.h io/default_serialize.cpp + io/directories.h + io/epi_data.h + io/epi_data.cpp + io/hdf5_cpp.h io/io.h io/io.cpp - io/hdf5_cpp.h io/json_serializer.h io/json_serializer.cpp - io/binary_serializer.h - io/binary_serializer.cpp io/history.h io/mobility_io.h io/mobility_io.cpp @@ -51,10 +56,6 @@ add_library(memilio io/parameters_io.cpp io/result_io.h io/result_io.cpp - io/epi_data.h - io/epi_data.cpp - io/cli.cpp - io/cli.h math/euler.cpp math/euler.h math/smoother.h @@ -120,9 +121,7 @@ add_library(memilio utils/mioomp.cpp utils/string_literal.h utils/type_list.h - utils/base_dir.h utils/back_inserter_second_element.h - ad/ad.h ) target_include_directories(memilio PUBLIC diff --git a/cpp/memilio/config_internal.h.in b/cpp/memilio/config_internal.h.in index ca394fd81a..13765e56d8 100644 --- a/cpp/memilio/config_internal.h.in +++ b/cpp/memilio/config_internal.h.in @@ -31,6 +31,10 @@ #cmakedefine MEMILIO_ENABLE_OPENMP #cmakedefine MEMILIO_ENABLE_PROFILING +namespace mio::details +{ const char* const MEMILIO_BASE_DIR = "${MEMILIO_BASE_DIR}"; +const char* const MEMILIO_DATA_DIR = "${MEMILIO_DATA_DIR}"; +} // namespace mio::details #endif diff --git a/cpp/memilio/utils/base_dir.h b/cpp/memilio/io/directories.h similarity index 53% rename from cpp/memilio/utils/base_dir.h rename to cpp/memilio/io/directories.h index 4616ac7387..6c00f1dd8a 100644 --- a/cpp/memilio/utils/base_dir.h +++ b/cpp/memilio/io/directories.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2026 MEmilio * -* Authors: Julia Bicker +* Authors: Julia Bicker, Rene Schmieding * * Contact: Martin J. Kuehn * @@ -17,10 +17,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef MIO_BASE_DIR_H -#define MIO_BASE_DIR_H +#ifndef MIO_UTILS_DIRECTORIES_H +#define MIO_UTILS_DIRECTORIES_H #include "memilio/config.h" // IWYU pragma: keep +#include "memilio/utils/stl_util.h" #include @@ -32,9 +33,27 @@ namespace mio */ const static std::string base_dir() { - return MEMILIO_BASE_DIR; + return details::MEMILIO_BASE_DIR; +} + +/** + * @brief Returns the absolute path to the project directory. + */ +[[maybe_unused]] const static std::string data_dir() +{ + return details::MEMILIO_DATA_DIR; +} + +/** + * @brief Returns the absolute path to a common ouput directory for the code examples. + */ +[[maybe_unused]] const static std::string example_results_dir(const std::string& example_name) +{ + // the last empty string is used to end the output path in a / + const static std::string dir = path_join(base_dir(), "example_results", example_name, ""); + return dir; } } // namespace mio -#endif // MIO_BASE_DIR_H +#endif // MIO_UTILS_DIRECTORIES_H diff --git a/cpp/tests/test_utils.cpp b/cpp/tests/test_utils.cpp index 06aba0c25d..c40961951f 100644 --- a/cpp/tests/test_utils.cpp +++ b/cpp/tests/test_utils.cpp @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" #include "memilio/utils/index.h" #include "memilio/utils/index_range.h" #include "memilio/utils/logging.h" From 3130ae99a0dc5d8ac09d096a21b65d4ac5531550 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:25:12 +0200 Subject: [PATCH 02/21] add create_directory_or_exit as a shortcut for opening directories in examples, minor fixes --- cpp/memilio/io/io.cpp | 13 ++++++++++++- cpp/memilio/io/io.h | 24 ++++++++++++++++-------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/cpp/memilio/io/io.cpp b/cpp/memilio/io/io.cpp index cb8acb237d..de98ac15c7 100644 --- a/cpp/memilio/io/io.cpp +++ b/cpp/memilio/io/io.cpp @@ -51,7 +51,7 @@ IOResult create_directory(std::string const& rel_path, std::string& abs_pa log_info("Directory '{:s}' was created.", dir.string()); } else { - log_info("Directory '{:s}' already exists.", dir.string(), mio::get_current_dir_name()); + log_info("Directory '{:s}' already exists.", dir.string()); } return success(created); @@ -63,6 +63,17 @@ IOResult create_directory(std::string const& rel_path) return create_directory(rel_path, abs_path); } +std::string create_directory_or_exit(std::string const& path) +{ + std::string abs_path; + auto result = create_directory(path, abs_path); + if (!result) { + log_critical("Could not create directory \"{}\": {}", path, result.error().message()); + exit(result.error().code().value()); + } + return abs_path; +} + bool file_exists(std::string const& rel_path, std::string& abs_path) { boost::filesystem::path dir(rel_path); diff --git a/cpp/memilio/io/io.h b/cpp/memilio/io/io.h index 319ce4e530..ce7f0ed9b2 100644 --- a/cpp/memilio/io/io.h +++ b/cpp/memilio/io/io.h @@ -869,19 +869,27 @@ IOResult deserialize(IOContext& io, Tag tag) std::string get_current_dir_name(); /** - * @brief Creates a directory in the file system - * @param rel_path path of directory relative to current working directory. - * @param abs_path Will contain the absolute path of the directory. + * @brief Creates a directory in the file system. + * @param[in] path Path of a directory. Can be relative to current working directory or absolute. + * @param[out] abs_path Will contain the absolute path of the directory. * @return true if the directory was created, false if it already exists, or any errors that occured. */ -IOResult create_directory(std::string const& rel_path, std::string& abs_path); +IOResult create_directory(std::string const& path, std::string& abs_path); /** - * @brief Creates a directory in the file system - * @param rel_path path of directory relative to current working directory. - * @return true if the directory was created, false if it already exists, or any errors that occured. + * @brief Creates a directory in the file system. + * @param[in] path Path of a directory. Can be relative to current working directory or absolute. + * @return True if the directory was created, false if it already exists, or any errors that occured. + */ +IOResult create_directory(std::string const& path); + +/** + * @brief Creates a directory in the file system, or exits the program with an error code. + * @param[in] path Path of a directory. Can be relative to current working directory or absolute. + * @return The absolute path to the given directory. + * Any error messages during creation will be logged at `LogLevel::Critical`. */ -IOResult create_directory(std::string const& rel_path); +std::string create_directory_or_exit(std::string const& path); /** * Check if a file exists. From ead1d749ec2a2f745bd00c3247cac7db37d5af1f Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:34:51 +0200 Subject: [PATCH 03/21] move example output using example_result_dir, use create_directory_or_exit to deal with potential I/O errors --- cpp/examples/abm_history_object.cpp | 5 ++++- cpp/examples/abm_minimal.cpp | 6 +++-- cpp/examples/abm_parameter_study.cpp | 8 ++----- cpp/examples/ode_seair_optimization.cpp | 22 +++++++++++-------- cpp/examples/ode_secir_parameter_study.cpp | 8 ++++--- .../ode_secir_parameter_study_graph.cpp | 16 ++++++-------- cpp/examples/ode_secir_read_graph.cpp | 10 ++++++--- cpp/examples/ode_secir_save_results.cpp | 5 ++++- 8 files changed, 46 insertions(+), 34 deletions(-) diff --git a/cpp/examples/abm_history_object.cpp b/cpp/examples/abm_history_object.cpp index f4240e44fa..58e523143d 100644 --- a/cpp/examples/abm_history_object.cpp +++ b/cpp/examples/abm_history_object.cpp @@ -22,8 +22,10 @@ #include "abm/simulation.h" #include "abm/model.h" #include "abm/location_type.h" +#include "memilio/io/io.h" #include "memilio/utils/abstract_parameter_distribution.h" #include "memilio/io/history.h" +#include "memilio/io/directories.h" #include "memilio/utils/parameter_distributions.h" #include @@ -43,7 +45,8 @@ void write_log_to_file(const T& history) auto loc_id = std::get<1>(logg); auto time_points = std::get<0>(logg); std::string input; - std::ofstream myfile("test_output.txt"); + std::ofstream myfile(mio::create_directory_or_exit(mio::example_results_dir("abm_history_object")) + + "test_output.txt"); myfile << "Locations as numbers:\n"; for (auto&& id : loc_id[0]) { myfile << convert_loc_id_to_string(id) << "\n"; diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 426526c120..c3b3f9bcd2 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -21,6 +21,7 @@ #include "abm/lockdown_rules.h" #include "abm/model.h" #include "abm/common_abm_loggers.h" +#include "memilio/io/directories.h" #include @@ -165,10 +166,11 @@ int main() // The first column is Time. The other columns correspond to the number of people with a certain infection state at this Time: // Time = Time in days, S = Susceptible, E = Exposed, I_NS = InfectedNoSymptoms, I_Sy = InfectedSymptoms, I_Sev = InfectedSevere, // I_Crit = InfectedCritical, R = Recovered, D = Dead - std::ofstream outfile("abm_minimal.txt"); + auto outpath = mio::create_directory_or_exit(mio::example_results_dir("abm_minimal")) + "history.txt"; + std::ofstream outfile(outpath); std::get<0>(historyTimeSeries.get_log()) .print_table(outfile, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4); - std::cout << "Results written to abm_minimal.txt" << std::endl; + std::cout << "Results written to " << outpath << std::endl; return 0; } diff --git a/cpp/examples/abm_parameter_study.cpp b/cpp/examples/abm_parameter_study.cpp index e3cf55da0a..01336fcf36 100644 --- a/cpp/examples/abm_parameter_study.cpp +++ b/cpp/examples/abm_parameter_study.cpp @@ -27,7 +27,7 @@ #include "memilio/data/analyze_result.h" #include "memilio/io/io.h" #include "memilio/io/result_io.h" -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" #include "memilio/utils/logging.h" #include "memilio/utils/miompi.h" #include "memilio/utils/random_number_generator.h" @@ -192,11 +192,7 @@ int main() mio::ParameterStudy study(std::move(model), t0, tmax, mio::abm::TimeSpan(0), num_runs); study.get_rng() = rng; // use the same RNG as the model - const std::string result_dir = mio::path_join(mio::base_dir(), "example_results"); - if (!mio::create_directory(result_dir)) { - mio::log_error("Could not create result directory \"{}\".", result_dir); - return 1; - } + const std::string result_dir = mio::create_directory_or_exit(mio::example_results_dir("abm_parameter_study")); // Run the study // The first lambda ("create_simulation" argument) sets up the simulation, the second ("process_simulation_result") diff --git a/cpp/examples/ode_seair_optimization.cpp b/cpp/examples/ode_seair_optimization.cpp index 8a9f3fe069..f73c97a3ef 100644 --- a/cpp/examples/ode_seair_optimization.cpp +++ b/cpp/examples/ode_seair_optimization.cpp @@ -23,6 +23,8 @@ #include "memilio/ad/ad.h" +#include "memilio/io/directories.h" +#include "memilio/io/io.h" #include "memilio/utils/compiler_diagnostics.h" #include "ode_seair/model.h" #include "ode_seair/infection_state.h" @@ -512,18 +514,20 @@ void Seair_NLP::finalize_solution(Ipopt::SolverReturn status, Ipopt::Index n, co } mio::oseair::Model model; + const auto result_dir = mio::create_directory_or_exit(mio::example_results_dir("ode_seair_optimization")); + //open files for parameter output - std::ofstream outFileSocialDistancing("SocialDistancing.txt"); - std::ofstream outFileQuarantined("Quarantined.txt"); - std::ofstream outFileTestingRate("TestingRate.txt"); + std::ofstream outFileSocialDistancing(result_dir + "SocialDistancing.txt"); + std::ofstream outFileQuarantined(result_dir + "Quarantined.txt"); + std::ofstream outFileTestingRate(result_dir + "TestingRate.txt"); //open files for state output - std::ofstream outFileSusceptible("Susceptible.txt"); - std::ofstream outFileExposed("Exposed.txt"); - std::ofstream outFileAsymptomatic("Asymptomatic.txt"); - std::ofstream outFileInfected("Infected.txt"); - std::ofstream outFileRecovered("Recovered.txt"); - std::ofstream outFileDead("Dead.txt"); + std::ofstream outFileSusceptible(result_dir + "Susceptible.txt"); + std::ofstream outFileExposed(result_dir + "Exposed.txt"); + std::ofstream outFileAsymptomatic(result_dir + "Asymptomatic.txt"); + std::ofstream outFileInfected(result_dir + "Infected.txt"); + std::ofstream outFileRecovered(result_dir + "Recovered.txt"); + std::ofstream outFileDead(result_dir + "Dead.txt"); set_initial_values(model); int gridindex = 0; diff --git a/cpp/examples/ode_secir_parameter_study.cpp b/cpp/examples/ode_secir_parameter_study.cpp index 374db285c4..b42031de99 100644 --- a/cpp/examples/ode_secir_parameter_study.cpp +++ b/cpp/examples/ode_secir_parameter_study.cpp @@ -18,7 +18,8 @@ * limitations under the License. */ #include "memilio/config.h" -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" +#include "memilio/io/io.h" #include "memilio/utils/miompi.h" #include "memilio/utils/stl_util.h" #include "ode_secir/model.h" @@ -36,8 +37,9 @@ */ mio::IOResult write_single_run_result(const size_t run, const mio::osecir::Simulation& sim) { - std::string abs_path = mio::path_join(mio::base_dir(), "example_results"); - BOOST_OUTCOME_TRY(auto&& created, mio::create_directory(abs_path)); + std::string abs_path; + BOOST_OUTCOME_TRY(auto&& created, + mio::create_directory(mio::example_results_dir("ode_secir_parameter_study"), abs_path)); if (run == 0) { std::cout << "Results are stored in " << abs_path << '\n'; diff --git a/cpp/examples/ode_secir_parameter_study_graph.cpp b/cpp/examples/ode_secir_parameter_study_graph.cpp index 762099bd41..3e2e4f3e2b 100644 --- a/cpp/examples/ode_secir_parameter_study_graph.cpp +++ b/cpp/examples/ode_secir_parameter_study_graph.cpp @@ -20,6 +20,7 @@ #include "memilio/compartments/parameter_studies.h" #include "memilio/config.h" +#include "memilio/io/directories.h" #include "memilio/io/epi_data.h" #include "memilio/io/result_io.h" #include "memilio/io/mobility_io.h" @@ -286,14 +287,14 @@ int main() auto params = std::vector>{}; params.reserve(results_graph.nodes().size()); std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(params), - [](auto&& node) { + [](auto&& node) { return node.property.get_simulation().get_model(); }); auto edges = std::vector>{}; edges.reserve(results_graph.edges().size()); std::transform(results_graph.edges().begin(), results_graph.edges().end(), std::back_inserter(edges), - [](auto&& edge) { + [](auto&& edge) { return edge.property.get_mobility_results(); }); @@ -314,18 +315,15 @@ int main() ensemble_edges.emplace_back(std::move(std::get<2>(run))); } // create directory for results. - boost::filesystem::path results_dir("test_results"); - bool created = boost::filesystem::create_directories(results_dir); - if (created) { - mio::log_info("Directory '{}' was created.", results_dir.string()); - } + const auto result_dir = + mio::create_directory_or_exit(mio::example_results_dir("ode_secir_parameter_study_graph")); auto county_ids = std::vector{1001, 1002, 1003}; - auto save_results_status = save_results(ensemble_results, ensemble_params, county_ids, results_dir, false); + auto save_results_status = save_results(ensemble_results, ensemble_params, county_ids, result_dir, false); auto pairs_edges = std::vector>{}; for (auto& edge : params_graph.edges()) { pairs_edges.push_back({county_ids[edge.start_node_idx], county_ids[edge.end_node_idx]}); } - auto save_edges_status = save_edges(ensemble_edges, pairs_edges, "test_results", false, true); + auto save_edges_status = save_edges(ensemble_edges, pairs_edges, result_dir, false, true); } } diff --git a/cpp/examples/ode_secir_read_graph.cpp b/cpp/examples/ode_secir_read_graph.cpp index 9a22c187d7..01320c9862 100644 --- a/cpp/examples/ode_secir_read_graph.cpp +++ b/cpp/examples/ode_secir_read_graph.cpp @@ -20,9 +20,10 @@ #include "memilio/compartments/parameter_studies.h" #include "memilio/config.h" #include "memilio/io/cli.h" +#include "memilio/io/io.h" #include "memilio/io/mobility_io.h" #include "memilio/mobility/metapopulation_mobility_instant.h" -#include "memilio/utils/base_dir.h" +#include "memilio/io/directories.h" #include "memilio/utils/stl_util.h" #include "ode_secir/model.h" @@ -35,6 +36,8 @@ int main(int argc, char** argv) { mio::set_log_level(mio::LogLevel::critical); + const std::string result_dir = mio::create_directory_or_exit(mio::example_results_dir("ode_secir_read_graph")); + auto parameters = mio::cli::ParameterSetBuilder() .add<"MobilityFile">( @@ -129,7 +132,7 @@ int main(int argc, char** argv) std::cout << "Done" << std::endl; std::cout << "Writing Json Files..." << std::flush; - auto write_status = mio::write_graph(graph, "graph_parameters"); + auto write_status = mio::write_graph(graph, result_dir + "graph_parameters"); if (!write_status) { std::cout << "\n" << write_status.error().formatted_message(); return 0; @@ -137,7 +140,8 @@ int main(int argc, char** argv) std::cout << "Done" << std::endl; std::cout << "Reading Json Files..." << std::flush; - auto graph_read_result = mio::read_graph>("graph_parameters"); + auto graph_read_result = + mio::read_graph>(result_dir + "graph_parameters"); if (!graph_read_result) { std::cout << "\n" << graph_read_result.error().formatted_message(); diff --git a/cpp/examples/ode_secir_save_results.cpp b/cpp/examples/ode_secir_save_results.cpp index 09a624c0cb..8b2604470f 100644 --- a/cpp/examples/ode_secir_save_results.cpp +++ b/cpp/examples/ode_secir_save_results.cpp @@ -17,6 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "memilio/io/directories.h" #include "ode_secir/model.h" #include "memilio/io/result_io.h" @@ -84,5 +85,7 @@ int main() std::vector> results_from_sim = {result_from_sim, result_from_sim}; std::vector ids = {1, 2}; - auto save_result_status = mio::save_result(results_from_sim, ids, (int)(size_t)nb_groups, "test_result.h5"); + const std::string result_dir = mio::create_directory_or_exit(mio::example_results_dir("ode_secir_save_results")); + auto save_result_status = + mio::save_result(results_from_sim, ids, (int)(size_t)nb_groups, result_dir + "test_result.h5"); } From 73eefeb0468150986b5969a75a834ea8908f8f6b Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:35:37 +0200 Subject: [PATCH 04/21] replace result output with print_table --- cpp/examples/ode_mseirs4.cpp | 15 ++++----------- cpp/examples/ode_secir.cpp | 28 ++++++---------------------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/cpp/examples/ode_mseirs4.cpp b/cpp/examples/ode_mseirs4.cpp index 464d8b8c7c..09f7368a4c 100644 --- a/cpp/examples/ode_mseirs4.cpp +++ b/cpp/examples/ode_mseirs4.cpp @@ -67,7 +67,7 @@ int main() // Compute S1 as residual to match N double assigned = 5000.0 + (300.0 + 150.0 + 80.0 + 70.0) + (200.0 + 100.0 + 50.0 + 50.0) + (40000.0 + 30000.0 + 20000.0 + 10000.0) + (100000.0 + 50000.0 + 50000.0); - double S1 = N - assigned; + double S1 = N - assigned; if (S1 < 0) S1 = 0; model.populations[{mio::Index(mio::omseirs4::InfectionState::S1)}] = S1; @@ -80,15 +80,8 @@ int main() double dt = 1.0; // daily output auto result = mio::simulate(t0, tmax, dt, model); - // print header - std::cout << "t M S1 S2 S3 S4 E1 E2 E3 E4 I1 I2 I3 I4 R1 R2 R3 R4\n"; - for (size_t i = 0; i < (size_t)result.get_num_time_points(); ++i) { - std::cout << result.get_time(i); - const auto& y = result.get_value(i); - for (size_t k = 0; k < (size_t)mio::omseirs4::InfectionState::Count; ++k) { - std::cout << ' ' << y[(Eigen::Index)k]; - } - std::cout << '\n'; - } + // print results + result.print_table( + {"M", "S1", "S2", "S3", "S4", "E1", "E2", "E3", "E4", "I1", "I2", "I3", "I4", "R1", "R2", "R3", "R4"}, 10, 2); return 0; } diff --git a/cpp/examples/ode_secir.cpp b/cpp/examples/ode_secir.cpp index 304eabc881..46e127baa7 100644 --- a/cpp/examples/ode_secir.cpp +++ b/cpp/examples/ode_secir.cpp @@ -93,26 +93,10 @@ int main() mio::TimeSeries secir = simulate(t0, tmax, dt, model, std::move(integrator)); */ - bool print_to_terminal = true; - - if (print_to_terminal) { - std::vector vars = {"S", "E", "C", "C_confirmed", "I", "I_confirmed", "H", "U", "R", "D"}; - printf("\n # t"); - for (size_t k = 0; k < (size_t)mio::osecir::InfectionState::Count; k++) { - printf(" %s", vars[k].c_str()); - } - - auto num_points = static_cast(secir.get_num_time_points()); - for (size_t i = 0; i < num_points; i++) { - printf("\n%.14f ", secir.get_time(i)); - Eigen::VectorX res_j = secir.get_value(i); - for (size_t j = 0; j < (size_t)mio::osecir::InfectionState::Count; j++) { - printf(" %.14f", res_j[j]); - } - } - - Eigen::VectorX res_j = secir.get_last_value(); - printf("number total: %f", - res_j[0] + res_j[1] + res_j[2] + res_j[3] + res_j[4] + res_j[5] + res_j[6] + res_j[7]); - } + secir.print_table({"S", "E", "C", "C_confirmed", "I", "I_confirmed", "H", "U", "R", "D"}, 20, 14); + + std::cout << "Number total:" + << (secir.get_last_value().sum() - + secir.get_last_value()[(Eigen::Index)mio::osecir::InfectionState::Dead]) + << "\n"; } From 9b7977c5fd19f5c0cea5f4a473d1f1de2c6adeed Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:21:50 +0200 Subject: [PATCH 05/21] use correct cmake method for path options --- cpp/CMakeLists.txt | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 891a2992ec..145e1308fd 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -27,8 +27,12 @@ option(MEMILIO_ENABLE_WARNINGS_AS_ERRORS "Build memilio with warnings as errors. option(MEMILIO_ENABLE_IPOPT "Enable numerical optimization with Ipopt, requires a Fortran compiler." OFF) option(MEMILIO_ENABLE_PROFILING "Enable runtime performance profiling of memilio." OFF) option(MEMILIO_ENABLE_LIKWID_MARKER "Enable performance measuring with likwid markers." OFF) -option(MEMILIO_BASE_DIR "Path to the MEmilio project root, used for file I/O. Set to overwrite default." "") -option(MEMILIO_DATA_DIR "Path to the MEmilio data directory. Set to overwrite default." "") + +cmake_path(CONVERT "${PROJECT_SOURCE_DIR}/.." TO_CMAKE_PATH_LIST PROJECT_ROOT_NORMALIZED NORMALIZE) +set(MEMILIO_BASE_DIR "${PROJECT_ROOT_NORMALIZED}" CACHE PATH + "Path to the MEmilio project root, used for file I/O. Can be used through mio::base_dir().") +set(MEMILIO_DATA_DIR "${PROJECT_ROOT_NORMALIZED}/data" CACHE PATH + "Path to the MEmilio data directory. Can be used through mio::data_dir().") mark_as_advanced( MEMILIO_USE_BUNDLED_SPDLOG @@ -86,15 +90,6 @@ set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set(CMAKE_INSTALL_RPATH "${CMAKE_BINARY_DIR}/lib" "${CMAKE_BINARY_DIR}/bin") -# if undefined, set MEMILIO_BASE_DIR to the directory containing cpp (i.e., the root of the git repo) -if(MEMILIO_BASE_DIR STREQUAL "") - cmake_path(CONVERT "${PROJECT_SOURCE_DIR}/.." TO_CMAKE_PATH_LIST MEMILIO_BASE_DIR NORMALIZE) -endif() -# if undefined, set MEMILIO_DATA_DIR to the projects data directory -if(MEMILIO_DATA_DIR STREQUAL "") - cmake_path(CONVERT "${PROJECT_SOURCE_DIR}/../data" TO_CMAKE_PATH_LIST MEMILIO_DATA_DIR NORMALIZE) -endif() - # code coverage analysis # Note: this only works under linux and with make # Ninja creates different directory names which do not work together with this scrupt From f5c0357d2c0d73be9e96e01bb77122fbabde00bb Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:17:26 +0200 Subject: [PATCH 06/21] make std::filesystem::path loggable --- cpp/memilio/utils/logging.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cpp/memilio/utils/logging.h b/cpp/memilio/utils/logging.h index 46442a39ed..4b56141288 100644 --- a/cpp/memilio/utils/logging.h +++ b/cpp/memilio/utils/logging.h @@ -33,6 +33,20 @@ MSVC_WARNING_DISABLE_PUSH(4996) #include MSVC_WARNING_POP() +#include + +/** + * @brief Make std::filesystem::path formattable as string. + */ +template <> +struct fmt::formatter : formatter { + template + auto format(const std::filesystem::path& path, FormatContext& ctxt) const + { + return formatter::format(path.string(), ctxt); + } +}; + namespace mio { From 3e74eae11d1aa67258df1063a11cfae4e6cec42a Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:07:00 +0200 Subject: [PATCH 07/21] switch from boost::filesystem to std::filesystem, make std::filesystem::path serializable, remove namespace aliases from headers, move GraphABModel into abm namespace, switch select path values from using std::string to std::filesystem::path --- cpp/CMakeLists.txt | 4 +- cpp/examples/abm_history_object.cpp | 2 +- cpp/examples/abm_minimal.cpp | 2 +- cpp/examples/abm_parameter_study.cpp | 6 +- cpp/examples/graph_abm.cpp | 6 +- cpp/examples/ode_seair_optimization.cpp | 20 +++---- .../ode_secir_parameter_study_graph.cpp | 2 +- cpp/examples/ode_secir_read_graph.cpp | 8 +-- cpp/examples/ode_secir_save_results.cpp | 4 +- cpp/memilio/CMakeLists.txt | 2 +- cpp/memilio/io/directories.h | 12 ++-- cpp/memilio/io/io.cpp | 57 +++++++++++-------- cpp/memilio/io/io.h | 47 +++++++++++++-- cpp/memilio/io/mobility_io.cpp | 6 +- cpp/memilio/io/mobility_io.h | 2 +- cpp/memilio/io/result_io.h | 7 +-- cpp/memilio/mobility/graph.h | 13 ++--- cpp/memilio/utils/stl_util.h | 54 ++---------------- cpp/models/graph_abm/graph_abm_mobility.h | 11 +--- cpp/models/graph_abm/graph_abmodel.h | 11 ++-- cpp/models/ode_secirts/parameters_io.cpp | 28 --------- cpp/tests/temp_file_register.h | 39 +++++++++---- cpp/tests/test_graph.cpp | 30 +++++----- cpp/tests/test_graph_abm.cpp | 26 +++++---- cpp/tests/test_save_parameters.cpp | 2 +- cpp/tests/test_utils.cpp | 10 ++-- cpp/thirdparty/CMakeLists.txt | 30 +--------- docs/source/cpp/graph_abm.rst | 6 +- .../simulation/bindings/io/result_io.h | 2 +- 29 files changed, 200 insertions(+), 249 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 145e1308fd..1e72d9043d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -30,9 +30,9 @@ option(MEMILIO_ENABLE_LIKWID_MARKER "Enable performance measuring with likwid ma cmake_path(CONVERT "${PROJECT_SOURCE_DIR}/.." TO_CMAKE_PATH_LIST PROJECT_ROOT_NORMALIZED NORMALIZE) set(MEMILIO_BASE_DIR "${PROJECT_ROOT_NORMALIZED}" CACHE PATH - "Path to the MEmilio project root, used for file I/O. Can be used through mio::base_dir().") + "Path to the MEmilio project root, used for file I/O. Accessed via mio::base_dir().") set(MEMILIO_DATA_DIR "${PROJECT_ROOT_NORMALIZED}/data" CACHE PATH - "Path to the MEmilio data directory. Can be used through mio::data_dir().") + "Path to the MEmilio data directory. Accessed via mio::data_dir().") mark_as_advanced( MEMILIO_USE_BUNDLED_SPDLOG diff --git a/cpp/examples/abm_history_object.cpp b/cpp/examples/abm_history_object.cpp index 58e523143d..4e1c3bf3e2 100644 --- a/cpp/examples/abm_history_object.cpp +++ b/cpp/examples/abm_history_object.cpp @@ -45,7 +45,7 @@ void write_log_to_file(const T& history) auto loc_id = std::get<1>(logg); auto time_points = std::get<0>(logg); std::string input; - std::ofstream myfile(mio::create_directory_or_exit(mio::example_results_dir("abm_history_object")) + + std::ofstream myfile(mio::create_directories_or_exit(mio::example_results_dir("abm_history_object")) / "test_output.txt"); myfile << "Locations as numbers:\n"; for (auto&& id : loc_id[0]) { diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index c3b3f9bcd2..4934fd34e1 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -166,7 +166,7 @@ int main() // The first column is Time. The other columns correspond to the number of people with a certain infection state at this Time: // Time = Time in days, S = Susceptible, E = Exposed, I_NS = InfectedNoSymptoms, I_Sy = InfectedSymptoms, I_Sev = InfectedSevere, // I_Crit = InfectedCritical, R = Recovered, D = Dead - auto outpath = mio::create_directory_or_exit(mio::example_results_dir("abm_minimal")) + "history.txt"; + auto outpath = mio::create_directories_or_exit(mio::example_results_dir("abm_minimal")) / "history.txt"; std::ofstream outfile(outpath); std::get<0>(historyTimeSeries.get_log()) .print_table(outfile, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4); diff --git a/cpp/examples/abm_parameter_study.cpp b/cpp/examples/abm_parameter_study.cpp index 01336fcf36..4e176a3c4b 100644 --- a/cpp/examples/abm_parameter_study.cpp +++ b/cpp/examples/abm_parameter_study.cpp @@ -192,7 +192,7 @@ int main() mio::ParameterStudy study(std::move(model), t0, tmax, mio::abm::TimeSpan(0), num_runs); study.get_rng() = rng; // use the same RNG as the model - const std::string result_dir = mio::create_directory_or_exit(mio::example_results_dir("abm_parameter_study")); + const auto result_dir = mio::create_directories_or_exit(mio::example_results_dir("abm_parameter_study")); // Run the study // The first lambda ("create_simulation" argument) sets up the simulation, the second ("process_simulation_result") @@ -208,7 +208,7 @@ int main() }, [&result_dir](auto&& sim, auto&& run_idx) { auto interpolated_result = mio::interpolate_simulation_result(sim.get_result()); - std::string outpath = mio::path_join(result_dir, "abm_minimal_run_" + std::to_string(run_idx) + ".txt"); + std::string outpath = result_dir / ("abm_minimal_run_" + std::to_string(run_idx) + ".txt"); std::ofstream outfile_run(outpath); sim.get_result().print_table(outfile_run, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4); std::cout << "Results written to " << outpath << std::endl; @@ -218,7 +218,7 @@ int main() // The study collects all results on the root rank, so we only process the results there if (mio::mpi::is_root()) { const auto write_percentile = [&](double p) { - std::ofstream out(mio::path_join(result_dir, fmt::format("Results_p{:0<4.2}.txt", p))); + std::ofstream out(result_dir / fmt::format("Results_p{:0<4.2}.txt", p)); auto ensemble_percentiles = ensemble_percentile(ensemble_results, p); ensemble_percentiles.front().print_table(out, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4); diff --git a/cpp/examples/graph_abm.cpp b/cpp/examples/graph_abm.cpp index 7050dc89a3..9eb1caf65a 100644 --- a/cpp/examples/graph_abm.cpp +++ b/cpp/examples/graph_abm.cpp @@ -47,7 +47,7 @@ struct Logger : mio::LogAlways { */ using Type = std::vector>>; - static Type log(const mio::abm::Simulation& sim) + static Type log(const mio::abm::Simulation& sim) { Type location_information{}; location_information.reserve(size_t(mio::abm::LocationType::Count)); @@ -76,7 +76,7 @@ int main() const auto age_group_adults = mio::AgeGroup(1); const auto age_group_seniors = mio::AgeGroup(2); - auto model1 = mio::GraphABModel(num_age_groups, 0); + auto model1 = mio::abm::GraphABModel(num_age_groups, 0); //Set infection parameters model1.parameters.get() = mio::ParameterDistributionConstant(4.); @@ -134,7 +134,7 @@ int main() add_household_group_to_model(model1, single_hh_group_m1); add_household_group_to_model(model1, family_hh_group_m1); - auto model2 = mio::GraphABModel(num_age_groups, 1); + auto model2 = mio::abm::GraphABModel(num_age_groups, 1); //Set infection parameters model2.parameters.get() = mio::ParameterDistributionConstant(4.); diff --git a/cpp/examples/ode_seair_optimization.cpp b/cpp/examples/ode_seair_optimization.cpp index f73c97a3ef..a12d5cb954 100644 --- a/cpp/examples/ode_seair_optimization.cpp +++ b/cpp/examples/ode_seair_optimization.cpp @@ -514,20 +514,20 @@ void Seair_NLP::finalize_solution(Ipopt::SolverReturn status, Ipopt::Index n, co } mio::oseair::Model model; - const auto result_dir = mio::create_directory_or_exit(mio::example_results_dir("ode_seair_optimization")); + const auto result_dir = mio::create_directories_or_exit(mio::example_results_dir("ode_seair_optimization")); //open files for parameter output - std::ofstream outFileSocialDistancing(result_dir + "SocialDistancing.txt"); - std::ofstream outFileQuarantined(result_dir + "Quarantined.txt"); - std::ofstream outFileTestingRate(result_dir + "TestingRate.txt"); + std::ofstream outFileSocialDistancing(result_dir / "SocialDistancing.txt"); + std::ofstream outFileQuarantined(result_dir / "Quarantined.txt"); + std::ofstream outFileTestingRate(result_dir / "TestingRate.txt"); //open files for state output - std::ofstream outFileSusceptible(result_dir + "Susceptible.txt"); - std::ofstream outFileExposed(result_dir + "Exposed.txt"); - std::ofstream outFileAsymptomatic(result_dir + "Asymptomatic.txt"); - std::ofstream outFileInfected(result_dir + "Infected.txt"); - std::ofstream outFileRecovered(result_dir + "Recovered.txt"); - std::ofstream outFileDead(result_dir + "Dead.txt"); + std::ofstream outFileSusceptible(result_dir / "Susceptible.txt"); + std::ofstream outFileExposed(result_dir / "Exposed.txt"); + std::ofstream outFileAsymptomatic(result_dir / "Asymptomatic.txt"); + std::ofstream outFileInfected(result_dir / "Infected.txt"); + std::ofstream outFileRecovered(result_dir / "Recovered.txt"); + std::ofstream outFileDead(result_dir / "Dead.txt"); set_initial_values(model); int gridindex = 0; diff --git a/cpp/examples/ode_secir_parameter_study_graph.cpp b/cpp/examples/ode_secir_parameter_study_graph.cpp index 3e2e4f3e2b..35ab9e4f7e 100644 --- a/cpp/examples/ode_secir_parameter_study_graph.cpp +++ b/cpp/examples/ode_secir_parameter_study_graph.cpp @@ -316,7 +316,7 @@ int main() } // create directory for results. const auto result_dir = - mio::create_directory_or_exit(mio::example_results_dir("ode_secir_parameter_study_graph")); + mio::create_directories_or_exit(mio::example_results_dir("ode_secir_parameter_study_graph")); auto county_ids = std::vector{1001, 1002, 1003}; auto save_results_status = save_results(ensemble_results, ensemble_params, county_ids, result_dir, false); diff --git a/cpp/examples/ode_secir_read_graph.cpp b/cpp/examples/ode_secir_read_graph.cpp index 01320c9862..074a4368ed 100644 --- a/cpp/examples/ode_secir_read_graph.cpp +++ b/cpp/examples/ode_secir_read_graph.cpp @@ -36,12 +36,12 @@ int main(int argc, char** argv) { mio::set_log_level(mio::LogLevel::critical); - const std::string result_dir = mio::create_directory_or_exit(mio::example_results_dir("ode_secir_read_graph")); + const auto result_dir = mio::create_directories_or_exit(mio::example_results_dir("ode_secir_read_graph")); auto parameters = mio::cli::ParameterSetBuilder() .add<"MobilityFile">( - mio::path_join(mio::base_dir(), "data", "Germany", "mobility", "commuter_mobility_2022.txt"), + mio::base_dir() / "data" / "Germany" / "mobility" / "commuter_mobility_2022.txt", {.description = "Create the mobility file with MEmilio Epidata's getCommuterMobility.py file."}) .build(); @@ -132,7 +132,7 @@ int main(int argc, char** argv) std::cout << "Done" << std::endl; std::cout << "Writing Json Files..." << std::flush; - auto write_status = mio::write_graph(graph, result_dir + "graph_parameters"); + auto write_status = mio::write_graph(graph, result_dir / "graph_parameters"); if (!write_status) { std::cout << "\n" << write_status.error().formatted_message(); return 0; @@ -141,7 +141,7 @@ int main(int argc, char** argv) std::cout << "Reading Json Files..." << std::flush; auto graph_read_result = - mio::read_graph>(result_dir + "graph_parameters"); + mio::read_graph>(result_dir / "graph_parameters"); if (!graph_read_result) { std::cout << "\n" << graph_read_result.error().formatted_message(); diff --git a/cpp/examples/ode_secir_save_results.cpp b/cpp/examples/ode_secir_save_results.cpp index 8b2604470f..022ca127b0 100644 --- a/cpp/examples/ode_secir_save_results.cpp +++ b/cpp/examples/ode_secir_save_results.cpp @@ -85,7 +85,7 @@ int main() std::vector> results_from_sim = {result_from_sim, result_from_sim}; std::vector ids = {1, 2}; - const std::string result_dir = mio::create_directory_or_exit(mio::example_results_dir("ode_secir_save_results")); + const auto result_dir = mio::create_directories_or_exit(mio::example_results_dir("ode_secir_save_results")); auto save_result_status = - mio::save_result(results_from_sim, ids, (int)(size_t)nb_groups, result_dir + "test_result.h5"); + mio::save_result(results_from_sim, ids, (int)(size_t)nb_groups, result_dir / "test_result.h5"); } diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index 7b09a55030..e7d9018849 100644 --- a/cpp/memilio/CMakeLists.txt +++ b/cpp/memilio/CMakeLists.txt @@ -130,7 +130,7 @@ target_include_directories(memilio PUBLIC $ ) -target_link_libraries(memilio PUBLIC spdlog::spdlog Eigen3::Eigen Boost::boost Boost::filesystem Boost::disable_autolinking Random123 AD::AD) +target_link_libraries(memilio PUBLIC spdlog::spdlog Eigen3::Eigen Boost::boost Boost::disable_autolinking Random123 AD::AD) target_compile_options(memilio PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS} diff --git a/cpp/memilio/io/directories.h b/cpp/memilio/io/directories.h index 6c00f1dd8a..38ff146a45 100644 --- a/cpp/memilio/io/directories.h +++ b/cpp/memilio/io/directories.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2026 MEmilio * -* Authors: Julia Bicker, Rene Schmieding +* Authors: Rene Schmieding, Julia Bicker * * Contact: Martin J. Kuehn * @@ -21,8 +21,8 @@ #define MIO_UTILS_DIRECTORIES_H #include "memilio/config.h" // IWYU pragma: keep -#include "memilio/utils/stl_util.h" +#include #include namespace mio @@ -31,7 +31,7 @@ namespace mio /** * @brief Returns the absolute path to the project directory. */ -const static std::string base_dir() +const static std::filesystem::path base_dir() { return details::MEMILIO_BASE_DIR; } @@ -39,7 +39,7 @@ const static std::string base_dir() /** * @brief Returns the absolute path to the project directory. */ -[[maybe_unused]] const static std::string data_dir() +[[maybe_unused]] const static std::filesystem::path data_dir() { return details::MEMILIO_DATA_DIR; } @@ -47,10 +47,10 @@ const static std::string base_dir() /** * @brief Returns the absolute path to a common ouput directory for the code examples. */ -[[maybe_unused]] const static std::string example_results_dir(const std::string& example_name) +[[maybe_unused]] const static std::filesystem::path example_results_dir(const std::string& example_name) { // the last empty string is used to end the output path in a / - const static std::string dir = path_join(base_dir(), "example_results", example_name, ""); + const static std::filesystem::path dir = base_dir() / "example_results" / example_name; return dir; } diff --git a/cpp/memilio/io/io.cpp b/cpp/memilio/io/io.cpp index de98ac15c7..dfd8cacefa 100644 --- a/cpp/memilio/io/io.cpp +++ b/cpp/memilio/io/io.cpp @@ -21,52 +21,61 @@ #include "memilio/utils/compiler_diagnostics.h" #include "memilio/utils/logging.h" -MSVC_WARNING_DISABLE_PUSH(4268) -#include -MSVC_WARNING_POP() +#include namespace mio { std::string get_current_dir_name() { - auto path = boost::filesystem::current_path(); + auto path = std::filesystem::current_path(); return path.string(); } -IOResult create_directory(std::string const& rel_path, std::string& abs_path) +IOResult create_directory(const std::filesystem::path& rel_path, std::string& abs_path, bool create_parents) { - boost::filesystem::path dir(rel_path); - boost::system::error_code ec; - bool created = boost::filesystem::create_directory(dir, ec); - if (ec) { - return failure(ec, rel_path); + auto result = create_directory(rel_path, create_parents); + if (result) { + std::error_code ec; + abs_path = std::filesystem::canonical(rel_path, ec).string(); + if (ec) { + return failure(ec, "Failed to get absolute path of " + rel_path.string()); + } } - abs_path = boost::filesystem::canonical(dir, ec).string(); + return result; +} + +IOResult create_directory(const std::filesystem::path& rel_path, bool create_parents) +{ + std::error_code ec; + bool created; + + if (create_parents) { + created = std::filesystem::create_directories(rel_path, ec); + } + else { + created = std::filesystem::create_directory(rel_path, ec); + } + if (ec) { - return failure(ec, rel_path); + const std::string with_parents = create_parents ? " (with parents)" : ""; + return failure(ec, "Failed to create directory " + rel_path.string() + with_parents); } if (created) { - log_info("Directory '{:s}' was created.", dir.string()); + log_info("Directory '{:s}' was created.", rel_path); } else { - log_info("Directory '{:s}' already exists.", dir.string()); + log_info("Directory '{:s}' already exists.", rel_path); } return success(created); } -IOResult create_directory(std::string const& rel_path) -{ - std::string abs_path; - return create_directory(rel_path, abs_path); -} - -std::string create_directory_or_exit(std::string const& path) +std::filesystem::path create_directories_or_exit(const std::filesystem::path& path, bool create_parents) { std::string abs_path; - auto result = create_directory(path, abs_path); + auto result = create_directory(path, abs_path, create_parents); if (!result) { log_critical("Could not create directory \"{}\": {}", path, result.error().message()); exit(result.error().code().value()); @@ -76,9 +85,9 @@ std::string create_directory_or_exit(std::string const& path) bool file_exists(std::string const& rel_path, std::string& abs_path) { - boost::filesystem::path dir(rel_path); + std::filesystem::path dir(rel_path); abs_path = dir.string(); - return boost::filesystem::exists(dir); + return std::filesystem::exists(dir); } } // namespace mio diff --git a/cpp/memilio/io/io.h b/cpp/memilio/io/io.h index ce7f0ed9b2..82c8022879 100644 --- a/cpp/memilio/io/io.h +++ b/cpp/memilio/io/io.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -608,6 +609,40 @@ IOResult> deserialize_internal(IOContext& io, Tag> tag) return details::deserialize_tuple_element(obj, tag); } +/** + * @brief Serialize an std::filesystem::path. + * @tparam IOContext A type that models the IOContext concept. + * @param io A reference to an IOContext. + * @param path An instance of std::filesystem::path. + */ +template +void serialize_internal(IOContext& io, const std::filesystem::path& path) +{ + auto obj = io.create_object("Path"); + obj.add_element("path", path.native()); +} + +/** + * @brief Deerialize an std::filesystem::path. + * @tparam IOContext A type that models the IOContext concept. + * @param io A reference to an IOContext. + * @param path An instance of std::filesystem::path. + * @return The path if successful, an error otherwise. + */ +template +IOResult deserialize_internal(IOContext& io, Tag) +{ + auto obj = io.expect_object("Path"); + auto str = obj.expect_element("path", Tag{}); + + return apply( + io, + [](auto&& str_) { + return std::filesystem::path(str_); + }, + str); +} + /** * serialize an Eigen matrix expression. * @tparam IOContext a type that models the IOContext concept. @@ -872,24 +907,28 @@ std::string get_current_dir_name(); * @brief Creates a directory in the file system. * @param[in] path Path of a directory. Can be relative to current working directory or absolute. * @param[out] abs_path Will contain the absolute path of the directory. + * @param[in] create_parents When true, create all directories in the path instead of only the last. Default: false. * @return true if the directory was created, false if it already exists, or any errors that occured. */ -IOResult create_directory(std::string const& path, std::string& abs_path); +IOResult create_directory(const std::filesystem::path& path, std::string& abs_path, bool create_parents = false); /** * @brief Creates a directory in the file system. * @param[in] path Path of a directory. Can be relative to current working directory or absolute. + * @param[in] create_parents When true, create all directories in the path instead of only the last. Default: false. * @return True if the directory was created, false if it already exists, or any errors that occured. */ -IOResult create_directory(std::string const& path); +IOResult create_directory(const std::filesystem::path& path, bool create_parents = false); /** - * @brief Creates a directory in the file system, or exits the program with an error code. + * @brief Creates directories in the file system, or exits the program with an error code. + * In contrast to create_directory, this method creates parent directories by default. * @param[in] path Path of a directory. Can be relative to current working directory or absolute. + * @param[in] create_parents When true, create all directories in the path instead of only the last. Default: true. * @return The absolute path to the given directory. * Any error messages during creation will be logged at `LogLevel::Critical`. */ -std::string create_directory_or_exit(std::string const& path); +std::filesystem::path create_directories_or_exit(const std::filesystem::path& path, bool create_parents = true); /** * Check if a file exists. diff --git a/cpp/memilio/io/mobility_io.cpp b/cpp/memilio/io/mobility_io.cpp index 99f90c9715..f25e23a069 100644 --- a/cpp/memilio/io/mobility_io.cpp +++ b/cpp/memilio/io/mobility_io.cpp @@ -160,7 +160,7 @@ IOResult read_mobility_plain(const std::string& filename) #ifdef MEMILIO_HAS_HDF5 IOResult save_edges(const std::vector>>& ensemble_edges, - const std::vector>& pairs_edges, const fs::path& result_dir, + const std::vector>& pairs_edges, const std::filesystem::path& result_dir, bool save_single_runs, bool save_percentiles) { //save results and sum of results over nodes @@ -244,8 +244,8 @@ IOResult save_edges(const std::vector>& results, int start_id = ids[edge_indx].first; auto total = Eigen::Matrix::Zero( - num_timepoints, num_elements) - .eval(); + num_timepoints, num_elements) + .eval(); while (edge_indx < num_edges && ids[edge_indx].first == start_id) { const auto& result_edge = results[edge_indx]; auto edge_result = Eigen::Matrix::Zero( diff --git a/cpp/memilio/io/mobility_io.h b/cpp/memilio/io/mobility_io.h index 3e49deaeaf..aad954cd85 100644 --- a/cpp/memilio/io/mobility_io.h +++ b/cpp/memilio/io/mobility_io.h @@ -207,7 +207,7 @@ IOResult save_edges(const std::vector>& results, * @return Any io errors that occur during writing of the files. */ IOResult save_edges(const std::vector>>& ensemble_edges, - const std::vector>& pairs_edges, const fs::path& result_dir, + const std::vector>& pairs_edges, const std::filesystem::path& result_dir, bool save_single_runs = true, bool save_percentiles = true); #endif //MEMILIO_HAS_HDF5 diff --git a/cpp/memilio/io/result_io.h b/cpp/memilio/io/result_io.h index e14bee3131..cda2bd2525 100644 --- a/cpp/memilio/io/result_io.h +++ b/cpp/memilio/io/result_io.h @@ -29,9 +29,7 @@ #include "memilio/data/analyze_result.h" #include "memilio/io/mobility_io.h" #include "memilio/io/io.h" -#include "boost/filesystem.hpp" -namespace fs = boost::filesystem; namespace mio { @@ -111,7 +109,7 @@ IOResult> read_result(const std::string& filename) template IOResult save_result_with_params(const std::vector>& result, const std::vector& params, const std::vector& county_ids, - const fs::path& result_dir, size_t run_idx) + const std::filesystem::path& result_dir, size_t run_idx) { auto result_dir_run = result_dir / ("run" + std::to_string(run_idx)); BOOST_OUTCOME_TRY(create_directory(result_dir_run.string())); @@ -136,7 +134,8 @@ IOResult save_result_with_params(const std::vector> template IOResult save_results(const std::vector>>& ensemble_results, const std::vector>& ensemble_params, const std::vector& county_ids, - const fs::path& result_dir, bool save_single_runs = true, bool save_percentiles = true) + const std::filesystem::path& result_dir, bool save_single_runs = true, + bool save_percentiles = true) { //save results and sum of results over nodes auto ensemble_result_sum = sum_nodes(ensemble_results); diff --git a/cpp/memilio/mobility/graph.h b/cpp/memilio/mobility/graph.h index 5a0b18d9cd..df2dad2cc2 100644 --- a/cpp/memilio/mobility/graph.h +++ b/cpp/memilio/mobility/graph.h @@ -32,11 +32,6 @@ #include #include -#include "boost/filesystem.hpp" - -//is used to provide some paths as function arguments -namespace fs = boost::filesystem; - namespace mio { @@ -329,9 +324,9 @@ class Graph */ template -IOResult set_nodes(const Parameters& params, Date start_date, Date end_date, const fs::path& data_dir, - const std::string& population_data_path, bool is_node_for_county, - Graph& params_graph, ReadFunction&& read_func, +IOResult set_nodes(const Parameters& params, Date start_date, Date end_date, + const std::filesystem::path& data_dir, const std::string& population_data_path, + bool is_node_for_county, Graph& params_graph, ReadFunction&& read_func, NodeIdFunction&& node_func, const std::vector& scaling_factor_inf, FP scaling_factor_icu, FP tnt_capacity_factor, int num_days = 0, bool export_time_series = false, bool rki_age_groups = true) @@ -402,7 +397,7 @@ IOResult set_nodes(const Parameters& params, Date start_date, Date end_dat */ template -IOResult set_edges(const fs::path& mobility_data_file, Graph& params_graph, +IOResult set_edges(const std::filesystem::path& mobility_data_file, Graph& params_graph, std::initializer_list& mobile_compartments, size_t contact_locations_size, ReadFunction&& read_func, std::vector commuting_weights, std::vector> indices_of_saved_edges = {}) diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index 4cf3a8c7ef..33744c5ae1 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -223,51 +223,6 @@ Range(T&& range) -> Range; template concept HasOstreamOperator = requires(std::ostream os, T t) { os << t; }; -namespace details -{ -/** - * length of a null terminated C string - */ -inline size_t string_length(const char* str) -{ - return std::strlen(str); -} - -/** - * length of a string (e.g std::string) - */ -template -size_t string_length(String&& str) -{ - return str.length(); -} - -/** - * breaks the recursion of path_join_rec. - */ -inline void path_join_rec(std::stringstream&, bool) -{ -} - -/** - * recursive template helper function to join paths - * @param ss stream that collects the result - * @param writeSeparator add separator before adding the next part of the path - * @param head next part of the path to add - * @param tail remaining parts of the path - */ -template -void path_join_rec(std::stringstream& ss, bool writeSeparator, Head&& head, Tail&&... tail) -{ - if (writeSeparator) { - ss << '/'; - } - ss << head; - path_join_rec(ss, string_length(head) > 0 && head[string_length(head) - 1] != '/', tail...); -} - -} // namespace details - /** join one ore more strings with path separators. * Accepts mixed C strings or std::strings. * @@ -283,10 +238,9 @@ void path_join_rec(std::stringstream& ss, bool writeSeparator, Head&& head, Tail template std::string path_join(String&& base, Strings&&... app) { - std::stringstream ss; - details::path_join_rec(ss, false, base, app...); - auto path = ss.str(); - return path; + std::filesystem::path p(base); + ((p /= app), ...); + return p.string(); } /** diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index 99e6e78050..96f5da34d9 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -24,19 +24,12 @@ #include "abm/simulation.h" #include "abm/time.h" #include "abm/location_type.h" -#include "abm/parameters.h" -#include "abm/person.h" -#include "abm/person_id.h" -#include "abm/model_functions.h" #include "graph_abm/graph_abmodel.h" #include "memilio/mobility/graph_simulation.h" #include "memilio/mobility/graph.h" -#include "memilio/utils/compiler_diagnostics.h" + #include -#include -#include #include -#include namespace mio { @@ -48,7 +41,7 @@ class ABMSimulationNode { public: - using Sim = abm::Simulation; + using Sim = abm::Simulation; template ::value, void>> ABMSimulationNode(std::tuple&& history, Args&&... args) diff --git a/cpp/models/graph_abm/graph_abmodel.h b/cpp/models/graph_abm/graph_abmodel.h index 7ea9b799a5..50f8c322c7 100644 --- a/cpp/models/graph_abm/graph_abmodel.h +++ b/cpp/models/graph_abm/graph_abmodel.h @@ -17,7 +17,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #ifndef MIO_ABM_GRAPH_ABMODEL_H #define MIO_ABM_GRAPH_ABMODEL_H @@ -26,19 +25,17 @@ #include "abm/person_id.h" #include "abm/time.h" #include "abm/location_id.h" -#include "memilio/utils/compiler_diagnostics.h" #include "memilio/utils/logging.h" -#include "memilio/utils/mioomp.h" #include "abm/mobility_rules.h" #include "abm/mobility_rules.h" -#include #include -#include #include namespace mio { -using namespace abm; +namespace abm +{ + class GraphABModel : public abm::Model { using Base = Model; @@ -204,6 +201,8 @@ class GraphABModel : public abm::Model std::vector m_person_buffer; ///< List with indices of persons that are subject to move to another node. }; + +} // namespace abm } // namespace mio #endif //MIO_ABM_GRAPH_ABMODEL_H diff --git a/cpp/models/ode_secirts/parameters_io.cpp b/cpp/models/ode_secirts/parameters_io.cpp index 23df74b3ef..576be3bb92 100644 --- a/cpp/models/ode_secirts/parameters_io.cpp +++ b/cpp/models/ode_secirts/parameters_io.cpp @@ -19,32 +19,6 @@ */ #include "ode_secirts/parameters_io.h" -#include "memilio/geography/regions.h" -#include "memilio/io/io.h" -#include "ode_secirts/parameters.h" - -#ifdef MEMILIO_HAS_JSONCPP - -#include "memilio/io/epi_data.h" -#include "memilio/utils/memory.h" -#include "memilio/utils/uncertain_value.h" -#include "memilio/utils/stl_util.h" -#include "memilio/mobility/graph.h" -#include "memilio/mobility/metapopulation_mobility_instant.h" -#include "memilio/epidemiology/damping.h" -#include "memilio/epidemiology/populations.h" -#include "memilio/epidemiology/uncertain_matrix.h" -#include "memilio/utils/compiler_diagnostics.h" -#include "memilio/utils/date.h" - -#include - -#include -#include -#include -#include -#include -#include namespace mio { @@ -55,5 +29,3 @@ namespace details } // namespace details } // namespace osecirts } // namespace mio - -#endif // MEMILIO_HAS_JSONCPP diff --git a/cpp/tests/temp_file_register.h b/cpp/tests/temp_file_register.h index d6d2d07ff3..3efe34fc9e 100644 --- a/cpp/tests/temp_file_register.h +++ b/cpp/tests/temp_file_register.h @@ -20,9 +20,11 @@ #ifndef MIO_TESTS_TMP_FILE_REGISTER_H #define MIO_TESTS_TMP_FILE_REGISTER_H -#include "memilio/io/io.h" #include "memilio/utils/logging.h" -#include "boost/filesystem.hpp" +#include "memilio/utils/random_number_generator.h" +#include "memilio/utils/string_literal.h" + +#include /** * Stores paths of files or directories that will be removed when the register is destroyed. @@ -37,8 +39,8 @@ class TempFileRegister ~TempFileRegister() { for (auto&& file : m_files) { - boost::system::error_code ec; - boost::filesystem::remove_all(file, ec); + std::error_code ec; + std::filesystem::remove_all(file, ec); if (ec) { //just log a warning, failed cleanup should not be considered a test failure. mio::log_warning("Failed to remove temporary file {}:{}", file.string(), ec.message()); @@ -57,26 +59,41 @@ class TempFileRegister */ std::string get_unique_path(const std::string& model = "%%%%-%%%%-%%%%-%%%%") { + // this is an in-place replacement for boost::filesystem::unique_path, as it was removed from std::filesystem + // due to security concerns: https://wg21.cmeerw.net/lwg/issue2633 + // as this storage is used for testing only, the paths created here should not pose any security risks + const auto random_char = []() { + static constexpr mio::StringLiteral char_table{"0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"}; + // this rng does not share a seed with the rest of the tests, so paths are not (trivially) predictable + static mio::RandomNumberGenerator rng; + return char_table.data()[rng() % char_table.size()]; + }; + std::string random_path = model; + for (char& c : random_path) { + if (c == '%') + c = random_char(); + } auto tmp_path = get_tmp_path(); - auto file_name = boost::filesystem::unique_path(model); - auto file_path = tmp_path / file_name; + auto file_path = tmp_path / random_path; m_files.push_back(file_path); return file_path.string(); } private: //get the system temp directory if available, otherwise the current working directory - boost::filesystem::path get_tmp_path() + std::filesystem::path get_tmp_path() { - boost::system::error_code ec; - auto path = boost::filesystem::temp_directory_path(ec); + std::error_code ec; + auto path = std::filesystem::temp_directory_path(ec); if (ec) { - path = boost::filesystem::current_path(); + path = std::filesystem::current_path(); } return path; } - std::vector m_files; + std::vector m_files; }; #endif // MIO_TESTS_TMP_FILE_REGISTER_H diff --git a/cpp/tests/test_graph.cpp b/cpp/tests/test_graph.cpp index 5b2bad8a51..43269506da 100644 --- a/cpp/tests/test_graph.cpp +++ b/cpp/tests/test_graph.cpp @@ -17,36 +17,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "matchers.h" +#include "memilio/epidemiology/age_group.h" +#include "memilio/io/io.h" #include "memilio/mobility/graph.h" #include "memilio/mobility/graph_builder.h" -#include "memilio/epidemiology/age_group.h" +#include "memilio/mobility/metapopulation_mobility_instant.h" #include "memilio/utils/compiler_diagnostics.h" #include "memilio/utils/date.h" -#include "memilio/epidemiology/damping.h" -#include "models/ode_secir/parameters_io.h" -#include "models/ode_secir/parameters.h" #include "models/ode_secir/infection_state.h" #include "models/ode_secir/model.h" +#include "models/ode_secir/parameters_io.h" +#include "models/ode_secir/parameters.h" +#include "models/ode_secirvvs/model.h" #include "models/ode_secirvvs/parameters_io.h" #include "models/ode_secirvvs/parameter_space.h" -#include "memilio/mobility/metapopulation_mobility_instant.h" -#include "models/ode_secirvvs/model.h" -#include "memilio/io/io.h" -#include "matchers.h" #include "temp_file_register.h" #include "memilio/utils/stl_util.h" #include "utils.h" + #include "gmock/gmock-matchers.h" #include "gmock/gmock.h" -#include -#include -#include -#include +#include "gtest/gtest.h" + #include #include -namespace fs = boost::filesystem; - enum class MockContactLocation { Work, @@ -171,7 +167,7 @@ TEST(TestGraph, set_nodes_secir) const auto& read_function_nodes = mock_read_function>; const auto& node_id_function = mock_node_function; - const fs::path& dir = " "; + const std::filesystem::path& dir = " "; auto result = mio::set_nodes, mio::osecir::ContactPatterns, @@ -197,7 +193,7 @@ TEST(TestGraph, set_nodes_secirvvs) const auto& read_function_nodes = mock_read_function>; const auto& node_id_function = mock_node_function; - const fs::path& dir = " "; + const std::filesystem::path& dir = " "; auto result = mio::set_nodes, mio::osecirvvs::ContactPatterns, @@ -222,7 +218,7 @@ TEST(TestGraph, set_edges) mio::osecir::Model model(6); model.populations[{mio::AgeGroup(3), mio::osecir::InfectionState::Exposed}] = 1; mio::Graph, mio::MobilityParameters> params_graph; - const fs::path& dir = " "; + const std::filesystem::path& dir = " "; auto mobile_compartments = {mio::osecir::InfectionState::Susceptible, mio::osecir::InfectionState::Exposed, mio::osecir::InfectionState::InfectedNoSymptoms, mio::osecir::InfectionState::InfectedSymptoms, mio::osecir::InfectionState::Recovered}; diff --git a/cpp/tests/test_graph_abm.cpp b/cpp/tests/test_graph_abm.cpp index e26fd7f6b6..1e0c23210d 100644 --- a/cpp/tests/test_graph_abm.cpp +++ b/cpp/tests/test_graph_abm.cpp @@ -47,7 +47,7 @@ TEST(TestGraphAbm, test_advance_node) { auto t = mio::abm::TimePoint(0); auto dt = mio::abm::hours(10); - auto model = mio::GraphABModel(size_t(1), 1); + auto model = mio::abm::GraphABModel(size_t(1), 1); model.parameters.get()[mio::AgeGroup(0)] = true; auto home_id = model.add_location(mio::abm::LocationType::Home); auto& home = model.get_location(home_id); @@ -70,11 +70,11 @@ TEST(TestGraphAbm, test_advance_node) TEST(TestGraphAbm, test_apply_mobility) { auto model1 = - mio::GraphABModel(size_t(2), 1, std::vector{&mio::abm::go_to_work}); + mio::abm::GraphABModel(size_t(2), 1, std::vector{&mio::abm::go_to_work}); auto model2 = - mio::GraphABModel(size_t(2), 2, std::vector{&mio::abm::go_to_work}); + mio::abm::GraphABModel(size_t(2), 2, std::vector{&mio::abm::go_to_work}); auto model3 = - mio::GraphABModel(size_t(2), 3, std::vector{&mio::abm::go_to_work}); + mio::abm::GraphABModel(size_t(2), 3, std::vector{&mio::abm::go_to_work}); model1.parameters.get()[mio::AgeGroup(0)] = true; model2.parameters.get()[mio::AgeGroup(0)] = true; model3.parameters.get()[mio::AgeGroup(0)] = true; @@ -168,8 +168,8 @@ TEST(TestGraphAbm, test_apply_mobility) TEST(TestGraphABM, test_graph_simulation) { - auto model1 = mio::GraphABModel(size_t(1), 0); - auto model2 = mio::GraphABModel(size_t(1), 1); + auto model1 = mio::abm::GraphABModel(size_t(1), 0); + auto model2 = mio::abm::GraphABModel(size_t(1), 1); mio::abm::TimePoint t0 = mio::abm::TimePoint(0); mio::abm::TimePoint tmax = t0 + mio::abm::days(5); @@ -188,7 +188,8 @@ TEST(TestGraphABM, test_graph_simulation) TEST(TestGraphABM, mask_compliance) { - auto model = mio::GraphABModel(size_t(2), 0, std::vector{&mio::abm::go_to_work}); + auto model = + mio::abm::GraphABModel(size_t(2), 0, std::vector{&mio::abm::go_to_work}); model.parameters.get()[mio::AgeGroup(1)] = true; model.parameters.get()[mio::AgeGroup(0)] = true; //add home, work and school location @@ -241,11 +242,12 @@ TEST(TestGraphABM, mask_compliance) TEST(TestGraphABM, test_get_person) { - auto model = mio::GraphABModel(size_t(2), 0, std::vector{&mio::abm::go_to_work}); - auto home = model.add_location(mio::abm::LocationType::Home); - auto work = model.add_location(mio::abm::LocationType::Work); - auto pid1 = model.add_person(home, mio::AgeGroup(0)); - auto pid2 = model.add_person(work, mio::AgeGroup(1)); + auto model = + mio::abm::GraphABModel(size_t(2), 0, std::vector{&mio::abm::go_to_work}); + auto home = model.add_location(mio::abm::LocationType::Home); + auto work = model.add_location(mio::abm::LocationType::Work); + auto pid1 = model.add_person(home, mio::AgeGroup(0)); + auto pid2 = model.add_person(work, mio::AgeGroup(1)); auto& p1 = model.get_person(pid1); EXPECT_EQ(p1.get_location(), home); diff --git a/cpp/tests/test_save_parameters.cpp b/cpp/tests/test_save_parameters.cpp index c8f628826f..7894d5f7a9 100644 --- a/cpp/tests/test_save_parameters.cpp +++ b/cpp/tests/test_save_parameters.cpp @@ -837,7 +837,7 @@ TEST(TestSaveParameters, ExtrapolateRKI) TempFileRegister file_register; auto results_dir = file_register.get_unique_path("ExtrapolateRKI-%%%%-%%%%"); - boost::filesystem::create_directory(results_dir); + std::filesystem::create_directory(results_dir); auto extrapolate_result = mio::osecir::export_input_data_county_timeseries( model, results_dir, county, date, scaling_factor_inf, scaling_factor_icu, 1, mio::path_join(TEST_DATA_DIR, "county_divi_ma7.json"), diff --git a/cpp/tests/test_utils.cpp b/cpp/tests/test_utils.cpp index c40961951f..040c07e98d 100644 --- a/cpp/tests/test_utils.cpp +++ b/cpp/tests/test_utils.cpp @@ -28,7 +28,7 @@ #include #include -#include +#include template struct CategoryTag : public mio::Index> { @@ -193,10 +193,10 @@ TEST(TestUtils, LogLevelOverride) TEST(TestUtils, base_dir) { - auto base_dir = boost::filesystem::path(mio::base_dir()); + auto base_dir = std::filesystem::path(mio::base_dir()); // check that the path exists - EXPECT_TRUE(boost::filesystem::exists(base_dir)); + EXPECT_TRUE(std::filesystem::exists(base_dir)); // check that the path is correct, by sampling some fixed paths from project files - EXPECT_TRUE(boost::filesystem::exists(base_dir / "cpp" / "memilio")); - EXPECT_TRUE(boost::filesystem::exists(base_dir / "pycode" / "memilio-epidata")); + EXPECT_TRUE(std::filesystem::exists(base_dir / "cpp" / "memilio")); + EXPECT_TRUE(std::filesystem::exists(base_dir / "pycode" / "memilio-epidata")); } diff --git a/cpp/thirdparty/CMakeLists.txt b/cpp/thirdparty/CMakeLists.txt index 72abaa1f67..f051ce8d5a 100644 --- a/cpp/thirdparty/CMakeLists.txt +++ b/cpp/thirdparty/CMakeLists.txt @@ -1,7 +1,7 @@ # Versions of the bundled libraries # If you like to upgrade, just change the number set(MEMILIO_EIGEN_VERSION "3.4.0") -set(MEMILIO_SPDLOG_VERSION "1.15.0") +set(MEMILIO_SPDLOG_VERSION "1.17.0") set(MEMILIO_BOOST_VERSION "1.84.0") set(MEMILIO_MINIMAL_BOOST_VERSION "1.76.0") set(MEMILIO_JSONCPP_VERSION "1.9.6") @@ -102,35 +102,11 @@ if(MEMILIO_USE_BUNDLED_BOOST) target_compile_definitions(boost_disable_autolink INTERFACE BOOST_ALL_NO_LIB) add_library(Boost::disable_autolinking ALIAS boost_disable_autolink) - add_library(boost_filesystem STATIC - ${boost_SOURCE_DIR}/libs/filesystem/src/codecvt_error_category.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/directory.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/exception.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/operations.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/path.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/path_traits.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/portability.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/unique_path.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp - ${boost_SOURCE_DIR}/libs/filesystem/src/windows_file_codecvt.cpp - ) - - # Ensure that the boost atomic library is used instead of the standard atomic library, where some functionality is only available as of C++20. - target_compile_definitions(boost_filesystem PUBLIC BOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF) - - target_link_libraries(boost_filesystem PUBLIC boost_disable_autolink boost) - set_property(TARGET boost_filesystem PROPERTY POSITION_INDEPENDENT_CODE ON) - add_library(Boost::filesystem ALIAS boost_filesystem) - - if(NOT MSVC) # on gcc and apple clang we need to define BOOST_NO_CXX98_FUNCTION_BASE because a deprecated function is sometimes used in boost - target_compile_definitions(boost_filesystem PUBLIC BOOST_NO_CXX98_FUNCTION_BASE) - endif() - - set(Boost_LIBRARIES Boost::boost Boost::filesystem) + set(Boost_LIBRARIES Boost::boost) set(Boost_FOUND ON) else() - find_package(Boost ${MEMILIO_MINIMAL_BOOST_VERSION}...${MEMILIO_BOOST_VERSION} REQUIRED COMPONENTS outcome optional filesystem) + find_package(Boost ${MEMILIO_MINIMAL_BOOST_VERSION}...${MEMILIO_BOOST_VERSION} REQUIRED COMPONENTS outcome optional) endif() # ## HDF5 diff --git a/docs/source/cpp/graph_abm.rst b/docs/source/cpp/graph_abm.rst index 76d6fc7220..fe277d346e 100644 --- a/docs/source/cpp/graph_abm.rst +++ b/docs/source/cpp/graph_abm.rst @@ -6,7 +6,7 @@ Introduction This model realizes multiple instances of the mobility-based agent-based model (ABM) ``abm::Model`` (see :doc:`mobility_based_abm` for the documentation) as nodes in a directed graph. One local model represents a geographical region. The regions are connected by the graph edges. Mobility within one node -and via the graph edges follows the same mobility rules that can be handed as argument to ``mio::GraphABModel``. +and via the graph edges follows the same mobility rules that can be handed as argument to ``mio::abm::GraphABModel``. Therefore this graph-based agent-based (graph-ABM) model can be reduced to a single mobility-based agent-based model if simulation time steps within the whole graph, i.e. the step size of each node and the step size of the edge exchange, are equal. Preimplemented mobility rules can be found in `cpp/models/abm/mobility_rules.h `_. @@ -35,8 +35,8 @@ First, the models (corresponding to the graph nodes) have to be created and init const auto age_group_adults = mio::AgeGroup(1); //Initialize the models with id 0 and 1 - auto model1 = mio::GraphABModel(num_age_groups, 0); - auto model2 = mio::GraphABModel(num_age_groups, 1); + auto model1 = mio::abm::GraphABModel(num_age_groups, 0); + auto model2 = mio::abm::GraphABModel(num_age_groups, 1); For all models/nodes, the infection parameters have to be set. If this is not done, the default values are used. For information on what parameters the mobility-based agent-based model ``abm::Model`` has and how to set them, see :doc:`mobility_based_abm`. Below, only the age groups that go to school and work are set: diff --git a/pycode/memilio-simulation/memilio/simulation/bindings/io/result_io.h b/pycode/memilio-simulation/memilio/simulation/bindings/io/result_io.h index 6f68190125..5dc0ae4e3c 100644 --- a/pycode/memilio-simulation/memilio/simulation/bindings/io/result_io.h +++ b/pycode/memilio-simulation/memilio/simulation/bindings/io/result_io.h @@ -38,7 +38,7 @@ void bind_save_results(pybind11::module_& m) [&](const std::vector>>& ensemble_results, const std::vector>& ensemble_params, const std::vector& county_ids, const std::string& result_dir, bool save_single_runs, bool save_percentiles) { - boost::filesystem::path dir(result_dir); + std::filesystem::path dir(result_dir); auto ioresult = mio::save_results(ensemble_results, ensemble_params, county_ids, dir, save_single_runs, save_percentiles); return NULL; From 45efdcb87282415337a61dcfdb7e53b2aaa4a735 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:07:18 +0200 Subject: [PATCH 08/21] update sbml converter --- cpp/sbml_model_generation/sbml_to_memilio.cpp | 166 ++++++++---------- 1 file changed, 69 insertions(+), 97 deletions(-) diff --git a/cpp/sbml_model_generation/sbml_to_memilio.cpp b/cpp/sbml_model_generation/sbml_to_memilio.cpp index 57b78515fa..87aad51cfc 100644 --- a/cpp/sbml_model_generation/sbml_to_memilio.cpp +++ b/cpp/sbml_model_generation/sbml_to_memilio.cpp @@ -3,32 +3,17 @@ #include "memilio/io/io.h" #include -#include #include #include +#include #include #include #include #include #include -/** - * @brief Get the filename. - * - * @param[in] filepath A path including the filename. - * @return std::string The path without the filename. - * - * Uses `boost::filesystem::path` to get the path to the input file, then uses `stem()` to get the name of the input - * file without file ending. - */ -std::string get_filename(const std::string& filepath) -{ - boost::filesystem::path p(filepath); - return p.stem().string(); -} - /** * @brief Get the path to a folder. * @@ -40,47 +25,30 @@ std::string get_filename(const std::string& filepath) * build directory. This function is thus tailored to find the folder for the sbml generated model files. * If the folder is not found, it returns an error code. */ -mio::IOResult get_path(std::string folder_name) +mio::IOResult get_path(std::string folder_name) { - boost::filesystem::path current_path = boost::filesystem::absolute(boost::filesystem::current_path()); - boost::filesystem::path folder_path = current_path / folder_name; - if (boost::filesystem::exists(folder_path) && boost::filesystem::is_directory(folder_path) && + std::filesystem::path current_path = std::filesystem::absolute(std::filesystem::current_path()); + std::filesystem::path folder_path = current_path / folder_name; + if (std::filesystem::exists(folder_path) && std::filesystem::is_directory(folder_path) && current_path.stem().string() != "build") { - return mio::success(folder_path.string()); + return mio::success(folder_path); } current_path = current_path.parent_path(); folder_path = current_path / folder_name; - if (boost::filesystem::exists(folder_path) && boost::filesystem::is_directory(folder_path) && + if (std::filesystem::exists(folder_path) && std::filesystem::is_directory(folder_path) && current_path.stem().string() != "build") { - return mio::success(folder_path.string()); + return mio::success(folder_path); } current_path = current_path.parent_path(); folder_path = current_path / folder_name; - if (boost::filesystem::exists(folder_path) && boost::filesystem::is_directory(folder_path) && + if (std::filesystem::exists(folder_path) && std::filesystem::is_directory(folder_path) && current_path.stem().string() != "build") { - return mio::success(folder_path.string()); + return mio::success(folder_path); } return mio::failure(mio::StatusCode::FileNotFound, "Could not find the folder " + folder_name + "in the current path or its parent directories."); } -/** - * @brief Creates a folder with the given name to store the generated model files. - * - * @param[in] foldername The name of the folder to be created. - * @param[in] path The path where the folder should be created -this needs to exist. - * - * Extracts the filename using :cpp:func:`get_filename(const std::string& filename)`, converts it to lower case and creates - * a folder with the resulting name at the location given by `path` using `boost::filesystem`. - */ -void create_folder(const std::string& foldername, const std::string& path) -{ - std::string lowercase_name = boost::to_lower_copy(foldername); - boost::filesystem::path p(path + "/" + lowercase_name); - boost::filesystem::create_directory(p); - mio::log_info("Creating folder at ./{}", p.string()); -} - /** * @brief Strips leading and trailing brackets and minus signs from a formula. * @@ -420,17 +388,17 @@ mio::IOResult verify_model_suitability(const Model& model) * @return mio::IOResult Succes or error code. * * Extracts the filename using :cpp:func:`get_filename(const std::string& filename)`, converts it to lower case and creates - * a file with the resulting name using `boost::filesystem` at the location given by `path`. Then it writes the + * a file with the resulting name using `std::filesystem` at the location given by `path`. Then it writes the * species in the model as infection states to the file. */ -mio::IOResult create_infection_state(Model& model, const std::string& filename, const std::string& path) +mio::IOResult create_infection_state(Model& model, const std::string& filename, const std::filesystem::path& path) { std::string lowercase_name = boost::to_lower_copy(filename); size_t number_species = model.getListOfSpecies()->size(); std::string uppercase_name = boost::to_upper_copy(filename); std::ofstream infection_state; - infection_state.open(path + "/" + lowercase_name + "/infection_state.h", std::ios::out); + infection_state.open(path / lowercase_name / "infection_state.h", std::ios::out); if (infection_state) { infection_state << "#ifndef " << uppercase_name << "_INFECTIONSTATE_H" << std::endl; @@ -465,11 +433,11 @@ mio::IOResult create_infection_state(Model& model, const std::string& file * @return mio::IOResult Succes or error code. * * Extracts the filename using :cpp:func:`get_filename(const std::string& filename)`, converts it to lower case and creates - * a file with the resulting name using `boost::filesystem` at the location given by `path`. + * a file with the resulting name using `std::filesystem` at the location given by `path`. * Then it creates one struct for every parameter in the model. It uses the value as returned by libsbml as * default value. (This may be overwritten in the example.cpp file.) */ -mio::IOResult create_parameters(Model& model, const std::string& filename, const std::string& path) +mio::IOResult create_parameters(Model& model, const std::string& filename, const std::filesystem::path& path) { std::string lowercase_name = boost::to_lower_copy(filename); std::string uppercase_name = boost::to_upper_copy(filename); @@ -477,7 +445,7 @@ mio::IOResult create_parameters(Model& model, const std::string& filename, size_t number_parameters = model.getListOfParameters()->size(); std::ofstream parameters; - parameters.open(path + "/" + lowercase_name + "/parameters.h", std::ios::out); + parameters.open(path / lowercase_name / "parameters.h", std::ios::out); if (parameters) { parameters << "#ifndef " << uppercase_name << "_PARAMETERS_H" << std::endl; @@ -535,13 +503,13 @@ mio::IOResult create_parameters(Model& model, const std::string& filename, * * Creates the generic model.cpp file. The file location is generated using the filename and the `path`. */ -mio::IOResult create_model_cpp(const std::string& filename, const std::string& path) +mio::IOResult create_model_cpp(const std::string& filename, const std::filesystem::path& path) { std::string lowercase_name = boost::to_lower_copy(filename); std::ofstream model_cpp; - model_cpp.open(path + "/" + lowercase_name + "/model.cpp", std::ios::out); + model_cpp.open(path / lowercase_name / "model.cpp", std::ios::out); if (model_cpp) { model_cpp << "#include \"" << lowercase_name << "/model.h\"\n\nnamespace mio\n{" << std::endl; @@ -571,14 +539,14 @@ mio::IOResult create_model_cpp(const std::string& filename, const std::str * * In the end it appends a generic serialization function. */ -mio::IOResult create_model_h(Model& model, const std::string& filename, const std::string& path) +mio::IOResult create_model_h(Model& model, const std::string& filename, const std::filesystem::path& path) { std::string lowercase_name = boost::to_lower_copy(filename); std::string uppercase_name = boost::to_upper_copy(filename); size_t number_species = model.getListOfSpecies()->size(); size_t number_reactions = model.getListOfReactions()->size(); std::ofstream model_h; - model_h.open(path + "/" + lowercase_name + "/model.h", std::ios::out); + model_h.open(path / lowercase_name / "model.h", std::ios::out); if (model_h) { //Add generic code model_h << "#ifndef " << uppercase_name << "_MODEL_H\n#define " << uppercase_name << "_MODEL_H" << std::endl; @@ -779,12 +747,12 @@ mio::IOResult create_model_h(Model& model, const std::string& filename, co * program are needed. The file location is generated using filename and path and the file is stored in the generated * folder. */ -mio::IOResult create_cmake(const std::string& filename, const std::string& path) +mio::IOResult create_cmake(const std::string& filename, const std::filesystem::path& path) { std::string lowercase_name = boost::to_lower_copy(filename); std::ofstream cmakelists; - cmakelists.open(path + "/" + lowercase_name + "/CMakeLists.txt", std::ios::out); + cmakelists.open(path / lowercase_name / "CMakeLists.txt", std::ios::out); if (cmakelists.good()) { cmakelists << "add_library(" << lowercase_name << std::endl; cmakelists << "infection_state.h\nmodel.h\nmodel.cpp\nparameters.h\n)" << std::endl; @@ -817,13 +785,13 @@ mio::IOResult create_cmake(const std::string& filename, const std::string& * The model is simulated in steps to add events that are triggered by a specific time. In the end the model is * simulated for another 50 days. The results are printed to the console and saved in a file as a table. */ -mio::IOResult create_example_cpp(Model& model, const std::string& filename, const std::string& path) +mio::IOResult create_example_cpp(Model& model, const std::string& filename, const std::filesystem::path& path) { std::string lowercase_name = boost::to_lower_copy(filename); size_t number_species = model.getListOfSpecies()->size(); std::ofstream example; - example.open(path + "/" + lowercase_name + ".cpp", std::ios::out); + example.open(path / (lowercase_name + ".cpp"), std::ios::out); if (example) { example << "#include \"memilio/compartments/simulation.h\"\n#include \"memilio/config.h\"\n#include " "\"memilio/math/euler.h\"\n#include \"memilio/math/integrator.h\"\n#include " @@ -928,39 +896,40 @@ mio::IOResult create_example_cpp(Model& model, const std::string& filename * * Appends the necessary commands for compilation to the CMakeLists.txt file in path using the filename. */ -mio::IOResult modify_cmakelists(const std::string& filename, const std::string& path) +mio::IOResult modify_cmakelists(const std::string& filename, const std::filesystem::path& path) { std::string lowercase_name = boost::to_lower_copy(filename); - std::ifstream cmakefile(path + "/CMakeLists.txt"); - if (cmakefile.is_open()) { - std::string line; - bool found = false; - while (std::getline(cmakefile, line)) { - if (line == "add_subdirectory(" + lowercase_name + ")") { - found = true; - break; - } - } - cmakefile.close(); - if (!found) { - std::ofstream modifications; - modifications.open(path + "/CMakeLists.txt", std::ios::app); - if (modifications) { - modifications << "add_subdirectory(" << lowercase_name << ")" << std::endl; - modifications << "add_executable(ex_" << lowercase_name << " " << lowercase_name << ".cpp)" - << std::endl; - modifications << "target_link_libraries(ex_" << lowercase_name << " PRIVATE memilio " << lowercase_name - << ")" << std::endl; - modifications << "target_compile_options(ex_" << lowercase_name - << " PRIVATE ${MEMILIO_CXX_FLAGS_SBML_GENERATED})\n" - << std::endl; - modifications.close(); - } + std::ifstream cmakefile(path / "CMakeLists.txt"); + if (!cmakefile) { + return mio::failure(mio::StatusCode::FileNotFound, "Could not open file for reading: CMakeLists.txt"); + } + std::string line; + bool found = false; + while (std::getline(cmakefile, line)) { + if (line == "add_subdirectory(" + lowercase_name + ")") { + found = true; + break; } } - else { + cmakefile.close(); + + if (found) { + mio::log_info("Found existing entry for {} in CMakeLists.txt", lowercase_name); + return mio::success(); + } + + std::ofstream modifications(path / "CMakeLists.txt", std::ios::app); + if (!modifications) { return mio::failure(mio::StatusCode::FileNotFound, "Could not open file for writing: CMakeLists.txt"); } + modifications << "add_subdirectory(" << lowercase_name << ")" << std::endl; + modifications << "add_executable(ex_" << lowercase_name << " " << lowercase_name << ".cpp)" << std::endl; + modifications << "target_link_libraries(ex_" << lowercase_name << " PRIVATE memilio " << lowercase_name << ")" + << std::endl; + modifications << "target_compile_options(ex_" << lowercase_name << " PRIVATE ${MEMILIO_CXX_FLAGS_SBML_GENERATED})\n" + << std::endl; + modifications.close(); + return mio::success(); } @@ -979,7 +948,7 @@ void format_files(const std::string& filename, const std::string& path) std::string lowercase_name = boost::to_lower_copy(filename); std::string command = std::string("clang-format -i --files= ") + path + "/" + lowercase_name + ".cpp " + path + "/" + lowercase_name + "/**.[hc]*"; - int status = system(command.c_str()); + int status = system(command.c_str()); mio::log_debug("Return status: {}", status); if (status != 0) { mio::log_error("Error while trying to format the files, files are functional but hard to read."); @@ -1006,10 +975,10 @@ int main(int argc, char* argv[]) mio::log_error("Please provide a SBML file at startup!"); return 1; } - std::string filename = argv[1]; + std::filesystem::path sbml_config_file = argv[1]; SBMLReader reader; - std::unique_ptr document(reader.readSBML(filename)); + std::unique_ptr document(reader.readSBML(sbml_config_file.string())); if (SBMLDocument_getNumErrors(document.get()) > 0) { if (XMLError_isFatal(SBMLDocument_getError(document.get(), 0)) || @@ -1027,7 +996,7 @@ int main(int argc, char* argv[]) return 4; } - std::string core_filename = get_filename(filename); + std::string core_filename = boost::to_lower_copy(sbml_config_file.stem().string()); std::string folder_name = "sbml_model_generation"; auto folder_location = get_path(folder_name); @@ -1035,53 +1004,56 @@ int main(int argc, char* argv[]) mio::log_error(folder_location.error().formatted_message()); return 5; } - std::string path = folder_location.value(); + std::filesystem::path path = folder_location.value(); - create_folder(core_filename, path); + if (auto r = mio::create_directory(path / core_filename); !r) { + mio::log_error(r.error().formatted_message()); + return 6; + } result = create_infection_state(model, core_filename, path); if (!result) { mio::log_error(result.error().formatted_message()); - return 6; + return 7; } result = create_parameters(model, core_filename, path); if (!result) { mio::log_error(result.error().formatted_message()); - return 7; + return 8; } result = create_model_cpp(core_filename, path); if (!result) { mio::log_error(result.error().formatted_message()); - return 8; + return 9; } result = create_model_h(model, core_filename, path); if (!result) { mio::log_error(result.error().formatted_message()); - return 9; + return 10; } result = create_cmake(core_filename, path); if (!result) { mio::log_error(result.error().formatted_message()); - return 10; + return 11; } result = create_example_cpp(model, core_filename, path); if (!result) { mio::log_error(result.error().formatted_message()); - return 11; + return 12; } result = modify_cmakelists(core_filename, path); if (!result) { mio::log_error(result.error().formatted_message()); - return 12; + return 13; } mio::log_info("Created all files."); mio::log_info("Formatting files."); - format_files(core_filename, path); + format_files(core_filename, path.string()); } From e283c3dd89e4b967c6999eaa62cb55b4c2d4d97b Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:26:17 +0200 Subject: [PATCH 09/21] update benchmark config dir syntax --- cpp/benchmarks/flow_simulation_ode_secirvvs.cpp | 2 +- cpp/benchmarks/flow_simulation_ode_seir.cpp | 2 +- cpp/benchmarks/graph_simulation.cpp | 2 +- cpp/benchmarks/integrator_step.cpp | 4 ++-- cpp/benchmarks/simulation.cpp | 3 ++- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp b/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp index b43b5e7f0e..64f686b2de 100644 --- a/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp +++ b/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp @@ -25,7 +25,7 @@ #include "ode_secirvvs/model.h" #include -const std::string config_path = mio::base_dir() + "cpp/benchmarks/simulation.config"; +const std::string config_path = (mio::base_dir() / "cpp/benchmarks/simulation.config").string(); // simulation without flows (not in Model definition and not calculated by Simulation) void flowless_sim(::benchmark::State& state) diff --git a/cpp/benchmarks/flow_simulation_ode_seir.cpp b/cpp/benchmarks/flow_simulation_ode_seir.cpp index f73d4c7f70..98521af9a7 100644 --- a/cpp/benchmarks/flow_simulation_ode_seir.cpp +++ b/cpp/benchmarks/flow_simulation_ode_seir.cpp @@ -25,7 +25,7 @@ #include -const std::string config_path = mio::base_dir() + "cpp/benchmarks/simulation.config"; +const std::string config_path = (mio::base_dir() / "cpp/benchmarks/simulation.config").string(); namespace mio { diff --git a/cpp/benchmarks/graph_simulation.cpp b/cpp/benchmarks/graph_simulation.cpp index d18e378490..29498d34f9 100644 --- a/cpp/benchmarks/graph_simulation.cpp +++ b/cpp/benchmarks/graph_simulation.cpp @@ -26,7 +26,7 @@ #include "ode_secirvvs/model.h" #include -const std::string config_path = mio::base_dir() + "cpp/benchmarks/graph_simulation.config"; +const std::string config_path = (mio::base_dir() / "cpp/benchmarks/graph_simulation.config").string(); mio::osecirvvs::Model create_model(size_t num_agegroups, const ScalarType tmax) { diff --git a/cpp/benchmarks/integrator_step.cpp b/cpp/benchmarks/integrator_step.cpp index 5b39a89c1d..4f4734765f 100644 --- a/cpp/benchmarks/integrator_step.cpp +++ b/cpp/benchmarks/integrator_step.cpp @@ -33,8 +33,8 @@ void integrator_step(::benchmark::State& state) // with "num_agegroups" agegroups, and taking "yt" as the state of the simulation at "t_init" // NOTE: yt must have #agegroups * #compartments entries // benchmark setup - auto cfg = - mio::benchmark::IntegratorStepConfig::initialize(mio::base_dir() + "cpp/benchmarks/integrator_step.config"); + auto cfg = mio::benchmark::IntegratorStepConfig::initialize( + (mio::base_dir() / "cpp/benchmarks/integrator_step.config").string()); //auto cfg = mio::benchmark::IntegratorStepConfig::initialize(); auto model = mio::benchmark::model::SecirAgeres(cfg.num_agegroups); // set deriv function and integrator diff --git a/cpp/benchmarks/simulation.cpp b/cpp/benchmarks/simulation.cpp index cba7060f4b..3a98cfab97 100644 --- a/cpp/benchmarks/simulation.cpp +++ b/cpp/benchmarks/simulation.cpp @@ -30,7 +30,8 @@ void simulation(::benchmark::State& state) // suppress non-critical messages mio::set_log_level(mio::LogLevel::critical); // setup benchmark parameters - auto cfg = mio::benchmark::SimulationConfig::initialize(mio::base_dir() + "cpp/benchmarks/simulation.config"); + auto cfg = + mio::benchmark::SimulationConfig::initialize((mio::base_dir() / "cpp/benchmarks/simulation.config").string()); //auto cfg = mio::benchmark::SimulationConfig::initialize(10); auto model = mio::benchmark::model::SecirAgeres(cfg.num_agegroups); From e68bb7f0999605d31fbe4d0a51be5289b0baa32f Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:58:10 +0200 Subject: [PATCH 10/21] start of msvc workarounds --- cpp/examples/abm_parameter_study.cpp | 4 ++-- .../ode_secir_parameter_study_graph.cpp | 6 ++--- cpp/examples/ode_secir_read_graph.cpp | 4 ++-- cpp/examples/ode_secir_save_results.cpp | 2 +- cpp/memilio/utils/stl_util.h | 2 +- cpp/tests/test_stl_util.cpp | 23 +++++++++++-------- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/cpp/examples/abm_parameter_study.cpp b/cpp/examples/abm_parameter_study.cpp index 4e176a3c4b..313663ef20 100644 --- a/cpp/examples/abm_parameter_study.cpp +++ b/cpp/examples/abm_parameter_study.cpp @@ -208,10 +208,10 @@ int main() }, [&result_dir](auto&& sim, auto&& run_idx) { auto interpolated_result = mio::interpolate_simulation_result(sim.get_result()); - std::string outpath = result_dir / ("abm_minimal_run_" + std::to_string(run_idx) + ".txt"); + auto outpath = result_dir / ("abm_minimal_run_" + std::to_string(run_idx) + ".txt"); std::ofstream outfile_run(outpath); sim.get_result().print_table(outfile_run, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4); - std::cout << "Results written to " << outpath << std::endl; + std::cout << "Results written to " << outpath.string() << std::endl; return std::vector{interpolated_result}; }); diff --git a/cpp/examples/ode_secir_parameter_study_graph.cpp b/cpp/examples/ode_secir_parameter_study_graph.cpp index 35ab9e4f7e..9531c74d77 100644 --- a/cpp/examples/ode_secir_parameter_study_graph.cpp +++ b/cpp/examples/ode_secir_parameter_study_graph.cpp @@ -287,14 +287,14 @@ int main() auto params = std::vector>{}; params.reserve(results_graph.nodes().size()); std::transform(results_graph.nodes().begin(), results_graph.nodes().end(), std::back_inserter(params), - [](auto&& node) { + [](auto&& node) { return node.property.get_simulation().get_model(); }); auto edges = std::vector>{}; edges.reserve(results_graph.edges().size()); std::transform(results_graph.edges().begin(), results_graph.edges().end(), std::back_inserter(edges), - [](auto&& edge) { + [](auto&& edge) { return edge.property.get_mobility_results(); }); @@ -316,7 +316,7 @@ int main() } // create directory for results. const auto result_dir = - mio::create_directories_or_exit(mio::example_results_dir("ode_secir_parameter_study_graph")); + mio::create_directories_or_exit(mio::example_results_dir("ode_secir_parameter_study_graph")).string(); auto county_ids = std::vector{1001, 1002, 1003}; auto save_results_status = save_results(ensemble_results, ensemble_params, county_ids, result_dir, false); diff --git a/cpp/examples/ode_secir_read_graph.cpp b/cpp/examples/ode_secir_read_graph.cpp index 074a4368ed..ff5e3b57e1 100644 --- a/cpp/examples/ode_secir_read_graph.cpp +++ b/cpp/examples/ode_secir_read_graph.cpp @@ -132,7 +132,7 @@ int main(int argc, char** argv) std::cout << "Done" << std::endl; std::cout << "Writing Json Files..." << std::flush; - auto write_status = mio::write_graph(graph, result_dir / "graph_parameters"); + auto write_status = mio::write_graph(graph, (result_dir / "graph_parameters").string()); if (!write_status) { std::cout << "\n" << write_status.error().formatted_message(); return 0; @@ -141,7 +141,7 @@ int main(int argc, char** argv) std::cout << "Reading Json Files..." << std::flush; auto graph_read_result = - mio::read_graph>(result_dir / "graph_parameters"); + mio::read_graph>((result_dir / "graph_parameters").string()); if (!graph_read_result) { std::cout << "\n" << graph_read_result.error().formatted_message(); diff --git a/cpp/examples/ode_secir_save_results.cpp b/cpp/examples/ode_secir_save_results.cpp index 022ca127b0..f8acc8c2a6 100644 --- a/cpp/examples/ode_secir_save_results.cpp +++ b/cpp/examples/ode_secir_save_results.cpp @@ -87,5 +87,5 @@ int main() const auto result_dir = mio::create_directories_or_exit(mio::example_results_dir("ode_secir_save_results")); auto save_result_status = - mio::save_result(results_from_sim, ids, (int)(size_t)nb_groups, result_dir / "test_result.h5"); + mio::save_result(results_from_sim, ids, (int)(size_t)nb_groups, (result_dir / "test_result.h5").string()); } diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index 33744c5ae1..ac42e312ac 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -239,7 +239,7 @@ template std::string path_join(String&& base, Strings&&... app) { std::filesystem::path p(base); - ((p /= app), ...); + ((p /= std::filesystem::path(app)), ...); return p.string(); } diff --git a/cpp/tests/test_stl_util.cpp b/cpp/tests/test_stl_util.cpp index ef4e5a6070..363584952e 100644 --- a/cpp/tests/test_stl_util.cpp +++ b/cpp/tests/test_stl_util.cpp @@ -154,21 +154,26 @@ TEST(TestPathJoin, joinOne) EXPECT_EQ(mio::path_join("."), "."); } +std::string native(std::string_view dir) +{ + return std::filesystem::path(dir).native(); +} + TEST(TestPathJoin, joinTwoMixedClasses) { - EXPECT_EQ(mio::path_join(".", "dir"), "./dir"); - EXPECT_EQ(mio::path_join("./", std::string("dir")), "./dir"); - EXPECT_EQ(mio::path_join(std::string("/"), "dir"), "/dir"); - EXPECT_EQ(mio::path_join(std::string("."), std::string("dir")), "./dir"); + EXPECT_EQ(mio::path_join(".", "dir"), native("./dir")); + EXPECT_EQ(mio::path_join("./", std::string("dir")), native("./dir")); + EXPECT_EQ(mio::path_join(std::string("/"), "dir"), native("/dir")); + EXPECT_EQ(mio::path_join(std::string("."), std::string("dir")), native("./dir")); } TEST(TestPathJoin, ignoreEmpty) { - EXPECT_EQ(mio::path_join(""), ""); - EXPECT_EQ(mio::path_join("", "dir"), "dir"); - EXPECT_EQ(mio::path_join("", "", "dir"), "dir"); - EXPECT_EQ(mio::path_join(".", "", "", "dir"), "./dir"); - EXPECT_EQ(mio::path_join("./", "", "", "dir"), "./dir"); + EXPECT_EQ(mio::path_join(""), native("")); + EXPECT_EQ(mio::path_join("", "dir"), native("dir")); + EXPECT_EQ(mio::path_join("", "", "dir"), native("dir")); + EXPECT_EQ(mio::path_join(".", "", "", "dir"), native("./dir")); + EXPECT_EQ(mio::path_join("./", "", "", "dir"), native("./dir")); } namespace From f2325a50afecca852b86cfb9e5b18cd0db71ce95 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:43:28 +0200 Subject: [PATCH 11/21] msvc workarounds 2 --- cpp/examples/ode_secir_read_graph.cpp | 2 +- cpp/memilio/io/io.h | 2 +- cpp/tests/test_stl_util.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/examples/ode_secir_read_graph.cpp b/cpp/examples/ode_secir_read_graph.cpp index ff5e3b57e1..cddad4cd25 100644 --- a/cpp/examples/ode_secir_read_graph.cpp +++ b/cpp/examples/ode_secir_read_graph.cpp @@ -41,7 +41,7 @@ int main(int argc, char** argv) auto parameters = mio::cli::ParameterSetBuilder() .add<"MobilityFile">( - mio::base_dir() / "data" / "Germany" / "mobility" / "commuter_mobility_2022.txt", + (mio::base_dir() / "data" / "Germany" / "mobility" / "commuter_mobility_2022.txt").string(), {.description = "Create the mobility file with MEmilio Epidata's getCommuterMobility.py file."}) .build(); diff --git a/cpp/memilio/io/io.h b/cpp/memilio/io/io.h index 82c8022879..b2eed3066b 100644 --- a/cpp/memilio/io/io.h +++ b/cpp/memilio/io/io.h @@ -619,7 +619,7 @@ template void serialize_internal(IOContext& io, const std::filesystem::path& path) { auto obj = io.create_object("Path"); - obj.add_element("path", path.native()); + obj.add_element("path", path.string()); } /** diff --git a/cpp/tests/test_stl_util.cpp b/cpp/tests/test_stl_util.cpp index 363584952e..08bc13a767 100644 --- a/cpp/tests/test_stl_util.cpp +++ b/cpp/tests/test_stl_util.cpp @@ -156,7 +156,7 @@ TEST(TestPathJoin, joinOne) std::string native(std::string_view dir) { - return std::filesystem::path(dir).native(); + return std::filesystem::path(dir).string(); } TEST(TestPathJoin, joinTwoMixedClasses) From b7050093420fb26ec7198c16f6d4a637ea9473fe Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:17:27 +0200 Subject: [PATCH 12/21] msvc workarounds 3 --- cpp/tests/test_stl_util.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/tests/test_stl_util.cpp b/cpp/tests/test_stl_util.cpp index 08bc13a767..86eed013a8 100644 --- a/cpp/tests/test_stl_util.cpp +++ b/cpp/tests/test_stl_util.cpp @@ -19,6 +19,7 @@ */ #include "memilio/utils/stl_util.h" #include "memilio/utils/compiler_diagnostics.h" +#include #include #include @@ -156,7 +157,7 @@ TEST(TestPathJoin, joinOne) std::string native(std::string_view dir) { - return std::filesystem::path(dir).string(); + return std::filesystem::path(dir, std::filesystem::path::format::generic_format).string(); } TEST(TestPathJoin, joinTwoMixedClasses) From e33d4533541d3cba777c918f7d547151ffc97171 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:56:42 +0200 Subject: [PATCH 13/21] msvc workarounds 4 --- cpp/tests/test_stl_util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/tests/test_stl_util.cpp b/cpp/tests/test_stl_util.cpp index 86eed013a8..a782e2d2b3 100644 --- a/cpp/tests/test_stl_util.cpp +++ b/cpp/tests/test_stl_util.cpp @@ -157,7 +157,7 @@ TEST(TestPathJoin, joinOne) std::string native(std::string_view dir) { - return std::filesystem::path(dir, std::filesystem::path::format::generic_format).string(); + return std::filesystem::path(std::filesystem::path(dir).native()).string(); } TEST(TestPathJoin, joinTwoMixedClasses) From 26f97fff048085d3e37d0730213e8dda2e0dcd78 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:47:13 +0200 Subject: [PATCH 14/21] msvc workarounds 5 --- cpp/tests/test_stl_util.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/cpp/tests/test_stl_util.cpp b/cpp/tests/test_stl_util.cpp index a782e2d2b3..8b90b057d5 100644 --- a/cpp/tests/test_stl_util.cpp +++ b/cpp/tests/test_stl_util.cpp @@ -22,6 +22,7 @@ #include #include #include +#include TEST(TestRange, index_operator) { @@ -155,26 +156,27 @@ TEST(TestPathJoin, joinOne) EXPECT_EQ(mio::path_join("."), "."); } -std::string native(std::string_view dir) +// replace windows path separators by generic ones +std::string generic(const std::string& dir) { - return std::filesystem::path(std::filesystem::path(dir).native()).string(); + return std::filesystem::path(dir).generic_string(); } TEST(TestPathJoin, joinTwoMixedClasses) { - EXPECT_EQ(mio::path_join(".", "dir"), native("./dir")); - EXPECT_EQ(mio::path_join("./", std::string("dir")), native("./dir")); - EXPECT_EQ(mio::path_join(std::string("/"), "dir"), native("/dir")); - EXPECT_EQ(mio::path_join(std::string("."), std::string("dir")), native("./dir")); + EXPECT_EQ(generic(mio::path_join(".", "dir")), "./dir"); + EXPECT_EQ(generic(mio::path_join("./", std::string("dir"))), "./dir"); + EXPECT_EQ(generic(mio::path_join(std::string("/"), "dir")), "/dir"); + EXPECT_EQ(generic(mio::path_join(std::string("."), std::string("dir"))), "./dir"); } TEST(TestPathJoin, ignoreEmpty) { - EXPECT_EQ(mio::path_join(""), native("")); - EXPECT_EQ(mio::path_join("", "dir"), native("dir")); - EXPECT_EQ(mio::path_join("", "", "dir"), native("dir")); - EXPECT_EQ(mio::path_join(".", "", "", "dir"), native("./dir")); - EXPECT_EQ(mio::path_join("./", "", "", "dir"), native("./dir")); + EXPECT_EQ(generic(mio::path_join("")), ""); + EXPECT_EQ(generic(mio::path_join("", "dir")), "dir"); + EXPECT_EQ(generic(mio::path_join("", "", "dir")), "dir"); + EXPECT_EQ(generic(mio::path_join(".", "", "", "dir")), "./dir"); + EXPECT_EQ(generic(mio::path_join("./", "", "", "dir")), "./dir"); } namespace From d59fd5e513425d0d1f334533be0a44e1e74f57de Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:17:05 +0200 Subject: [PATCH 15/21] speed up sbml test by not building test suite --- .github/actions/sbml-test/action.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/sbml-test/action.yaml b/.github/actions/sbml-test/action.yaml index 4b1aa7ba85..5715a2a0dd 100644 --- a/.github/actions/sbml-test/action.yaml +++ b/.github/actions/sbml-test/action.yaml @@ -38,7 +38,7 @@ runs: shell: bash run: | cd cpp/build - cmake -DCMAKE_BUILD_TYPE=Release -DMEMILIO_ENABLE_SBML=ON -Dsbml_DIR=/usr/lib/x86_64-linux-gnu/cmake .. + cmake -DCMAKE_BUILD_TYPE=Release -DMEMILIO_BUILD_TESTS=OFF -DMEMILIO_ENABLE_SBML=ON -Dsbml_DIR=/usr/lib/x86_64-linux-gnu/cmake .. cmake --build . -j 4 - name: Run SBML importer shell: bash @@ -49,5 +49,5 @@ runs: shell: bash run: | cd cpp/build - cmake -DCMAKE_BUILD_TYPE=Release -DMEMILIO_ENABLE_SBML=ON -Dsbml_DIR=/usr/lib/x86_64-linux-gnu/cmake .. + cmake -DCMAKE_BUILD_TYPE=Release -DMEMILIO_BUILD_TESTS=OFF -DMEMILIO_ENABLE_SBML=ON -Dsbml_DIR=/usr/lib/x86_64-linux-gnu/cmake .. cmake --build . -j 4 From 4889b64f0845e23d51b11932419e0dd89f9d8960 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:01:28 +0200 Subject: [PATCH 16/21] serialize paths as (generic) string, add test --- cpp/memilio/io/io.h | 19 ++++++++----------- cpp/tests/test_json_serializer.cpp | 7 +++++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cpp/memilio/io/io.h b/cpp/memilio/io/io.h index b2eed3066b..bcfbb1c23d 100644 --- a/cpp/memilio/io/io.h +++ b/cpp/memilio/io/io.h @@ -618,8 +618,7 @@ IOResult> deserialize_internal(IOContext& io, Tag> tag) template void serialize_internal(IOContext& io, const std::filesystem::path& path) { - auto obj = io.create_object("Path"); - obj.add_element("path", path.string()); + serialize_internal(io, path.generic_string()); } /** @@ -632,15 +631,13 @@ void serialize_internal(IOContext& io, const std::filesystem::path& path) template IOResult deserialize_internal(IOContext& io, Tag) { - auto obj = io.expect_object("Path"); - auto str = obj.expect_element("path", Tag{}); - - return apply( - io, - [](auto&& str_) { - return std::filesystem::path(str_); - }, - str); + auto result = deserialize_internal(io, Tag{}); + if (result) { + return success(result.value()); + } + else { + return failure(result.error()); + } } /** diff --git a/cpp/tests/test_json_serializer.cpp b/cpp/tests/test_json_serializer.cpp index 006647a1b3..81afb46d02 100644 --- a/cpp/tests/test_json_serializer.cpp +++ b/cpp/tests/test_json_serializer.cpp @@ -196,6 +196,13 @@ TEST(TestJsonSerializer, string) check_serialization_of_basic_type(std::string("Hello, World!")); } +TEST(TestJsonSerializer, path) +{ + check_serialization_of_basic_type(std::filesystem::path()); + check_serialization_of_basic_type(std::filesystem::path("Hello/World")); + check_serialization_of_basic_type(std::filesystem::path("Hello\\World")); +} + namespace jsontest { enum class E : int From a9f635cc4688cd5d9350b6c3fd07f97d99a21534 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:07:32 +0200 Subject: [PATCH 17/21] add tests for new features --- cpp/tests/CMakeLists.txt | 1 + cpp/tests/temp_file_register.h | 9 ++-- cpp/tests/test_io_directories.cpp | 51 +++++++++++++++++++++++ cpp/tests/test_io_framework.cpp | 69 +++++++++++++++++++++++++++++++ cpp/tests/test_utils.cpp | 13 ------ 5 files changed, 125 insertions(+), 18 deletions(-) create mode 100644 cpp/tests/test_io_directories.cpp diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 6447c6eabc..99f5e197fe 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -58,6 +58,7 @@ set(TESTSOURCES test_dynamic_npis.cpp test_regions.cpp test_io_cli.cpp + test_io_directories.cpp test_io_framework.cpp test_binary_serializer.cpp test_compartments_simulation.cpp diff --git a/cpp/tests/temp_file_register.h b/cpp/tests/temp_file_register.h index 3efe34fc9e..04ecb5effb 100644 --- a/cpp/tests/temp_file_register.h +++ b/cpp/tests/temp_file_register.h @@ -60,7 +60,7 @@ class TempFileRegister std::string get_unique_path(const std::string& model = "%%%%-%%%%-%%%%-%%%%") { // this is an in-place replacement for boost::filesystem::unique_path, as it was removed from std::filesystem - // due to security concerns: https://wg21.cmeerw.net/lwg/issue2633 + // due to (unfixable) security concerns: https://wg21.cmeerw.net/lwg/issue2633 // as this storage is used for testing only, the paths created here should not pose any security risks const auto random_char = []() { static constexpr mio::StringLiteral char_table{"0123456789" @@ -70,13 +70,12 @@ class TempFileRegister static mio::RandomNumberGenerator rng; return char_table.data()[rng() % char_table.size()]; }; - std::string random_path = model; - for (char& c : random_path) { + std::string random_name = model; + for (char& c : random_name) { if (c == '%') c = random_char(); } - auto tmp_path = get_tmp_path(); - auto file_path = tmp_path / random_path; + auto file_path = get_tmp_path() / std::filesystem::path(random_name); m_files.push_back(file_path); return file_path.string(); } diff --git a/cpp/tests/test_io_directories.cpp b/cpp/tests/test_io_directories.cpp new file mode 100644 index 0000000000..586cbb701e --- /dev/null +++ b/cpp/tests/test_io_directories.cpp @@ -0,0 +1,51 @@ +/* +* Copyright (C) 2020-2026 MEmilio +* +* Authors: Rene Schmieding +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#include "memilio/io/directories.h" +#include "gtest/gtest.h" + +TEST(TestDirectories, base_dir) +{ + auto base_dir = mio::base_dir(); + // check that the path exists + EXPECT_TRUE(std::filesystem::exists(base_dir)); + EXPECT_TRUE(std::filesystem::is_directory(base_dir)); + // check that the path is correct, by sampling some fixed paths from project files + EXPECT_TRUE(std::filesystem::exists(base_dir / "cpp" / "memilio")); + EXPECT_TRUE(std::filesystem::exists(base_dir / "pycode" / "memilio-simulation")); +} + +TEST(TestDirectories, data_dir) +{ + auto data_dir = mio::data_dir(); + // check that the path or its parent exists + EXPECT_TRUE(std::filesystem::is_directory(data_dir) || + (data_dir.has_parent_path() && std::filesystem::is_directory(data_dir.parent_path()))); + // no assumptions on contents +} + +TEST(TestDirectories, example_results_dir) +{ + auto ex_name = ".__hidden_test_directory__"; + auto exrs_dir = mio::example_results_dir(ex_name); + // check that the path does *not* exist, as examples are expected to create their own directories + EXPECT_FALSE(std::filesystem::exists(exrs_dir)); + // check composition + EXPECT_EQ(mio::base_dir() / "example_results" / ex_name, exrs_dir); +} diff --git a/cpp/tests/test_io_framework.cpp b/cpp/tests/test_io_framework.cpp index 267da0e80b..055a02f9d1 100644 --- a/cpp/tests/test_io_framework.cpp +++ b/cpp/tests/test_io_framework.cpp @@ -19,7 +19,12 @@ */ #include "memilio/io/io.h" #include "matchers.h" +#include "memilio/utils/logging.h" +#include "temp_file_register.h" +#include "utils.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include namespace iotest { @@ -102,3 +107,67 @@ TEST(IO, apply_with_validation_error) EXPECT_EQ(r, mio::IOResult(mio::failure(mio::StatusCode::InvalidValue, ""))); EXPECT_EQ(io.status, mio::IOStatus(mio::StatusCode::InvalidValue, "")); } + +TEST(IO, create_directory) +{ + mio::RedirectLogger logger(mio::LogLevel::info); + mio::IOResult result = mio::success(true); + TempFileRegister tmp; + std::string model = "%%%%-%%%%-%%%%-%%%%"; + const auto dir = std::filesystem::path(tmp.get_unique_path(model)); + const auto dangling_subdir = std::filesystem::path(tmp.get_unique_path(model + "/" + model)); + + logger.capture(); + // case group: do not create parents + // case: create new directory; expect success, value true + result = mio::create_directory(dir); + ASSERT_THAT(print_wrap(result), IsSuccess()); + EXPECT_TRUE(result.value()); + EXPECT_THAT(logger.read(), ::testing::HasSubstr("was created")); + // case: recreate existing directory; expect success, value false + result = mio::create_directory(dir); + ASSERT_THAT(print_wrap(result), IsSuccess()); + EXPECT_FALSE(result.value()); + EXPECT_THAT(logger.read(), ::testing::HasSubstr("already exists")); + // case: create a directory in a non-existent subdirectory; expect failure + result = mio::create_directory(dangling_subdir); + ASSERT_TRUE(result.has_failure()); // error code may be OS dependant, so we don't use IsFailure + EXPECT_TRUE(logger.read().empty()); + EXPECT_THAT(result.error().message(), ::testing::HasSubstr("Failed to create")); + + // case group: create parents + // case: create new directory; expect success, value true + result = mio::create_directory(dangling_subdir, true); + ASSERT_THAT(print_wrap(result), IsSuccess()); + EXPECT_TRUE(result.value()); + EXPECT_THAT(logger.read(), ::testing::HasSubstr("was created")); + // case: recreate existing directory; expect success, value false + result = mio::create_directory(dangling_subdir, true); + ASSERT_THAT(print_wrap(result), IsSuccess()); + EXPECT_FALSE(result.value()); + EXPECT_THAT(logger.read(), ::testing::HasSubstr("already exists")); + // omit case for failure, as that would require file system problems (e.g. insufficient space or permissions) + + logger.release(); +} + +TEST(IO, create_directories_or_exit) +{ + mio::set_death_test_mode(); + TempFileRegister tmp; + std::string model = "%%%%-%%%%-%%%%-%%%%"; + const auto dangling_subdir = std::filesystem::path(tmp.get_unique_path(model + "/" + model)); + + // test cases here do not cover all create_directory cases + // case: create new dangling directory without creating parents; expect exit + EXPECT_DEATH( + { + mio::LogLevelOverride llo(mio::LogLevel::off); + mio::create_directories_or_exit(dangling_subdir, false); + }, + ::testing::IsEmpty()); + // case: create new dangling directory, now with creating parents: expect directory to be created + std::filesystem::path result_path = mio::create_directories_or_exit(dangling_subdir, true); + EXPECT_TRUE(result_path.is_absolute()); + EXPECT_TRUE(std::filesystem::exists(result_path)); +} diff --git a/cpp/tests/test_utils.cpp b/cpp/tests/test_utils.cpp index 040c07e98d..be4616f7eb 100644 --- a/cpp/tests/test_utils.cpp +++ b/cpp/tests/test_utils.cpp @@ -17,7 +17,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "memilio/io/directories.h" #include "memilio/utils/index.h" #include "memilio/utils/index_range.h" #include "memilio/utils/logging.h" @@ -28,8 +27,6 @@ #include #include -#include - template struct CategoryTag : public mio::Index> { CategoryTag(size_t value) @@ -190,13 +187,3 @@ TEST(TestUtils, LogLevelOverride) logger.release(); } - -TEST(TestUtils, base_dir) -{ - auto base_dir = std::filesystem::path(mio::base_dir()); - // check that the path exists - EXPECT_TRUE(std::filesystem::exists(base_dir)); - // check that the path is correct, by sampling some fixed paths from project files - EXPECT_TRUE(std::filesystem::exists(base_dir / "cpp" / "memilio")); - EXPECT_TRUE(std::filesystem::exists(base_dir / "pycode" / "memilio-epidata")); -} From ea84545577e2f0652238ceb61e01118568609e12 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:29:42 +0200 Subject: [PATCH 18/21] [ci skip] update version strings --- cpp/README.md | 2 +- docs/source/cpp/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/README.md b/cpp/README.md index 2aee390507..9583300592 100644 --- a/cpp/README.md +++ b/cpp/README.md @@ -28,7 +28,7 @@ The following table lists the dependencies that are used. Most of them are requi | Library | Version | Required | Bundled | Notes | |---------|----------|----------|-----------------------|-------| -| spdlog | 1.15.0 | Yes | Yes (git repo) | https://github.com/gabime/spdlog | +| spdlog | 1.17.0 | Yes | Yes (git repo) | https://github.com/gabime/spdlog | | Eigen | 3.4.0 | Yes | Yes (git repo) | http://gitlab.com/libeigen/eigen | | Boost | 1.84.0 | Yes | Yes (git repo) | https://github.com/boostorg/boost | | JsonCpp | 1.9.6 | No | Yes (git repo) | https://github.com/open-source-parsers/jsoncpp | diff --git a/docs/source/cpp/installation.rst b/docs/source/cpp/installation.rst index e4c917bca8..2753d433e6 100644 --- a/docs/source/cpp/installation.rst +++ b/docs/source/cpp/installation.rst @@ -52,7 +52,7 @@ instead. Version compatibility needs to be ensured by the user, the version we c - Bundled - Notes * - spdlog - - 1.15.0 + - 1.17.0 - Yes - Yes (git repo) - https://github.com/gabime/spdlog From caa5e4a0ceeef164c9665cfbf3928019a221256a Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:45:16 +0200 Subject: [PATCH 19/21] msvc workarounds 6 --- cpp/tests/test_json_serializer.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cpp/tests/test_json_serializer.cpp b/cpp/tests/test_json_serializer.cpp index 81afb46d02..e41d8c1238 100644 --- a/cpp/tests/test_json_serializer.cpp +++ b/cpp/tests/test_json_serializer.cpp @@ -196,11 +196,19 @@ TEST(TestJsonSerializer, string) check_serialization_of_basic_type(std::string("Hello, World!")); } +// workaround for some msvc versions not supporting cast from path to string +struct PathToStringConverter : public std::string { + PathToStringConverter(const std::filesystem::path& p) + : std::string(p.generic_string()) + { + } +}; + TEST(TestJsonSerializer, path) { - check_serialization_of_basic_type(std::filesystem::path()); - check_serialization_of_basic_type(std::filesystem::path("Hello/World")); - check_serialization_of_basic_type(std::filesystem::path("Hello\\World")); + check_serialization_of_basic_type({}); + check_serialization_of_basic_type({"Hello/World"}); + check_serialization_of_basic_type({"Hello\\World"}); } namespace jsontest From 5a16a0e65bb3940aea2b7a012740cb640375c5a7 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:33:27 +0200 Subject: [PATCH 20/21] remove path serialization due to msvc bug --- cpp/memilio/io/io.h | 31 ------------------------------ cpp/tests/test_json_serializer.cpp | 15 --------------- 2 files changed, 46 deletions(-) diff --git a/cpp/memilio/io/io.h b/cpp/memilio/io/io.h index bcfbb1c23d..79e91332a7 100644 --- a/cpp/memilio/io/io.h +++ b/cpp/memilio/io/io.h @@ -609,37 +609,6 @@ IOResult> deserialize_internal(IOContext& io, Tag> tag) return details::deserialize_tuple_element(obj, tag); } -/** - * @brief Serialize an std::filesystem::path. - * @tparam IOContext A type that models the IOContext concept. - * @param io A reference to an IOContext. - * @param path An instance of std::filesystem::path. - */ -template -void serialize_internal(IOContext& io, const std::filesystem::path& path) -{ - serialize_internal(io, path.generic_string()); -} - -/** - * @brief Deerialize an std::filesystem::path. - * @tparam IOContext A type that models the IOContext concept. - * @param io A reference to an IOContext. - * @param path An instance of std::filesystem::path. - * @return The path if successful, an error otherwise. - */ -template -IOResult deserialize_internal(IOContext& io, Tag) -{ - auto result = deserialize_internal(io, Tag{}); - if (result) { - return success(result.value()); - } - else { - return failure(result.error()); - } -} - /** * serialize an Eigen matrix expression. * @tparam IOContext a type that models the IOContext concept. diff --git a/cpp/tests/test_json_serializer.cpp b/cpp/tests/test_json_serializer.cpp index e41d8c1238..006647a1b3 100644 --- a/cpp/tests/test_json_serializer.cpp +++ b/cpp/tests/test_json_serializer.cpp @@ -196,21 +196,6 @@ TEST(TestJsonSerializer, string) check_serialization_of_basic_type(std::string("Hello, World!")); } -// workaround for some msvc versions not supporting cast from path to string -struct PathToStringConverter : public std::string { - PathToStringConverter(const std::filesystem::path& p) - : std::string(p.generic_string()) - { - } -}; - -TEST(TestJsonSerializer, path) -{ - check_serialization_of_basic_type({}); - check_serialization_of_basic_type({"Hello/World"}); - check_serialization_of_basic_type({"Hello\\World"}); -} - namespace jsontest { enum class E : int From e387735f20ec3091e4f774ec725928880d05ac6c Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Thu, 23 Apr 2026 09:30:34 +0200 Subject: [PATCH 21/21] shorten create_directory using ternaries --- cpp/memilio/io/io.cpp | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/cpp/memilio/io/io.cpp b/cpp/memilio/io/io.cpp index dfd8cacefa..1a50a553e5 100644 --- a/cpp/memilio/io/io.cpp +++ b/cpp/memilio/io/io.cpp @@ -48,26 +48,15 @@ IOResult create_directory(const std::filesystem::path& rel_path, std::stri IOResult create_directory(const std::filesystem::path& rel_path, bool create_parents) { std::error_code ec; - bool created; - - if (create_parents) { - created = std::filesystem::create_directories(rel_path, ec); - } - else { - created = std::filesystem::create_directory(rel_path, ec); - } + bool created = create_parents ? std::filesystem::create_directories(rel_path, ec) + : std::filesystem::create_directory(rel_path, ec); if (ec) { const std::string with_parents = create_parents ? " (with parents)" : ""; return failure(ec, "Failed to create directory " + rel_path.string() + with_parents); } - if (created) { - log_info("Directory '{:s}' was created.", rel_path); - } - else { - log_info("Directory '{:s}' already exists.", rel_path); - } + log_info("Directory '{}' {}.", rel_path, created ? "was created" : "already exists"); return success(created); }