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 diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 79a0485eca..1e72d9043d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -28,7 +28,21 @@ option(MEMILIO_ENABLE_IPOPT "Enable numerical optimization with Ipopt, requires 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) +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. Accessed via mio::base_dir().") +set(MEMILIO_DATA_DIR "${PROJECT_ROOT_NORMALIZED}/data" CACHE PATH + "Path to the MEmilio data directory. Accessed via mio::data_dir().") + +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,9 +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") -# 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) - # 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 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/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp b/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp index e6bbeefd6d..64f686b2de 100644 --- a/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp +++ b/cpp/benchmarks/flow_simulation_ode_secirvvs.cpp @@ -21,11 +21,11 @@ #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 -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 d50b16f0f8..98521af9a7 100644 --- a/cpp/benchmarks/flow_simulation_ode_seir.cpp +++ b/cpp/benchmarks/flow_simulation_ode_seir.cpp @@ -20,12 +20,12 @@ #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 -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 1b19b4eb40..29498d34f9 100644 --- a/cpp/benchmarks/graph_simulation.cpp +++ b/cpp/benchmarks/graph_simulation.cpp @@ -22,11 +22,11 @@ #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 -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 c179476f70..4f4734765f 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) @@ -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 5205e6d2d9..3a98cfab97 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) @@ -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); 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/abm_history_object.cpp b/cpp/examples/abm_history_object.cpp index f4240e44fa..4e1c3bf3e2 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_directories_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..4934fd34e1 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_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); - 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..313663ef20 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 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") @@ -212,17 +208,17 @@ 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"); + 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}; }); // 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/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/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_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_seair_optimization.cpp b/cpp/examples/ode_seair_optimization.cpp index 8a9f3fe069..a12d5cb954 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_directories_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.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"; } 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..9531c74d77 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" @@ -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_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, 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..cddad4cd25 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,10 +36,12 @@ int main(int argc, char** argv) { mio::set_log_level(mio::LogLevel::critical); + 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").string(), {.description = "Create the mobility file with MEmilio Epidata's getCommuterMobility.py file."}) .build(); @@ -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").string()); 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").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 09a624c0cb..f8acc8c2a6 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 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").string()); } diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index 658733a7f8..e7d9018849 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 @@ -131,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/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 50% rename from cpp/memilio/utils/base_dir.h rename to cpp/memilio/io/directories.h index 4616ac7387..38ff146a45 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: Rene Schmieding, Julia Bicker * * Contact: Martin J. Kuehn * @@ -17,11 +17,12 @@ * 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 #include namespace mio @@ -30,11 +31,29 @@ 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 MEMILIO_BASE_DIR; + return details::MEMILIO_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; +} + +/** + * @brief Returns the absolute path to a common ouput directory for the code examples. + */ +[[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; } } // namespace mio -#endif // MIO_BASE_DIR_H +#endif // MIO_UTILS_DIRECTORIES_H diff --git a/cpp/memilio/io/io.cpp b/cpp/memilio/io/io.cpp index cb8acb237d..1a50a553e5 100644 --- a/cpp/memilio/io/io.cpp +++ b/cpp/memilio/io/io.cpp @@ -21,53 +21,62 @@ #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 = create_parents ? std::filesystem::create_directories(rel_path, ec) + : 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()); - } - else { - log_info("Directory '{:s}' already exists.", dir.string(), mio::get_current_dir_name()); - } + log_info("Directory '{}' {}.", rel_path, created ? "was created" : "already exists"); return success(created); } -IOResult create_directory(std::string const& rel_path) +std::filesystem::path create_directories_or_exit(const std::filesystem::path& path, bool create_parents) { std::string abs_path; - return create_directory(rel_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()); + } + return abs_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 319ce4e530..79e91332a7 100644 --- a/cpp/memilio/io/io.h +++ b/cpp/memilio/io/io.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -869,19 +870,31 @@ 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. + * @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& rel_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 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. + * @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(const std::filesystem::path& path, bool create_parents = false); + +/** + * @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`. */ -IOResult create_directory(std::string const& rel_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/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 { diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index 4cf3a8c7ef..ac42e312ac 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 /= std::filesystem::path(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/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()); } 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 d6d2d07ff3..04ecb5effb 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,40 @@ class TempFileRegister */ std::string get_unique_path(const std::string& model = "%%%%-%%%%-%%%%-%%%%") { - auto tmp_path = get_tmp_path(); - auto file_name = boost::filesystem::unique_path(model); - auto file_path = tmp_path / file_name; + // this is an in-place replacement for boost::filesystem::unique_path, as it was removed from std::filesystem + // 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" + "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_name = model; + for (char& c : random_name) { + if (c == '%') + c = random_char(); + } + auto file_path = get_tmp_path() / std::filesystem::path(random_name); 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_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_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_stl_util.cpp b/cpp/tests/test_stl_util.cpp index ef4e5a6070..8b90b057d5 100644 --- a/cpp/tests/test_stl_util.cpp +++ b/cpp/tests/test_stl_util.cpp @@ -19,8 +19,10 @@ */ #include "memilio/utils/stl_util.h" #include "memilio/utils/compiler_diagnostics.h" +#include #include #include +#include TEST(TestRange, index_operator) { @@ -154,21 +156,27 @@ TEST(TestPathJoin, joinOne) EXPECT_EQ(mio::path_join("."), "."); } +// replace windows path separators by generic ones +std::string generic(const std::string& dir) +{ + return std::filesystem::path(dir).generic_string(); +} + 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(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(""), ""); - 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(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 diff --git a/cpp/tests/test_utils.cpp b/cpp/tests/test_utils.cpp index 06aba0c25d..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/utils/base_dir.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 = boost::filesystem::path(mio::base_dir()); - // check that the path exists - EXPECT_TRUE(boost::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")); -} 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/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 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;