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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] 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/34] [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/34] 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/34] 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/34] 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); } From c2b5467c0fd7589748a28703be62d47e482faced Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 6 May 2026 11:07:36 +0200 Subject: [PATCH 22/34] add include for apple build --- cpp/tests/load_test_data.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp/tests/load_test_data.h b/cpp/tests/load_test_data.h index 09572d530f..87f680c5b7 100644 --- a/cpp/tests/load_test_data.h +++ b/cpp/tests/load_test_data.h @@ -19,10 +19,11 @@ */ #include "test_data_dir.h" #include "memilio/utils/stl_util.h" + +#include #include -#include +#include #include -#include template std::string get_test_data_file_path(String&& filename) From 3f7722aeff81d17f119046b0fd6af8ba69b4d184 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 11:35:46 +0200 Subject: [PATCH 23/34] clean up directories.h implementation --- cpp/memilio/CMakeLists.txt | 1 + cpp/memilio/io/directories.cpp | 40 ++++++++++++++++++++++++++++++++++ cpp/memilio/io/directories.h | 25 +++++++-------------- 3 files changed, 49 insertions(+), 17 deletions(-) create mode 100644 cpp/memilio/io/directories.cpp diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index e7d9018849..85be088d64 100644 --- a/cpp/memilio/CMakeLists.txt +++ b/cpp/memilio/CMakeLists.txt @@ -41,6 +41,7 @@ add_library(memilio io/cli.h io/default_serialize.h io/default_serialize.cpp + io/directories.cpp io/directories.h io/epi_data.h io/epi_data.cpp diff --git a/cpp/memilio/io/directories.cpp b/cpp/memilio/io/directories.cpp new file mode 100644 index 0000000000..c13a059be2 --- /dev/null +++ b/cpp/memilio/io/directories.cpp @@ -0,0 +1,40 @@ +/* +* 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" + +namespace mio +{ + +std::filesystem::path base_dir() +{ + return details::MEMILIO_BASE_DIR; +} + +[[maybe_unused]] std::filesystem::path data_dir() +{ + return details::MEMILIO_DATA_DIR; +} + +[[maybe_unused]] std::filesystem::path example_results_dir(const std::string& example_name) +{ + return base_dir() / "example_results" / example_name; +} + +} // namespace mio diff --git a/cpp/memilio/io/directories.h b/cpp/memilio/io/directories.h index 38ff146a45..b668823f01 100644 --- a/cpp/memilio/io/directories.h +++ b/cpp/memilio/io/directories.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef MIO_UTILS_DIRECTORIES_H -#define MIO_UTILS_DIRECTORIES_H +#ifndef MIO_IO_DIRECTORIES_H +#define MIO_IO_DIRECTORIES_H #include "memilio/config.h" // IWYU pragma: keep @@ -31,29 +31,20 @@ namespace mio /** * @brief Returns the absolute path to the project directory. */ -const static std::filesystem::path base_dir() -{ - return details::MEMILIO_BASE_DIR; -} +std::filesystem::path base_dir(); /** * @brief Returns the absolute path to the project directory. */ -[[maybe_unused]] const static std::filesystem::path data_dir() -{ - return details::MEMILIO_DATA_DIR; -} +[[maybe_unused]] std::filesystem::path data_dir(); /** * @brief Returns the absolute path to a common ouput directory for the code examples. + * The directory lies in base_dir() and has + * @param[in] example_name Name of the example (e.g. the filename without .cpp). */ -[[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::filesystem::path dir = base_dir() / "example_results" / example_name; - return dir; -} +[[maybe_unused]] std::filesystem::path example_results_dir(const std::string& example_name); } // namespace mio -#endif // MIO_UTILS_DIRECTORIES_H +#endif // MIO_IO_DIRECTORIES_H From 7ab3b40e2ff65817b342a94ac1582d84eeaebb70 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 11:36:10 +0200 Subject: [PATCH 24/34] fixes from review --- cpp/examples/abm_minimal.cpp | 5 +---- cpp/examples/ode_secir_parameter_study.cpp | 2 +- docs/source/cpp/mobility_based_abm.rst | 8 ++++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 4934fd34e1..d5fbad0413 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -162,10 +162,7 @@ int main() // Run the simulation until tmax with the history object. sim.advance(tmax, historyTimeSeries); - // The results are written into the file "abm_minimal.txt" as a table with 9 columns. - // 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 + // Write results to a file. Also print the filepath to make it easier to find 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()) diff --git a/cpp/examples/ode_secir_parameter_study.cpp b/cpp/examples/ode_secir_parameter_study.cpp index b42031de99..7e582f1d3f 100644 --- a/cpp/examples/ode_secir_parameter_study.cpp +++ b/cpp/examples/ode_secir_parameter_study.cpp @@ -39,7 +39,7 @@ mio::IOResult write_single_run_result(const size_t run, const mio::osecir: { std::string abs_path; BOOST_OUTCOME_TRY(auto&& created, - mio::create_directory(mio::example_results_dir("ode_secir_parameter_study"), abs_path)); + mio::create_directory(mio::example_results_dir("ode_secir_parameter_study"), abs_path, true)); if (run == 0) { std::cout << "Results are stored in " << abs_path << '\n'; diff --git a/docs/source/cpp/mobility_based_abm.rst b/docs/source/cpp/mobility_based_abm.rst index 74ac617933..00f00dbe91 100644 --- a/docs/source/cpp/mobility_based_abm.rst +++ b/docs/source/cpp/mobility_based_abm.rst @@ -344,7 +344,7 @@ Finally, for example, we can print the data to a text file: .. code-block:: cpp - std::ofstream outfile("abm_minimal.txt"); - std::get<0>(log).print_table({"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4, outfile); - std::cout << "Results written to abm_minimal.txt" << std::endl; - + auto outpath = mio::create_directories_or_exit(mio::example_results_dir("abm_minimal")) / "history.txt"; + std::ofstream outfile(outpath); + std::get<0>(log).print_table(outfile, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4); + std::cout << "Results written to " << outpath << std::endl; From 1d3ee5c224eda172d155b0a54f6ece37c17848da Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 13:54:23 +0200 Subject: [PATCH 25/34] bump python version for CI --- .github/actions/test-py/action.yml | 2 +- .github/actions/test-pylint/action.yml | 4 ++-- .github/workflows/epidata_main.yml | 10 +++++----- .github/workflows/main.yml | 8 ++++---- docs/source/getting_started.rst | 2 +- pycode/README.rst | 2 +- pyproject.toml | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/actions/test-py/action.yml b/.github/actions/test-py/action.yml index 5eeb4d14a0..5d766fc7ba 100644 --- a/.github/actions/test-py/action.yml +++ b/.github/actions/test-py/action.yml @@ -7,7 +7,7 @@ inputs: version: description: "Python version to use for the test." required: false - default: "3.8" + default: "3.9" coverage: description: "Generate coverage report from running the tests, ON or OFF, default OFF." required: false diff --git a/.github/actions/test-pylint/action.yml b/.github/actions/test-pylint/action.yml index af05c50c38..1a9fff1acd 100644 --- a/.github/actions/test-pylint/action.yml +++ b/.github/actions/test-pylint/action.yml @@ -15,10 +15,10 @@ runs: python -m pip install --upgrade pip # Pylint runs against the prebuilt wheels in pycode/wheelhouse/*cp312*. # So, use current latest supported Python version. - - name: Set up Python 3.12 + - name: Set up Python 3.14 uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.14" - name: Download Python Wheels uses: actions/download-artifact@v7 with: diff --git a/.github/workflows/epidata_main.yml b/.github/workflows/epidata_main.yml index 34be381203..475cf2ef67 100644 --- a/.github/workflows/epidata_main.yml +++ b/.github/workflows/epidata_main.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: 3.14 - uses: pre-commit/action@v3.0.1 build-py-epidata: @@ -51,7 +51,7 @@ jobs: needs: build-py-epidata strategy: matrix: - version: ["3.8", "3.12"] + version: ["3.9", "3.14"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -66,7 +66,7 @@ jobs: needs: [build-py-plot, build-py-epidata] strategy: matrix: - version: ["3.8", "3.12"] + version: ["3.9", "3.14"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -123,10 +123,10 @@ jobs: with: name: python-wheels-epidata path: pycode/wheelhouse - - name: Set up Python 3.12 + - name: Set up Python 3.14 uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: 3.14 - name: Install Python Wheels run: | for pkg in `ls pycode/wheelhouse/*cp312*.whl`; do python -m pip install $pkg; done # packages that contain native extensions are version specific diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c914695f5f..5823891391 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: 3.12 + python-version: 3.14 - uses: pre-commit/action@v3.0.1 build-cpp-gcc_clang: @@ -339,7 +339,7 @@ jobs: if: github.event.pull_request.draft == false strategy: matrix: - version: ["3.8", "3.12"] + version: ["3.9", "3.14"] needs: build-py-generation runs-on: ubuntu-latest steps: @@ -353,7 +353,7 @@ jobs: needs: build-py-simulation strategy: matrix: - version: ["3.8", "3.12"] + version: ["3.9", "3.14"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -366,7 +366,7 @@ jobs: needs: [build-py-surrogatemodel, build-py-simulation] strategy: matrix: - version: ["3.8", "3.12"] + version: ["3.9", "3.14"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index c9d9b74648..151f2fb172 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -258,7 +258,7 @@ Before you can install MEmilio, you need to install some common development tool * **Python:** Required for the Python packages. - * MEmilio is tested daily with Python 3.8 and 3.12. While other versions may also work, we recommend using the latest release of either of these. You can download it from the official website `python.org `__. + * MEmilio is tested daily with Python 3.9 and 3.14. While other versions may also work, we recommend using the latest release of either of these. You can download it from the official website `python.org `__. * **C++ Compiler and CMake:** diff --git a/pycode/README.rst b/pycode/README.rst index bc304b43fe..07debd1da9 100644 --- a/pycode/README.rst +++ b/pycode/README.rst @@ -25,4 +25,4 @@ We recommend to use a virtual python environment to avoid dependency conflicts w Refer to the `Python documentation `_ for more information about virtual environments. -The packages are tested for Python 3.8 - 3.12. +The packages are tested for Python 3.9 - 3.14. diff --git a/pyproject.toml b/pyproject.toml index 15fa1202dd..1e0e3ff415 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,4 +67,4 @@ max-line-length = 79 [tool.pyright] include = ["pycode"] -exclude = ["pycode/memilio-simulation"] \ No newline at end of file +exclude = ["pycode/memilio-simulation"] From b6bf68c6eec00eaf68cff8b10c2eaa4f8614c043 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 14:02:25 +0200 Subject: [PATCH 26/34] bump pre-commit-hooks and match cpython versions --- .github/actions/build-py/action.yml | 16 ++++++++-------- .github/actions/test-pylint/action.yml | 4 ++-- .github/workflows/epidata_main.yml | 2 +- .pre-commit-config.yaml | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/actions/build-py/action.yml b/.github/actions/build-py/action.yml index 418b3d0380..c8062a97a2 100644 --- a/.github/actions/build-py/action.yml +++ b/.github/actions/build-py/action.yml @@ -28,17 +28,17 @@ runs: cd pycode/memilio-${{ inputs.package }}/ # else stay in root directory fi - /opt/python/cp38-cp38/bin/python -m pip install --upgrade pip setuptools wheel - /opt/python/cp312-cp312/bin/python -m pip install --upgrade pip setuptools wheel - /opt/python/cp38-cp38/bin/python -m pip install scikit-build scikit-build-core - /opt/python/cp312-cp312/bin/python -m pip install scikit-build scikit-build-core + /opt/python/cp39-cp39/bin/python -m pip install --upgrade pip setuptools wheel + /opt/python/cp314-cp314/bin/python -m pip install --upgrade pip setuptools wheel + /opt/python/cp39-cp39/bin/python -m pip install scikit-build scikit-build-core + /opt/python/cp314-cp314/bin/python -m pip install scikit-build scikit-build-core # Install setuptools-scm only for memilio-simulation if [ "${{ inputs.package }}" == "simulation" ]; then - /opt/python/cp38-cp38/bin/python -m pip install setuptools-scm - /opt/python/cp312-cp312/bin/python -m pip install setuptools-scm + /opt/python/cp39-cp39/bin/python -m pip install setuptools-scm + /opt/python/cp314-cp314/bin/python -m pip install setuptools-scm fi - /opt/python/cp38-cp38/bin/python -m build --no-isolation --wheel - /opt/python/cp312-cp312/bin/python -m build --no-isolation --wheel + /opt/python/cp39-cp39/bin/python -m build --no-isolation --wheel + /opt/python/cp314-cp314/bin/python -m build --no-isolation --wheel # Exclude memilio-generation, because its a pure python package, cmake is only used in the build process to retrieve data from cpp if [[ -f "CMakeLists.txt" ]] && [ "${{ inputs.package }}" != "generation" ]; then # includes native dependencies in the wheel diff --git a/.github/actions/test-pylint/action.yml b/.github/actions/test-pylint/action.yml index 1a9fff1acd..b1129d36d6 100644 --- a/.github/actions/test-pylint/action.yml +++ b/.github/actions/test-pylint/action.yml @@ -13,7 +13,7 @@ runs: sudo apt-get -qq update sudo apt-get -qq -y install python3-pip gnupg python -m pip install --upgrade pip - # Pylint runs against the prebuilt wheels in pycode/wheelhouse/*cp312*. + # Pylint runs against the prebuilt wheels in pycode/wheelhouse/*cp314*. # So, use current latest supported Python version. - name: Set up Python 3.14 uses: actions/setup-python@v6 @@ -28,7 +28,7 @@ runs: shell: bash run: | shopt -s nullglob - for pkg in pycode/wheelhouse/*cp312*.whl; do python -m pip install "$pkg"; done # packages that contain native extensions are version specific + for pkg in pycode/wheelhouse/*cp314*.whl; do python -m pip install "$pkg"; done # packages that contain native extensions are version specific for pkg in pycode/wheelhouse/*py3*.whl; do python -m pip install "$pkg"; done # pure python packages are not version specific python -m pip install --upgrade-strategy only-if-needed --prefer-binary --find-links pycode/wheelhouse "memilio-${{ inputs.package }}[dev]" - name: Run pylint diff --git a/.github/workflows/epidata_main.yml b/.github/workflows/epidata_main.yml index 475cf2ef67..80124b195a 100644 --- a/.github/workflows/epidata_main.yml +++ b/.github/workflows/epidata_main.yml @@ -129,7 +129,7 @@ jobs: python-version: 3.14 - name: Install Python Wheels run: | - for pkg in `ls pycode/wheelhouse/*cp312*.whl`; do python -m pip install $pkg; done # packages that contain native extensions are version specific + for pkg in `ls pycode/wheelhouse/*cp314*.whl`; do python -m pip install $pkg; done # packages that contain native extensions are version specific for pkg in `ls pycode/wheelhouse/*py3*.whl`; do python -m pip install $pkg; done # pure python packages are not version specific - name: Download Data run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58c80bd6c6..00a110e368 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: ^(docs|cpp)/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v6.0.0 hooks: - id: check-merge-conflict - id: check-yaml From 5442935ded508a63e875261169c59545cef10f53 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 14:20:01 +0200 Subject: [PATCH 27/34] bump pyupgrade --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00a110e368..9a5376ed1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,10 +6,10 @@ repos: - id: check-merge-conflict - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.21.2 hooks: - id: pyupgrade - args: [--py37-plus] + args: [--py39-plus] - repo: https://github.com/hhatto/autopep8 rev: v2.3.2 hooks: From f478f1adec90edf1443677ca11a431916c812b2d Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 15:09:37 +0200 Subject: [PATCH 28/34] fully upgrade to python 3.9+, apply pyupgrade --- docs/source/getting_started.rst | 2 +- docs/source/python/m-simulation.rst | 2 +- pycode/memilio-epidata/memilio/epidata/getCaseData.py | 2 +- .../memilio-epidata/memilio/epidata/getContactData.py | 3 ++- pycode/memilio-epidata/memilio/epidata/getDIVIData.py | 4 ++-- .../memilio/epidata/getVaccinationData.py | 2 +- pycode/memilio-epidata/pyproject.toml | 2 +- .../memilio/generation/intermediate_representation.py | 2 +- .../memilio-generation/memilio/generation/utility.py | 11 +++-------- .../memilio-generation/memilio/tools/example_oseir.py | 7 +------ pycode/memilio-generation/pyproject.toml | 5 ++--- .../memilio-generation/tests/test_oseir_generation.py | 7 +------ pycode/memilio-plot/pyproject.toml | 2 +- pycode/memilio-simulation/README.md | 2 +- pycode/memilio-simulation/tools/generate_stubs.py | 2 +- .../memilio/surrogatemodel/GNN/evaluate_and_train.py | 11 ++++++----- pycode/memilio-surrogatemodel/pyproject.toml | 2 +- pyproject.toml | 4 ++-- 18 files changed, 29 insertions(+), 43 deletions(-) diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index 151f2fb172..b9772dce62 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -313,7 +313,7 @@ If you just want to run simulations with the latest released version, install th pip install memilio-simulation -This requires no C++ compiler or CMake. Pre-built wheels are provided for Linux and Windows on Python 3.8 to 3.13. +This requires no C++ compiler or CMake. Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.14. **Option A.2: Install from source (for the latest development version)** diff --git a/docs/source/python/m-simulation.rst b/docs/source/python/m-simulation.rst index f47a6e0045..874c6b9a7b 100644 --- a/docs/source/python/m-simulation.rst +++ b/docs/source/python/m-simulation.rst @@ -12,7 +12,7 @@ The ``memilio-simulation`` package can be installed in two ways depending on you **Option 1: Install from PyPI (Recommended - no C++ compiler required)** -Pre-built wheels are provided for Linux and Windows on Python 3.8 to 3.13. +Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.14. .. code-block:: console diff --git a/pycode/memilio-epidata/memilio/epidata/getCaseData.py b/pycode/memilio-epidata/memilio/epidata/getCaseData.py index 788f25a083..06a82e9058 100644 --- a/pycode/memilio-epidata/memilio/epidata/getCaseData.py +++ b/pycode/memilio-epidata/memilio/epidata/getCaseData.py @@ -455,7 +455,7 @@ def get_case_data(read_data: bool = dd.defaultDict['read_data'], rep_date: bool = dd.defaultDict['rep_date'], files: str or list = 'All', **kwargs - ) -> Dict: + ) -> dict: """ Wrapper function that downloads the case data and provides different kind of structured data into json files. The data is read either from the internet or from a json file (CaseDataFull.json), stored in an earlier run. diff --git a/pycode/memilio-epidata/memilio/epidata/getContactData.py b/pycode/memilio-epidata/memilio/epidata/getContactData.py index 297f75ed70..186f02e989 100644 --- a/pycode/memilio-epidata/memilio/epidata/getContactData.py +++ b/pycode/memilio-epidata/memilio/epidata/getContactData.py @@ -32,7 +32,8 @@ import io import os import zipfile -from typing import Iterable, Optional +from typing import Optional +from collections.abc import Iterable import numpy as np import pandas as pd diff --git a/pycode/memilio-epidata/memilio/epidata/getDIVIData.py b/pycode/memilio-epidata/memilio/epidata/getDIVIData.py index 44d46fb7da..57fb79038d 100644 --- a/pycode/memilio-epidata/memilio/epidata/getDIVIData.py +++ b/pycode/memilio-epidata/memilio/epidata/getDIVIData.py @@ -99,7 +99,7 @@ def preprocess_divi_data(df_raw: pd.DataFrame, end_date: date = dd.defaultDict['end_date'], impute_dates: bool = dd.defaultDict['impute_dates'], moving_average: int = dd.defaultDict['moving_average'], - ) -> Tuple[pd.DataFrame, pd.DataFrame]: + ) -> tuple[pd.DataFrame, pd.DataFrame]: """ Processing of the downloaded data * the columns are renamed to English and the state and county names are added. @@ -178,7 +178,7 @@ def write_divi_data(df: pd.DataFrame, file_format: str = dd.defaultDict['file_format'], impute_dates: bool = dd.defaultDict['impute_dates'], moving_average: int = dd.defaultDict['moving_average'], - ) -> Dict: + ) -> dict: """ Write the divi data into json files Three kinds of structuring of the data are done. diff --git a/pycode/memilio-epidata/memilio/epidata/getVaccinationData.py b/pycode/memilio-epidata/memilio/epidata/getVaccinationData.py index 58c11fb821..9f66e92dc5 100644 --- a/pycode/memilio-epidata/memilio/epidata/getVaccinationData.py +++ b/pycode/memilio-epidata/memilio/epidata/getVaccinationData.py @@ -897,7 +897,7 @@ def write_vaccination_data(dict_data: dict, file_format: str = dd.defaultDict['file_format'], impute_dates: bool = True, moving_average: int = dd.defaultDict['moving_average'], - ) -> None or Tuple: + ) -> None or tuple: """ Writes the vaccination data The data is exported in three different ways: - all_county_vacc: Resolved per county by grouping all original age groups (05-11, 12-17, 18-59, 60+) diff --git a/pycode/memilio-epidata/pyproject.toml b/pycode/memilio-epidata/pyproject.toml index 55ebfbaffa..b85ea8c4f4 100644 --- a/pycode/memilio-epidata/pyproject.toml +++ b/pycode/memilio-epidata/pyproject.toml @@ -7,7 +7,7 @@ name = "memilio-epidata" version = "1.0.0" description = "Part of MEmilio project, reads epidemiological data from different official and unofficial sources." readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "Apache-2.0" } authors = [{ name = "MEmilio Team" }] maintainers = [ diff --git a/pycode/memilio-generation/memilio/generation/intermediate_representation.py b/pycode/memilio-generation/memilio/generation/intermediate_representation.py index 3637a66146..b582c4584b 100644 --- a/pycode/memilio-generation/memilio/generation/intermediate_representation.py +++ b/pycode/memilio-generation/memilio/generation/intermediate_representation.py @@ -77,7 +77,7 @@ def check_model_base(self: Self) -> None: else: raise IndexError("model_base is empty. No base classes found.") - def check_complete_data(self: Self, optional: Dict + def check_complete_data(self: Self, optional: dict [str, Union[str, bool]]) -> None: """Check for missing data in the IntermediateRepresentation. Called by the Scanner as last step of the data extraction. diff --git a/pycode/memilio-generation/memilio/generation/utility.py b/pycode/memilio-generation/memilio/generation/utility.py index dc3d00e846..ce46089e8b 100644 --- a/pycode/memilio-generation/memilio/generation/utility.py +++ b/pycode/memilio-generation/memilio/generation/utility.py @@ -28,12 +28,7 @@ import sys -if sys.version_info >= (3, 9): - # For python 3.9 and newer - import importlib.resources as importlib_resources -else: - # For older python versions - import importlib_resources +import importlib.resources as importlib_resources from clang.cindex import Config, Cursor, Type @@ -88,7 +83,7 @@ def try_get_compilation_database_path(skbuild_path_to_database: str) -> str: return dirname -def get_base_class(base_type: Type) -> List[Any]: +def get_base_class(base_type: Type) -> list[Any]: """Retrieve the base class. Example for base_type: CompartmentalModel. @@ -101,7 +96,7 @@ def get_base_class(base_type: Type) -> List[Any]: return result -def get_base_class_string(base_type: Type) -> List[Any]: +def get_base_class_string(base_type: Type) -> list[Any]: """Retrieve the spelling of the base class. Example for base_type.spelling: CompartmentalModel, Parameters>. diff --git a/pycode/memilio-generation/memilio/tools/example_oseir.py b/pycode/memilio-generation/memilio/tools/example_oseir.py index bc1fccc84e..f3b7b8c30a 100644 --- a/pycode/memilio-generation/memilio/tools/example_oseir.py +++ b/pycode/memilio-generation/memilio/tools/example_oseir.py @@ -24,12 +24,7 @@ import sys import os -if sys.version_info >= (3, 9): - # For python 3.9 and newer - import importlib.resources as importlib_resources -else: - # For older python versions - import importlib_resources +import importlib.resources as importlib_resources from memilio.generation import Generator, Scanner, ScannerConfig, AST, ast_handler from memilio.generation.graph_visualization import Visualization diff --git a/pycode/memilio-generation/pyproject.toml b/pycode/memilio-generation/pyproject.toml index ea206d2557..827a478ec6 100644 --- a/pycode/memilio-generation/pyproject.toml +++ b/pycode/memilio-generation/pyproject.toml @@ -11,7 +11,7 @@ name = "memilio-generation" version = "0.1.0" description = "Part of MEmilio project, automatic generation of model specific python bindings." readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "Apache-2.0" } authors = [{ name = "MEmilio Team" }] maintainers = [ @@ -21,8 +21,7 @@ dependencies = [ "libclang==18.1.1", "dataclasses", "dataclasses_json", - "graphviz", - "importlib-resources>=1.1.0; python_version < '3.9'" + "graphviz" ] [project.optional-dependencies] diff --git a/pycode/memilio-generation/tests/test_oseir_generation.py b/pycode/memilio-generation/tests/test_oseir_generation.py index caf77f2cc3..77b4b4cccf 100644 --- a/pycode/memilio-generation/tests/test_oseir_generation.py +++ b/pycode/memilio-generation/tests/test_oseir_generation.py @@ -27,12 +27,7 @@ from memilio.generation import Generator, Scanner, ScannerConfig, AST -if sys.version_info >= (3, 9): - # For python 3.9 and newer - import importlib.resources as importlib_resources -else: - # For older python versions - import importlib_resources +import importlib.resources as importlib_resources class TestOseirGeneration(unittest.TestCase): diff --git a/pycode/memilio-plot/pyproject.toml b/pycode/memilio-plot/pyproject.toml index 3f8d15d540..9838362755 100644 --- a/pycode/memilio-plot/pyproject.toml +++ b/pycode/memilio-plot/pyproject.toml @@ -7,7 +7,7 @@ name = "memilio-plot" version = "1.0.0" description = "Part of MEmilio project, plots data to maps or visualizes simulation curves." readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "Apache-2.0" } authors = [{ name = "MEmilio Team" }] maintainers = [ diff --git a/pycode/memilio-simulation/README.md b/pycode/memilio-simulation/README.md index d521a36e04..88115fadc5 100644 --- a/pycode/memilio-simulation/README.md +++ b/pycode/memilio-simulation/README.md @@ -6,7 +6,7 @@ This package contains Python bindings for the MEmilio C++ library. It enables se ### Option 1: Install from PyPI (Recommended) -Pre-built wheels are provided for Linux and Windows on Python 3.8 to 3.13. +Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.14. ```bash pip install memilio-simulation diff --git a/pycode/memilio-simulation/tools/generate_stubs.py b/pycode/memilio-simulation/tools/generate_stubs.py index 7f5c776192..fc9176c8b5 100644 --- a/pycode/memilio-simulation/tools/generate_stubs.py +++ b/pycode/memilio-simulation/tools/generate_stubs.py @@ -31,7 +31,7 @@ name = "memilio-stubs" version = "0.1" description = "Stubs for the memilio.simulation package." -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [] [tool.setuptools.packages.find] diff --git a/pycode/memilio-surrogatemodel/memilio/surrogatemodel/GNN/evaluate_and_train.py b/pycode/memilio-surrogatemodel/memilio/surrogatemodel/GNN/evaluate_and_train.py index 451c0a150e..7aed2dc70a 100644 --- a/pycode/memilio-surrogatemodel/memilio/surrogatemodel/GNN/evaluate_and_train.py +++ b/pycode/memilio-surrogatemodel/memilio/surrogatemodel/GNN/evaluate_and_train.py @@ -37,7 +37,8 @@ import warnings from dataclasses import asdict, dataclass from pathlib import Path -from typing import Dict, Iterable, List, Tuple +from typing import Dict, List, Tuple +from collections.abc import Iterable import numpy as np import pandas as pd @@ -95,8 +96,8 @@ class TrainingSummary: mean_test_loss: float mean_test_loss_orig: float training_time: float - train_losses: List[List[float]] - val_losses: List[List[float]] + train_losses: list[list[float]] + val_losses: list[list[float]] def load_gnn_dataset( @@ -315,8 +316,8 @@ def _make_loader(dataset, *, batch_size, shuffle=False): best_weights = model.get_weights() patience_counter = es_patience - epoch_train_losses: List[float] = [] - epoch_val_losses: List[float] = [] + epoch_train_losses: list[float] = [] + epoch_val_losses: list[float] = [] start_time = time.perf_counter() diff --git a/pycode/memilio-surrogatemodel/pyproject.toml b/pycode/memilio-surrogatemodel/pyproject.toml index 7c2b95c518..cbcc7b2cd8 100644 --- a/pycode/memilio-surrogatemodel/pyproject.toml +++ b/pycode/memilio-surrogatemodel/pyproject.toml @@ -7,7 +7,7 @@ name = "memilio-surrogatemodel" version = "1.0.0" description = "Part of MEmilio project, implementation of surrogate models for the existing models in MEmilio." readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "Apache-2.0" } authors = [{ name = "MEmilio Team" }] maintainers = [ diff --git a/pyproject.toml b/pyproject.toml index 1e0e3ff415..b29b86c406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ name = "memilio-simulation" dynamic = ["version"] description = "Part of MEmilio project, Python bindings to the C++ libraries that contain the models and simulations." readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = "Apache-2.0" authors = [{ name = "MEmilio Team" }] maintainers = [ @@ -21,12 +21,12 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: POSIX :: Linux", "Operating System :: Microsoft :: Windows" ] From e71745dd16db5183a55c2d0f90ba94a59e2ffde1 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 15:29:20 +0200 Subject: [PATCH 29/34] try newer twill version --- pycode/memilio-epidata/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycode/memilio-epidata/pyproject.toml b/pycode/memilio-epidata/pyproject.toml index b85ea8c4f4..7425afb978 100644 --- a/pycode/memilio-epidata/pyproject.toml +++ b/pycode/memilio-epidata/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "requests", "pyxlsb", "wget", - "twill==3.1", + "twill", "PyQt6-sip<13.9", "PyQt6", "python-calamine", From d99adb8162932ba2b6c63b17e33f02b0964094fd Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 15:48:24 +0200 Subject: [PATCH 30/34] abandon 3.14 and downgrade to 3.13 for now --- .github/actions/test-pylint/action.yml | 4 ++-- .github/workflows/epidata_main.yml | 10 +++++----- .github/workflows/main.yml | 8 ++++---- docs/source/getting_started.rst | 4 ++-- docs/source/python/m-simulation.rst | 2 +- pycode/README.rst | 2 +- pycode/memilio-simulation/README.md | 2 +- pyproject.toml | 1 - 8 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/actions/test-pylint/action.yml b/.github/actions/test-pylint/action.yml index b1129d36d6..f5b6745ca5 100644 --- a/.github/actions/test-pylint/action.yml +++ b/.github/actions/test-pylint/action.yml @@ -15,10 +15,10 @@ runs: python -m pip install --upgrade pip # Pylint runs against the prebuilt wheels in pycode/wheelhouse/*cp314*. # So, use current latest supported Python version. - - name: Set up Python 3.14 + - name: Set up Python 3.13 uses: actions/setup-python@v6 with: - python-version: "3.14" + python-version: "3.13" - name: Download Python Wheels uses: actions/download-artifact@v7 with: diff --git a/.github/workflows/epidata_main.yml b/.github/workflows/epidata_main.yml index 80124b195a..9b1ad772c8 100644 --- a/.github/workflows/epidata_main.yml +++ b/.github/workflows/epidata_main.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: 3.14 + python-version: 3.13 - uses: pre-commit/action@v3.0.1 build-py-epidata: @@ -51,7 +51,7 @@ jobs: needs: build-py-epidata strategy: matrix: - version: ["3.9", "3.14"] + version: ["3.9", "3.13"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -66,7 +66,7 @@ jobs: needs: [build-py-plot, build-py-epidata] strategy: matrix: - version: ["3.9", "3.14"] + version: ["3.9", "3.13"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -123,10 +123,10 @@ jobs: with: name: python-wheels-epidata path: pycode/wheelhouse - - name: Set up Python 3.14 + - name: Set up Python 3.13 uses: actions/setup-python@v6 with: - python-version: 3.14 + python-version: 3.13 - name: Install Python Wheels run: | for pkg in `ls pycode/wheelhouse/*cp314*.whl`; do python -m pip install $pkg; done # packages that contain native extensions are version specific diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5823891391..8cc1ae158f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: 3.14 + python-version: 3.13 - uses: pre-commit/action@v3.0.1 build-cpp-gcc_clang: @@ -339,7 +339,7 @@ jobs: if: github.event.pull_request.draft == false strategy: matrix: - version: ["3.9", "3.14"] + version: ["3.9", "3.13"] needs: build-py-generation runs-on: ubuntu-latest steps: @@ -353,7 +353,7 @@ jobs: needs: build-py-simulation strategy: matrix: - version: ["3.9", "3.14"] + version: ["3.9", "3.13"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -366,7 +366,7 @@ jobs: needs: [build-py-surrogatemodel, build-py-simulation] strategy: matrix: - version: ["3.9", "3.14"] + version: ["3.9", "3.13"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index b9772dce62..07614e5ad8 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -258,7 +258,7 @@ Before you can install MEmilio, you need to install some common development tool * **Python:** Required for the Python packages. - * MEmilio is tested daily with Python 3.9 and 3.14. While other versions may also work, we recommend using the latest release of either of these. You can download it from the official website `python.org `__. + * MEmilio is tested daily with Python 3.9 and 3.13. While other versions may also work, we recommend using the latest release of either of these. You can download it from the official website `python.org `__. * **C++ Compiler and CMake:** @@ -313,7 +313,7 @@ If you just want to run simulations with the latest released version, install th pip install memilio-simulation -This requires no C++ compiler or CMake. Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.14. +This requires no C++ compiler or CMake. Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.13. **Option A.2: Install from source (for the latest development version)** diff --git a/docs/source/python/m-simulation.rst b/docs/source/python/m-simulation.rst index 874c6b9a7b..9fd78e6186 100644 --- a/docs/source/python/m-simulation.rst +++ b/docs/source/python/m-simulation.rst @@ -12,7 +12,7 @@ The ``memilio-simulation`` package can be installed in two ways depending on you **Option 1: Install from PyPI (Recommended - no C++ compiler required)** -Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.14. +Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.13. .. code-block:: console diff --git a/pycode/README.rst b/pycode/README.rst index 07debd1da9..9309d81afe 100644 --- a/pycode/README.rst +++ b/pycode/README.rst @@ -25,4 +25,4 @@ We recommend to use a virtual python environment to avoid dependency conflicts w Refer to the `Python documentation `_ for more information about virtual environments. -The packages are tested for Python 3.9 - 3.14. +The packages are tested for Python 3.9 - 3.13. diff --git a/pycode/memilio-simulation/README.md b/pycode/memilio-simulation/README.md index 88115fadc5..d2070b944d 100644 --- a/pycode/memilio-simulation/README.md +++ b/pycode/memilio-simulation/README.md @@ -6,7 +6,7 @@ This package contains Python bindings for the MEmilio C++ library. It enables se ### Option 1: Install from PyPI (Recommended) -Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.14. +Pre-built wheels are provided for Linux and Windows on Python 3.9 to 3.13. ```bash pip install memilio-simulation diff --git a/pyproject.toml b/pyproject.toml index b29b86c406..700e294c30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", "Operating System :: POSIX :: Linux", "Operating System :: Microsoft :: Windows" ] From 905ad80b6ec5e87de2cfb2d6c94c11b294f7c6b0 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 8 May 2026 16:20:05 +0200 Subject: [PATCH 31/34] try letting pip choose newer pyfakefs --- pycode/memilio-epidata/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycode/memilio-epidata/pyproject.toml b/pycode/memilio-epidata/pyproject.toml index 7425afb978..4adb594308 100644 --- a/pycode/memilio-epidata/pyproject.toml +++ b/pycode/memilio-epidata/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ # first support of python 3.11 is 4.6 # 5.3.4 has conflicts with openpyxl # 5.3.3 broken - "pyfakefs>=4.6,<5.3.3" + "pyfakefs>=4.6,!=5.3.3,!=5.3.4" ] [project.optional-dependencies] From efedcab7e30ccb8adcf0f76c008e3b0cf788468a Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 11 May 2026 13:00:05 +0200 Subject: [PATCH 32/34] try fix epidata ci --- .../tests/test_epidata_getHospitalizationData.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycode/memilio-epidata/tests/test_epidata_getHospitalizationData.py b/pycode/memilio-epidata/tests/test_epidata_getHospitalizationData.py index 2300b9731d..d08bf3eadb 100644 --- a/pycode/memilio-epidata/tests/test_epidata_getHospitalizationData.py +++ b/pycode/memilio-epidata/tests/test_epidata_getHospitalizationData.py @@ -24,6 +24,7 @@ import numpy as np import pandas as pd +from numpy.testing import assert_array_almost_equal from pyfakefs import fake_filesystem_unittest from memilio.epidata import getDataIntoPandasDataFrame as gd @@ -162,8 +163,7 @@ def test_compute_hospitailzations_per_day(self): except: pass # should be equal to the input array - np.testing.assert_array_almost_equal( - inputarray_r[6:], control_sum[6:], 10) + assert_array_almost_equal(inputarray_r[6:], control_sum[6:], 10) if __name__ == '__main__': From 1970d82c145390e2e5df02e4cf350d41f51ace2e Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 11 May 2026 13:16:41 +0200 Subject: [PATCH 33/34] use assertCountEqual (independent of order in list) --- .../tests/test_epidata_cleandata.py | 71 ++++++++++--------- ...test_epidata_getDataIntoPandasDataFrame.py | 7 +- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/pycode/memilio-epidata/tests/test_epidata_cleandata.py b/pycode/memilio-epidata/tests/test_epidata_cleandata.py index ece9606b5d..83cb52995a 100644 --- a/pycode/memilio-epidata/tests/test_epidata_cleandata.py +++ b/pycode/memilio-epidata/tests/test_epidata_cleandata.py @@ -168,13 +168,13 @@ def test_set_dirs_and_files(self): elif country == "Spain": self.assertEqual(len(os.listdir(pydata_dir)), 2) - self.assertEqual(os.listdir(pydata_dir), [ - "b_jh.json", "b_jh.h5"]) + self.assertCountEqual(os.listdir(pydata_dir), [ + "b_jh.json", "b_jh.h5"]) elif country == "France": self.assertEqual(len(os.listdir(pydata_dir)), 2) - self.assertEqual(os.listdir(pydata_dir), [ - "c_jh.json", "c_jh.h5"]) + self.assertCountEqual(os.listdir(pydata_dir), [ + "c_jh.json", "c_jh.h5"]) elif country == "Global": # For Global, the expected files include its own file(s) @@ -222,7 +222,7 @@ def test_clean_data_all_should_not_delete_all(self): # Should delete everything self.assertEqual(len(os.listdir(self.path)), 3) - self.assertEqual( + self.assertCountEqual( os.listdir(self.path), ["China", "ImportantDir", "wichtig.py"]) self.assertEqual(len(os.listdir(dir_path)), 1) @@ -267,12 +267,12 @@ def test_clean_data_cases(self): elif dir == "Spain": self.assertEqual(len(os.listdir(dir_path)), 2) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["b_jh.json", "b_jh.h5"]) elif dir == 'Global': self.assertEqual(len(os.listdir(dir_path)), 4) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["all_jh.json", "all_jh.h5", "FullJohnHopkins.json", "FullJohnHopkins.h5"]) @@ -318,12 +318,12 @@ def test_clean_data_cases_h5(self): elif dir == "Spain": self.assertEqual(len(os.listdir(dir_path)), 2) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["b_jh.json", "b_jh.h5"]) elif dir == 'Global': self.assertEqual(len(os.listdir(dir_path)), 4) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["all_jh.json", "all_jh.h5", "FullJohnHopkins.json", "FullJohnHopkins.h5"]) @@ -371,7 +371,7 @@ def test_clean_data_cases_del_dir(self): sorted(["c_jh.json", "c_jh.h5"])) elif country == 'Global': self.assertEqual(len(os.listdir(country_pydata)), 4) - self.assertEqual( + self.assertCountEqual( os.listdir(country_pydata), ["all_jh.json", "all_jh.h5", "FullJohnHopkins.json", "FullJohnHopkins.h5"]) @@ -418,12 +418,12 @@ def test_clean_data_population(self): elif dir == "Spain": self.assertEqual(len(os.listdir(dir_path)), 2) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["b_jh.json", "b_jh.h5"]) elif dir == 'Global': self.assertEqual(len(os.listdir(dir_path)), 4) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["all_jh.json", "all_jh.h5", "FullJohnHopkins.json", "FullJohnHopkins.h5"]) @@ -470,16 +470,16 @@ def test_clean_data_population_hdf5(self): elif dir == "Spain": self.assertEqual(len(os.listdir(dir_path)), 2) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["b_jh.json", "b_jh.h5"]) elif dir == "France": - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["c_jh.json", "c_jh.h5"]) elif dir == 'Global': self.assertEqual(len(os.listdir(dir_path)), 4) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["all_jh.json", "all_jh.h5", "FullJohnHopkins.json", "FullJohnHopkins.h5"]) @@ -523,12 +523,12 @@ def test_clean_data_population_del_dir(self): if dir == "Spain": self.assertEqual(len(os.listdir(dir_path)), 2) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["b_jh.json", "b_jh.h5"]) elif dir == 'Global': self.assertEqual(len(os.listdir(dir_path)), 4) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["all_jh.json", "all_jh.h5", "FullJohnHopkins.json", "FullJohnHopkins.h5"]) @@ -576,12 +576,12 @@ def test_all_false(self): elif dir == "Spain": self.assertEqual(len(os.listdir(dir_path)), 2) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["b_jh.json", "b_jh.h5"]) elif dir == 'Global': self.assertEqual(len(os.listdir(dir_path)), 4) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["all_jh.json", "all_jh.h5", "FullJohnHopkins.json", "FullJohnHopkins.h5"]) @@ -602,8 +602,8 @@ def test_wrong_path(self): dir2 = os.listdir(self.path) dir2a = os.listdir(os.path.join(self.path, 'Germany/')) - self.assertEqual(dir1, dir2) - self.assertEqual(dir1a, dir2a) + self.assertCountEqual(dir1, dir2) + self.assertCountEqual(dir1a, dir2a) def test_clean_data_jh(self): """ """ @@ -714,8 +714,8 @@ def test_clean_data_jh_both_endings(self): self.assertEqual(len(os.listdir(self.path)), 1) - self.assertEqual(os.listdir(self.path), - dir_list) + self.assertCountEqual(os.listdir(self.path), + dir_list) for dir in dir_list: dir_path = os.path.join(self.path, dir, 'pydata') @@ -770,7 +770,8 @@ def test_file_not_found_cases(self): False, False, False, False, True, False, False, self.path) self.assertEqual(len(os.listdir(self.path)), 2) - self.assertEqual(os.listdir(self.path), ["ImportantDir", "wichtig.py"]) + self.assertCountEqual(os.listdir(self.path), + ["ImportantDir", "wichtig.py"]) def test_file_not_found_population(self): """ """ @@ -787,7 +788,8 @@ def test_file_not_found_population(self): False, False, False, False, True, False, False, self.path) self.assertEqual(len(os.listdir(self.path)), 2) - self.assertEqual(os.listdir(self.path), ["ImportantDir", "wichtig.py"]) + self.assertCountEqual(os.listdir(self.path), + ["ImportantDir", "wichtig.py"]) def test_file_not_found_jh(self): """ """ @@ -804,7 +806,8 @@ def test_file_not_found_jh(self): False, False, False, False, True, False, False, self.path) self.assertEqual(len(os.listdir(self.path)), 2) - self.assertEqual(os.listdir(self.path), ["ImportantDir", "wichtig.py"]) + self.assertCountEqual(os.listdir(self.path), + ["ImportantDir", "wichtig.py"]) def test_file_not_found_divi(self): """ """ @@ -821,7 +824,8 @@ def test_file_not_found_divi(self): False, False, False, False, True, False, False, self.path) self.assertEqual(len(os.listdir(self.path)), 2) - self.assertEqual(os.listdir(self.path), ["ImportantDir", "wichtig.py"]) + self.assertCountEqual(os.listdir(self.path), + ["ImportantDir", "wichtig.py"]) def test_file_not_found_vacc(self): """ """ @@ -838,7 +842,8 @@ def test_file_not_found_vacc(self): True, False, False, False, True, False, False, self.path) self.assertEqual(len(os.listdir(self.path)), 2) - self.assertEqual(os.listdir(self.path), ["ImportantDir", "wichtig.py"]) + self.assertCountEqual(os.listdir(self.path), + ["ImportantDir", "wichtig.py"]) def test_file_not_found_commuter(self): """ """ @@ -855,7 +860,8 @@ def test_file_not_found_commuter(self): False, True, False, False, True, False, False, self.path) self.assertEqual(len(os.listdir(self.path)), 2) - self.assertEqual(os.listdir(self.path), ["ImportantDir", "wichtig.py"]) + self.assertCountEqual(os.listdir(self.path), + ["ImportantDir", "wichtig.py"]) def test_file_not_found_testing(self): """ """ @@ -872,7 +878,8 @@ def test_file_not_found_testing(self): False, False, True, False, True, False, False, self.path) self.assertEqual(len(os.listdir(self.path)), 2) - self.assertEqual(os.listdir(self.path), ["ImportantDir", "wichtig.py"]) + self.assertCountEqual(os.listdir(self.path), + ["ImportantDir", "wichtig.py"]) def test_no_files(self): """ """ @@ -1129,13 +1136,13 @@ def test_clean_divi_vacc_commuter_testing_json(self): elif dir == "Spain": self.assertEqual(len(os.listdir(dir_path)), 2) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["b_jh.json", "b_jh.h5"]) elif dir == "France": self.assertEqual(len(os.listdir(dir_path)), 2) - self.assertEqual( + self.assertCountEqual( os.listdir(dir_path), ["c_jh.json", "c_jh.h5"]) diff --git a/pycode/memilio-epidata/tests/test_epidata_getDataIntoPandasDataFrame.py b/pycode/memilio-epidata/tests/test_epidata_getDataIntoPandasDataFrame.py index 66ddc104c9..e97d6bf6b2 100644 --- a/pycode/memilio-epidata/tests/test_epidata_getDataIntoPandasDataFrame.py +++ b/pycode/memilio-epidata/tests/test_epidata_getDataIntoPandasDataFrame.py @@ -373,14 +373,14 @@ def test_write_dataframe(self): file0 = "test_csv.csv" self.assertEqual(len(os.listdir(self.path)), 1) - self.assertEqual(os.listdir(self.path), [file0]) + self.assertCountEqual(os.listdir(self.path), [file0]) gd.write_dataframe(df, self.path, "test_json", 'json') file1 = "test_json.json" self.assertEqual(len(os.listdir(self.path)), 2) - self.assertEqual(os.listdir(self.path), [file0, file1]) + self.assertCountEqual(os.listdir(self.path), [file0, file1]) file_with_path = os.path.join(self.path, file1) f = open(file_with_path) @@ -400,8 +400,7 @@ def test_write_dataframe(self): file2 = "test_json_timeasstring.json" self.assertEqual(len(os.listdir(self.path)), 3) - self.assertEqual(os.listdir(self.path).sort(), - [file0, file1, file2].sort()) + self.assertCountEqual(os.listdir(self.path), [file0, file1, file2]) file_with_path = os.path.join(self.path, file2) f = open(file_with_path) From 9d82963ab6be93128d7e6ea33986fb79657cc534 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 11 May 2026 13:42:07 +0200 Subject: [PATCH 34/34] cp3.13 --- .github/actions/build-py/action.yml | 8 ++++---- .github/actions/test-pylint/action.yml | 4 ++-- .github/workflows/epidata_main.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/actions/build-py/action.yml b/.github/actions/build-py/action.yml index c8062a97a2..4900f92333 100644 --- a/.github/actions/build-py/action.yml +++ b/.github/actions/build-py/action.yml @@ -29,16 +29,16 @@ runs: # else stay in root directory fi /opt/python/cp39-cp39/bin/python -m pip install --upgrade pip setuptools wheel - /opt/python/cp314-cp314/bin/python -m pip install --upgrade pip setuptools wheel + /opt/python/cp313-cp313/bin/python -m pip install --upgrade pip setuptools wheel /opt/python/cp39-cp39/bin/python -m pip install scikit-build scikit-build-core - /opt/python/cp314-cp314/bin/python -m pip install scikit-build scikit-build-core + /opt/python/cp313-cp313/bin/python -m pip install scikit-build scikit-build-core # Install setuptools-scm only for memilio-simulation if [ "${{ inputs.package }}" == "simulation" ]; then /opt/python/cp39-cp39/bin/python -m pip install setuptools-scm - /opt/python/cp314-cp314/bin/python -m pip install setuptools-scm + /opt/python/cp313-cp313/bin/python -m pip install setuptools-scm fi /opt/python/cp39-cp39/bin/python -m build --no-isolation --wheel - /opt/python/cp314-cp314/bin/python -m build --no-isolation --wheel + /opt/python/cp313-cp313/bin/python -m build --no-isolation --wheel # Exclude memilio-generation, because its a pure python package, cmake is only used in the build process to retrieve data from cpp if [[ -f "CMakeLists.txt" ]] && [ "${{ inputs.package }}" != "generation" ]; then # includes native dependencies in the wheel diff --git a/.github/actions/test-pylint/action.yml b/.github/actions/test-pylint/action.yml index f5b6745ca5..62c5270a95 100644 --- a/.github/actions/test-pylint/action.yml +++ b/.github/actions/test-pylint/action.yml @@ -13,7 +13,7 @@ runs: sudo apt-get -qq update sudo apt-get -qq -y install python3-pip gnupg python -m pip install --upgrade pip - # Pylint runs against the prebuilt wheels in pycode/wheelhouse/*cp314*. + # Pylint runs against the prebuilt wheels in pycode/wheelhouse/*cp313*. # So, use current latest supported Python version. - name: Set up Python 3.13 uses: actions/setup-python@v6 @@ -28,7 +28,7 @@ runs: shell: bash run: | shopt -s nullglob - for pkg in pycode/wheelhouse/*cp314*.whl; do python -m pip install "$pkg"; done # packages that contain native extensions are version specific + for pkg in pycode/wheelhouse/*cp313*.whl; do python -m pip install "$pkg"; done # packages that contain native extensions are version specific for pkg in pycode/wheelhouse/*py3*.whl; do python -m pip install "$pkg"; done # pure python packages are not version specific python -m pip install --upgrade-strategy only-if-needed --prefer-binary --find-links pycode/wheelhouse "memilio-${{ inputs.package }}[dev]" - name: Run pylint diff --git a/.github/workflows/epidata_main.yml b/.github/workflows/epidata_main.yml index 9b1ad772c8..1be5d7c6e6 100644 --- a/.github/workflows/epidata_main.yml +++ b/.github/workflows/epidata_main.yml @@ -129,7 +129,7 @@ jobs: python-version: 3.13 - name: Install Python Wheels run: | - for pkg in `ls pycode/wheelhouse/*cp314*.whl`; do python -m pip install $pkg; done # packages that contain native extensions are version specific + for pkg in `ls pycode/wheelhouse/*cp313*.whl`; do python -m pip install $pkg; done # packages that contain native extensions are version specific for pkg in `ls pycode/wheelhouse/*py3*.whl`; do python -m pip install $pkg; done # pure python packages are not version specific - name: Download Data run: |