diff --git a/.github/workflows/cmake_install_macos.yml b/.github/workflows/cmake_install_macos.yml index b58890aa3..8e39b1dd0 100644 --- a/.github/workflows/cmake_install_macos.yml +++ b/.github/workflows/cmake_install_macos.yml @@ -27,5 +27,5 @@ jobs: touch test.cpp echo "#include " >> test.cpp echo "int main() {}" >> test.cpp - g++ test.cpp -std=c++20 && echo "Compiled successfully" \ + g++ test.cpp -std=c++20 -lspdlog && echo "Compiled successfully" \ || (echo "Cannot include dsm" ; exit 1) diff --git a/.github/workflows/cmake_test_macos.yml b/.github/workflows/cmake_test_macos.yml index 09f94e4cb..73c5eda97 100644 --- a/.github/workflows/cmake_test_macos.yml +++ b/.github/workflows/cmake_test_macos.yml @@ -12,7 +12,6 @@ env: jobs: build: runs-on: macos-latest - steps: - uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 289b50096..9cf365f3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,28 +2,35 @@ cmake_minimum_required(VERSION 3.16.0) project(dms VERSION 2.0.0 LANGUAGES CXX) -# set the C++ standard +# Set the C++ standard set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +include(FetchContent) +# Get spdlog +FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.15.0 +) +FetchContent_MakeAvailable(spdlog) + file(GLOB SOURCES "src/dsm/headers/*.cpp") add_library(dsm STATIC ${SOURCES}) + +target_link_libraries(dsm PUBLIC spdlog::spdlog) + target_include_directories(dsm PUBLIC $ $ ) install(TARGETS dsm - EXPORT dsmConfig - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin) - -install(DIRECTORY ${PROJECT_SOURCE_DIR}/src/ DESTINATION ${CMAKE_INSTALL_PREFIX}/include) + EXPORT dsmConfig + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) -install(EXPORT dsmConfig - FILE dsmConfig.cmake - NAMESPACE dsm:: - DESTINATION lib/cmake/dsm) +install(DIRECTORY ${PROJECT_SOURCE_DIR}/src/ DESTINATION ${CMAKE_INSTALL_PREFIX}/include) \ No newline at end of file diff --git a/README.md b/README.md index 724efcc1a..1c51cab4f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ This rework consists of a full code rewriting, in order to implement more featur ## Requirements -The project only requires `C++20` or greater and `cmake`. +This project requieres a compiler whith full support for `C++20` or greater and `cmake`. +It also depends on [spdlog](https://github.com/gabime/spdlog). Utilities are written in python. To install their dependencies: ```shell @@ -15,20 +16,12 @@ pip install -r ./requirements.txt ``` ## Installation -The library can be installed using CMake. To build and install the project in the default folder run: +The library can be installed using CMake. +To build and install the project in the default folder run: ```shell -cmake -B build && cmake --build build +cmake -B build && make -C build sudo cmake --install build ``` -Otherwise, it is possible to customize the installation path: -```shell -cmake -B build -DCMAKE_INSTALL_PREFIX=/path/to/install -``` -then building and installing it (eventually in sudo mode) with: -```shell -cmake --build build -cmake --install build -``` ## Testing This project uses [Doctest](https://github.com/doctest/doctest) for testing. diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index beb15a5a6..3476f3275 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -9,6 +9,15 @@ set(CMAKE_CXX_EXTENSIONS OFF) include_directories(../extern/benchmark/) +include(FetchContent) +# Get spdlog +FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.15.0 +) +FetchContent_MakeAvailable(spdlog) + # add subdirectories add_subdirectory(Graph) add_subdirectory(Street) diff --git a/benchmark/Dynamics/CMakeLists.txt b/benchmark/Dynamics/CMakeLists.txt index 5ed567e8f..c52b11113 100644 --- a/benchmark/Dynamics/CMakeLists.txt +++ b/benchmark/Dynamics/CMakeLists.txt @@ -13,6 +13,7 @@ set(EXECUTABLE_OUTPUT_PATH ../../) include_directories(../../src/dsm/headers) include_directories(../../src/dsm/utility/) +include_directories(${spdlog_SOURCE_DIR}/include) file(GLOB SOURCES "../../src/dsm/headers/*.cpp") diff --git a/benchmark/Graph/CMakeLists.txt b/benchmark/Graph/CMakeLists.txt index 96fe5ce50..4ec9b5495 100644 --- a/benchmark/Graph/CMakeLists.txt +++ b/benchmark/Graph/CMakeLists.txt @@ -13,6 +13,7 @@ set(EXECUTABLE_OUTPUT_PATH ../../) include_directories(../../src/dsm/headers/) include_directories(../../src/dsm/utility/) +include_directories(${spdlog_SOURCE_DIR}/include) file(GLOB SOURCES "../../src/dsm/headers/*.cpp") diff --git a/benchmark/Street/CMakeLists.txt b/benchmark/Street/CMakeLists.txt index 6ce181c0a..853825057 100644 --- a/benchmark/Street/CMakeLists.txt +++ b/benchmark/Street/CMakeLists.txt @@ -13,6 +13,7 @@ set(EXECUTABLE_OUTPUT_PATH ../../) include_directories(../../src/dsm/headers) include_directories(../../src/dsm/utility/) +include_directories(${spdlog_SOURCE_DIR}/include) file(GLOB SOURCES "../../src/dsm/headers/*.cpp") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 649afec66..b80ca3d34 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -17,6 +17,15 @@ endif() # Set the folder for the executable set(EXECUTABLE_OUTPUT_PATH ../) +include(FetchContent) +# Get spdlog +FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.15.0 +) +FetchContent_MakeAvailable(spdlog) + # add as executable all cpp files into '.' folder file(GLOB SOURCES "*.cpp") file(GLOB SRC_SOURCES "../src/dsm/headers/*.cpp") @@ -25,5 +34,5 @@ file(GLOB SRC_SOURCES "../src/dsm/headers/*.cpp") foreach(SOURCE ${SOURCES}) get_filename_component(EXE_NAME ${SOURCE} NAME_WE) add_executable(${EXE_NAME}.out ${SOURCE} ${SRC_SOURCES}) - target_include_directories(${EXE_NAME}.out PRIVATE ../src/dsm/headers/ ../src/dsm/utility/TypeTraits/) + target_include_directories(${EXE_NAME}.out PRIVATE ../src/dsm/headers/ ../src/dsm/utility/TypeTraits/ ${spdlog_SOURCE_DIR}/include) endforeach() diff --git a/profiling/CMakeLists.txt b/profiling/CMakeLists.txt index fdf8884c3..b0d5197d9 100644 --- a/profiling/CMakeLists.txt +++ b/profiling/CMakeLists.txt @@ -13,13 +13,22 @@ string(APPEND CMAKE_CXX_FLAGS "-Wall -Wextra -Os") # Set the folder for the executable set(EXECUTABLE_OUTPUT_PATH ../) +include(FetchContent) +# Get spdlog +FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.15.0 +) +FetchContent_MakeAvailable(spdlog) + file(GLOB SOURCES "../src/dsm/headers/*.cpp") # Define the executable add_executable(prof.out main.cpp ${SOURCES}) -target_include_directories(prof.out PRIVATE ../src/) +target_include_directories(prof.out PRIVATE ../src/ ${spdlog_SOURCE_DIR}/include) target_compile_options(prof.out PRIVATE -pg) target_link_options(prof.out PRIVATE -pg) add_executable(mem.out main.cpp ${SOURCES}) -target_include_directories(mem.out PRIVATE ../src/) +target_include_directories(mem.out PRIVATE ../src/ ${spdlog_SOURCE_DIR}/include) add_executable(parse_massif.out parse_massif.cpp) diff --git a/src/dsm/dsm.hpp b/src/dsm/dsm.hpp index bfc499166..0eca0e455 100644 --- a/src/dsm/dsm.hpp +++ b/src/dsm/dsm.hpp @@ -1,5 +1,4 @@ -#ifndef dsm_hpp -#define dsm_hpp +#pragma once #include #include @@ -27,5 +26,3 @@ namespace dsm { #include "utility/TypeTraits/is_node.hpp" #include "utility/TypeTraits/is_street.hpp" #include "utility/TypeTraits/is_numeric.hpp" - -#endif diff --git a/src/dsm/headers/Agent.hpp b/src/dsm/headers/Agent.hpp index e1626edb0..7c6bea873 100644 --- a/src/dsm/headers/Agent.hpp +++ b/src/dsm/headers/Agent.hpp @@ -21,15 +21,18 @@ #include #include +#include +#include +#include + namespace dsm { /// @brief The Agent class represents an agent in the network. - /// @tparam Id, The type of the agent's id. It must be an unsigned integral type. - /// @tparam Size, The type of the size of a street. It must be an unsigned integral type. /// @tparam Delay, The type of the agent's delay. It must be a numeric type (see utility/TypeTraits/is_numeric.hpp). template requires(is_numeric_v) class Agent { private: + inline static auto const pConsoleLogger{spdlog::stdout_color_mt("DSM_AGENT_CONSOLE")}; Id m_id; Id m_itineraryId; std::optional m_streetId; @@ -63,11 +66,9 @@ namespace dsm { /// @throw std::invalid_argument, if speed is negative void setSpeed(double speed); /// @brief Increment the agent's delay by 1 - /// @throw std::overflow_error, if delay has reached its maximum value void incrementDelay(); /// @brief Increment the agent's delay by a given value /// @param delay The agent's delay - /// @throw std::overflow_error, if delay has reached its maximum value void incrementDelay(Delay const delay); /// @brief Decrement the agent's delay by 1 /// @throw std::underflow_error, if delay has reached its minimum value @@ -78,12 +79,10 @@ namespace dsm { /// @param distance The value to increment the agent's distance byù /// @throw std::invalid_argument, if distance is negative void incrementDistance(double distance); - /// @brief Increment the agent's time by 1 - /// @throw std::overflow_error, if time has reached its maximum value + /// @brief Increment the agent's time by 1. void incrementTime(); - /// @brief Increment the agent's time by a given value + /// @brief Increment the agent's time by a given value. /// @param time The value to increment the agent's time by - /// @throw std::overflow_error, if time has reached its maximum value void incrementTime(unsigned int const time); /// @brief Reset the agent's time to 0 void resetTime() { m_time = 0; } @@ -147,7 +146,9 @@ namespace dsm { requires(is_numeric_v) void Agent::incrementDelay() { if (m_delay == std::numeric_limits::max()) { - throw std::overflow_error(buildLog("Delay has reached its maximum value")); + pConsoleLogger->critical( + "Agent {} delay has reached its maximum value ({}).", m_id, m_delay); + std::abort(); } ++m_delay; } @@ -155,7 +156,9 @@ namespace dsm { requires(is_numeric_v) void Agent::incrementDelay(Delay const delay) { if (m_delay + delay < m_delay) { - throw std::overflow_error(buildLog("Delay has reached its maximum value")); + pConsoleLogger->critical( + "Agent {} delay has reached its maximum value ({}).", m_id, m_delay); + std::abort(); } m_delay += delay; } @@ -181,7 +184,9 @@ namespace dsm { requires(is_numeric_v) void Agent::incrementTime() { if (m_time == std::numeric_limits::max()) { - throw std::overflow_error(buildLog("Time has reached its maximum value")); + pConsoleLogger->critical( + "Agent {} time has reached its maximum value ({}).", m_id, m_time); + std::abort(); } ++m_time; } @@ -189,7 +194,9 @@ namespace dsm { requires(is_numeric_v) void Agent::incrementTime(unsigned int const time) { if (m_time + time < m_time) { - throw std::overflow_error(buildLog("Time has reached its maximum value")); + pConsoleLogger->critical( + "Agent {} time has reached its maximum value ({}).", m_id, m_time); + std::abort(); } m_time += time; } diff --git a/src/dsm/headers/Dynamics.hpp b/src/dsm/headers/Dynamics.hpp index cb7c11099..9f00b14dd 100644 --- a/src/dsm/headers/Dynamics.hpp +++ b/src/dsm/headers/Dynamics.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "Agent.hpp" #include "Itinerary.hpp" @@ -31,6 +32,10 @@ #include "../utility/Logger.hpp" #include "../utility/Typedef.hpp" +#include +#include +#include + namespace dsm { using TimePoint = long long unsigned int; @@ -67,6 +72,10 @@ namespace dsm { requires(is_numeric_v) class Dynamics { protected: + inline static auto const pFileLogger{spdlog::basic_logger_mt( + "DSM_DYNAMICS_FILE", std::format("{}{}", DSM_LOG_FOLDER, DSM_LOG_FILE), true)}; + inline static auto const pConsoleLogger{ + spdlog::stdout_color_mt("DSM_DYNAMICS_CONSOLE")}; std::unordered_map> m_itineraries; std::map>> m_agents; TimePoint m_time, m_previousSpireTime; @@ -145,10 +154,10 @@ namespace dsm { path.insert(nodeId, nextNodeId, true); } } else if ((nextNodeId != destinationID)) { - std::cerr << std::format("WARNING: No path found from node {} to node {}", - nextNodeId, - destinationID) - << std::endl; + pFileLogger->warn( + "No path found from node {} to node {}", nextNodeId, destinationID); + pConsoleLogger->warn( + "No path found from node {} to node {}", nextNodeId, destinationID); } } } @@ -249,7 +258,7 @@ namespace dsm { void addAgent(const Agent& agent); /// @brief Add an agent to the simulation /// @param agent std::unique_ptr to the agent - void addAgent(std::unique_ptr> agent); + // void addAgent(std::unique_ptr> agent); /// @brief Add an agent with given source node and itinerary /// @param srcNodeId The id of the source node /// @param itineraryId The id of the itinerary @@ -283,6 +292,12 @@ namespace dsm { /// @throw std::runtime_error If there are no itineraries virtual void addAgentsUniformly(Size nAgents, std::optional itineraryId = std::nullopt); + /// @brief Add nAgents agents to the simulation, randomly choosing the source and destination nodes + /// @param nAgents The number of agents to add + /// @param src_weights A map representing the weights of the source nodes + /// @param dst_weights A map representing the weights of the destination nodes + /// @details The weights are used to randomly choose the source and destination nodes. It is not + /// necessary that the sum of the weights is 1, but they must be all positive. template requires(std::is_same_v> || std::is_same_v>) @@ -408,6 +423,11 @@ namespace dsm { } } } +#ifndef NDEBUG + spdlog::flush_every(std::chrono::milliseconds(100)); + pFileLogger->set_level(spdlog::level::debug); + pConsoleLogger->set_level(spdlog::level::debug); +#endif } template @@ -427,6 +447,7 @@ namespace dsm { uint8_t p{0}; auto iterator = possibleMoves.begin(); // while loop to avoid U turns in non-roundabout junctions + pFileLogger->debug("Entering loop to avoid U-turns"); do { p = moveDist(this->m_generator); iterator = possibleMoves.begin(); @@ -435,6 +456,7 @@ namespace dsm { (m_graph.streetSet()[iterator->first]->nodePair().second == m_graph.streetSet()[streetId.value()]->nodePair().first) and (possibleMoves.size() > 1)); + pFileLogger->debug("Exiting loop to avoid U-turns"); return iterator->first; } @@ -536,7 +558,7 @@ namespace dsm { if (intersection.agents().empty()) { return false; } - for (auto const [angle, agentId] : intersection.agents()) { + for (auto const& [angle, agentId] : intersection.agents()) { auto const& nextStreet{m_graph.streetSet()[m_agentNextStreetId[agentId]]}; if (nextStreet->isFull()) { if (m_forcePriorities) { @@ -641,11 +663,15 @@ namespace dsm { assert(agent->srcNodeId().has_value()); const auto& srcNode{this->m_graph.nodeSet()[agent->srcNodeId().value()]}; if (srcNode->isFull()) { + pFileLogger->debug( + "Agent {} is not able to enter full node {}.", agentId, srcNode->id()); continue; } const auto& nextStreet{ m_graph.streetSet()[this->m_nextStreetId(agentId, srcNode->id())]}; if (nextStreet->isFull()) { + pFileLogger->debug( + "Agent {} is not able to enter full street {}.", agentId, nextStreet->id()); continue; } assert(srcNode->id() == nextStreet->nodePair().first); @@ -746,6 +772,9 @@ namespace dsm { template requires(is_numeric_v) void Dynamics::evolve(bool reinsert_agents) { + pFileLogger->debug("Init evolving step {}", m_time); + + pFileLogger->debug("Evolving streets..."); // move the first agent of each street queue, if possible, putting it in the next node bool const bUpdateData = m_dataUpdatePeriod.has_value() && m_time % m_dataUpdatePeriod.value() == 0; @@ -757,6 +786,7 @@ namespace dsm { this->m_evolveStreet(streetId, pStreet, reinsert_agents); } } + pFileLogger->debug("Evolving junctions..."); // Move transport capacity agents from each node for (const auto& [nodeId, pNode] : m_graph.nodeSet()) { for (auto i = 0; i < pNode->transportCapacity(); ++i) { @@ -769,8 +799,10 @@ namespace dsm { tl.increaseCounter(); } } + pFileLogger->debug("Evolving vehicles..."); // cycle over agents and update their times this->m_evolveAgents(); + pFileLogger->debug("End evolving step {}", m_time); // increment time simulation ++this->m_time; } @@ -922,22 +954,12 @@ namespace dsm { throw std::invalid_argument( buildLog(std::format("Agent with id {} already exists.", agent.id()))); } + pFileLogger->debug("Adding agent with id {} and destination node {}", + agent.id(), + agent.itineraryId()); this->m_agents.emplace(agent.id(), std::make_unique>(agent)); } - template - requires(is_numeric_v) - void Dynamics::addAgent(std::unique_ptr> agent) { - if (this->m_agents.size() + 1 > this->m_graph.maxCapacity()) { - throw std::overflow_error(buildLog( - std::format("Graph is already holding the max possible number of agents ({})", - this->m_graph.maxCapacity()))); - } - if (this->m_agents.contains(agent->id())) { - throw std::invalid_argument( - buildLog(std::format("Agent with id {} already exists.", agent->id()))); - } - this->m_agents.emplace(agent->id(), std::move(agent)); - } + template requires(is_numeric_v) void Dynamics::addAgent(Id srcNodeId, Id itineraryId) { @@ -1075,9 +1097,12 @@ namespace dsm { const TContainer& dst_weights) { if (src_weights.size() == 1 && dst_weights.size() == 1 && src_weights.begin()->first == dst_weights.begin()->first) { - throw std::invalid_argument(buildLog( - std::format("The only source node {} is also the only destination node.", - src_weights.begin()->first))); + pFileLogger->critical("The only source node {} is also the only destination node.", + src_weights.begin()->first); + pConsoleLogger->critical( + "The only source node {} is also the only destination node.", + src_weights.begin()->first); + std::abort(); } auto const srcSum{std::accumulate( src_weights.begin(), @@ -1085,8 +1110,11 @@ namespace dsm { 0., [](double sum, const std::pair& p) { if (p.second < 0.) { - throw std::invalid_argument(buildLog(std::format( - "Negative weight ({}) for source node {}.", p.second, p.first))); + pFileLogger->critical( + "Negative weight ({}) for source node {}.", p.second, p.first); + pConsoleLogger->critical( + "Negative weight ({}) for source node {}.", p.second, p.first); + std::abort(); } return sum + p.second; })}; @@ -1096,8 +1124,11 @@ namespace dsm { 0., [](double sum, const std::pair& p) { if (p.second < 0.) { - throw std::invalid_argument(buildLog(std::format( - "Negative weight ({}) for destination node {}.", p.second, p.first))); + pFileLogger->critical( + "Negative weight ({}) for destination node {}.", p.second, p.first); + pConsoleLogger->critical( + "Negative weight ({}) for destination node {}.", p.second, p.first); + std::abort(); } return sum + p.second; })}; @@ -1135,14 +1166,31 @@ namespace dsm { } } } + if (src_weights.size() > 1) { + dstId = srcId; + } + pFileLogger->debug("Entering loop to randomly pick destination node."); + while (dstId == srcId) { + dRand = dstUniformDist(m_generator); + sum = 0.; + for (const auto& [id, weight] : dst_weights) { + dstId = id; + sum += weight; + if (dRand < sum) { + break; + } + } + } + pFileLogger->debug("Exiting loop to randomly pick destination node."); // find the itinerary with the given destination as destination auto itineraryIt{std::find_if( m_itineraries.begin(), m_itineraries.end(), [dstId](const auto& itinerary) { return itinerary.second->destination() == dstId; })}; if (itineraryIt == m_itineraries.end()) { - throw std::invalid_argument( - buildLog(std::format("Itinerary with destination {} not found.", dstId))); + pFileLogger->critical("Itinerary with destination {} not found.", dstId); + pConsoleLogger->critical("Itinerary with destination {} not found.", dstId); + std::abort(); } this->addAgent(srcId, itineraryIt->first); --nAgents; @@ -1152,6 +1200,9 @@ namespace dsm { template requires(is_numeric_v) void Dynamics::removeAgent(Size agentId) { + pFileLogger->debug("Removing agent with id {} and destination node {}", + agentId, + m_agents[agentId]->itineraryId()); m_agents.erase(agentId); } diff --git a/src/dsm/headers/Graph.cpp b/src/dsm/headers/Graph.cpp index 3b117e68a..45d02c2be 100644 --- a/src/dsm/headers/Graph.cpp +++ b/src/dsm/headers/Graph.cpp @@ -421,7 +421,8 @@ namespace dsm { Roundabout& Graph::makeRoundabout(Id nodeId) { if (!m_nodes.contains(nodeId)) { - throw std::invalid_argument(buildLog("Node does not exist.")); + pConsoleLogger->critical("Node {} does not exist in graph.", nodeId); + std::abort(); } auto& pNode = m_nodes[nodeId]; pNode = std::make_unique(*pNode); @@ -429,8 +430,8 @@ namespace dsm { } SpireStreet& Graph::makeSpireStreet(Id streetId) { if (!m_streets.contains(streetId)) { - throw std::invalid_argument( - buildLog(std::format("Street with id {} does not exist.", streetId))); + pConsoleLogger->critical("Street with id {} does not exist in graph.", streetId); + std::abort(); } auto& pStreet = m_streets[streetId]; pStreet = std::make_unique(pStreet->id(), *pStreet); @@ -439,8 +440,8 @@ namespace dsm { void Graph::addStreet(std::shared_ptr street) { if (m_streets.contains(street->id())) { - throw std::invalid_argument( - buildLog(std::format("Street with id {} already exists.", street->id()))); + pConsoleLogger->critical("Street with id {} already exists.", street->id()); + std::abort(); } // emplace nodes const auto srcId{street->nodePair().first}; @@ -457,8 +458,8 @@ namespace dsm { void Graph::addStreet(const Street& street) { if (m_streets.contains(street.id())) { - throw std::invalid_argument( - buildLog(std::format("Street with id {} already exists.", street.id()))); + pConsoleLogger->critical("Street with id {} already exists.", street.id()); + std::abort(); } // emplace nodes const auto srcId{street.nodePair().first}; @@ -497,10 +498,11 @@ namespace dsm { const std::unique_ptr* Graph::oppositeStreet(Id streetId) const { if (!m_streets.contains(streetId)) { - throw std::invalid_argument( - buildLog(std::format("Street with id {} does not exist: maybe it has changed " - "id once called buildAdj.", - streetId))); + pConsoleLogger->critical( + "Street with id {} does not exist: maybe it has changed id once callde " + "buildAdj.", + streetId); + std::abort(); } const auto& nodePair = m_streets.at(streetId)->nodePair(); return this->street(nodePair.second, nodePair.first); diff --git a/src/dsm/headers/Graph.hpp b/src/dsm/headers/Graph.hpp index 02c77ceaa..bb5f14eb9 100644 --- a/src/dsm/headers/Graph.hpp +++ b/src/dsm/headers/Graph.hpp @@ -35,6 +35,10 @@ #include "../utility/TypeTraits/is_node.hpp" #include "../utility/TypeTraits/is_street.hpp" +#include +#include +#include + namespace dsm { /// @brief The Graph class represents a graph in the network. @@ -42,6 +46,7 @@ namespace dsm { /// @tparam Size, The type of the graph's capacity. It must be an unsigned integral type. class Graph { private: + inline static auto const pConsoleLogger{spdlog::stdout_color_mt("DSM_GRAPH_CONSOLE")}; std::unordered_map> m_nodes; std::unordered_map> m_streets; std::unordered_map m_nodeMapping; @@ -164,19 +169,16 @@ namespace dsm { /// @tparam Delay The type of the traffic light's delay /// @param nodeId The id of the node to convert to a traffic light /// @return A reference to the traffic light - /// @throws std::invalid_argument if the node does not exist template requires(std::unsigned_integral) - TrafficLight& makeTrafficLight(Id nodeId); + TrafficLight& makeTrafficLight(Id const nodeId); /// @brief Convert an existing node into a roundabout /// @param nodeId The id of the node to convert to a roundabout /// @return A reference to the roundabout - /// @throws std::invalid_argument if the node does not exist Roundabout& makeRoundabout(Id nodeId); /// @brief Convert an existing street into a spire street /// @param streetId The id of the street to convert to a spire street /// @return A reference to the spire street - /// @throws std::invalid_argument if the street does not exist SpireStreet& makeSpireStreet(Id streetId); /// @brief Add a street to the graph @@ -221,7 +223,6 @@ namespace dsm { const std::unique_ptr* street(Id source, Id destination) const; /// @brief Get the opposite street of a street in the graph /// @param streetId The id of the street - /// @throws std::invalid_argument if the street does not exist /// @return A std::unique_ptr to the street if it exists, nullptr otherwise const std::unique_ptr* oppositeStreet(Id streetId) const; @@ -284,9 +285,10 @@ namespace dsm { template requires(std::unsigned_integral) - TrafficLight& Graph::makeTrafficLight(Id nodeId) { + TrafficLight& Graph::makeTrafficLight(Id const nodeId) { if (!m_nodes.contains(nodeId)) { - throw std::invalid_argument(buildLog("Node does not exist.")); + pConsoleLogger->error("Node {} does not exist in graph.", nodeId); + std::abort(); } auto& pNode = m_nodes[nodeId]; pNode = std::make_unique>(*pNode); diff --git a/src/dsm/headers/Itinerary.cpp b/src/dsm/headers/Itinerary.cpp index 11496b04e..78b1fed16 100644 --- a/src/dsm/headers/Itinerary.cpp +++ b/src/dsm/headers/Itinerary.cpp @@ -9,23 +9,25 @@ namespace dsm { void Itinerary::setDestination(Id destination) { m_destination = destination; - this->m_path.clear(); + m_path.clear(); } void Itinerary::setPath(SparseMatrix path) { if (path.getRowDim() != path.getColDim()) { - throw std::invalid_argument(buildLog( - std::format("The path's row ({}) and column ({}) dimensions must be equal.", - path.getRowDim(), - path.getColDim()))); + pConsoleLogger->critical( + "The path's row ({}) and column ({}) dimensions must be equal.", + path.getRowDim(), + path.getColDim()); + std::abort(); } if (path.getRowDim() < m_destination) { - throw std::invalid_argument( - buildLog(std::format("The path's row ({}) and column ({}) dimensions must be " - "greater than the itinerary's destination ({}).", - path.getRowDim(), - path.getColDim(), - m_destination))); + pConsoleLogger->critical( + "The path's row ({}) and column ({}) dimensions must be greater than the " + "itinerary's destination ({}).", + path.getRowDim(), + path.getColDim(), + m_destination); + std::abort(); } m_path = std::move(path); } diff --git a/src/dsm/headers/Itinerary.hpp b/src/dsm/headers/Itinerary.hpp index 754218be3..1a5e41f36 100644 --- a/src/dsm/headers/Itinerary.hpp +++ b/src/dsm/headers/Itinerary.hpp @@ -16,11 +16,17 @@ #include #include +#include +#include +#include + namespace dsm { /// @brief The Itinerary class represents an itinerary in the network. /// @tparam Id The type of the itinerary's id. It must be an unsigned integral type. class Itinerary { private: + inline static auto const pConsoleLogger{ + spdlog::stdout_color_mt("DSM_ITINERARY_CONSOLE")}; Id m_id; SparseMatrix m_path; Id m_destination; @@ -39,7 +45,6 @@ namespace dsm { void setDestination(Id destination); /// @brief Set the itinerary's path /// @param path An adjacency matrix made by a SparseMatrix representing the itinerary's path - /// @throw std::invalid_argument, if the itinerary's source or destination is not in the path's void setPath(SparseMatrix path); /// @brief Get the itinerary's id diff --git a/src/dsm/headers/Node.cpp b/src/dsm/headers/Node.cpp index 46633d9ff..cb324eff1 100644 --- a/src/dsm/headers/Node.cpp +++ b/src/dsm/headers/Node.cpp @@ -7,22 +7,24 @@ namespace dsm { void Intersection::setCapacity(Size capacity) { if (capacity < m_agents.size()) { - throw std::runtime_error(buildLog(std::format( - "Intersection capacity ({}) is smaller than the current queue size ({}).", + pConsoleLogger->critical( + "New capacity ({}) is smaller than the current agent size ({}).", capacity, - m_agents.size()))); + m_agents.size()); + std::abort(); } Node::setCapacity(capacity); } void Intersection::addAgent(double angle, Id agentId) { if (isFull()) { - throw std::runtime_error(buildLog("Intersection is full.")); + pConsoleLogger->critical("Intersection is full."); + std::abort(); } for (auto const [angle, id] : m_agents) { if (id == agentId) { - throw std::runtime_error( - buildLog(std::format("Agent with id {} is already on the node.", agentId))); + pConsoleLogger->critical("Agent with id {} is already on the node.", agentId); + std::abort(); } } auto iAngle{static_cast(angle * 100)}; @@ -58,12 +60,14 @@ namespace dsm { void Roundabout::enqueue(Id agentId) { if (isFull()) { - throw std::runtime_error(buildLog("Roundabout is full.")); + pConsoleLogger->critical("Roundabout is full."); + std::abort(); } for (const auto id : m_agents) { if (id == agentId) { - throw std::runtime_error(buildLog( - std::format("Agent with id {} is already on the roundabout.", agentId))); + pConsoleLogger->critical("Agent with id {} is already on the roundabout.", + agentId); + std::abort(); } } m_agents.push(agentId); @@ -71,7 +75,8 @@ namespace dsm { Id Roundabout::dequeue() { if (m_agents.empty()) { - throw std::runtime_error(buildLog("Roundabout is empty.")); + pConsoleLogger->critical("Roundabout is empty."); + std::abort(); } Id agentId{m_agents.front()}; m_agents.pop(); diff --git a/src/dsm/headers/Node.hpp b/src/dsm/headers/Node.hpp index 9e4b06ef6..c86a8907f 100644 --- a/src/dsm/headers/Node.hpp +++ b/src/dsm/headers/Node.hpp @@ -24,12 +24,17 @@ #include "../utility/queue.hpp" #include "../utility/Typedef.hpp" +#include +#include +#include + namespace dsm { /// @brief The Node class represents the concept of a node in the network. /// @tparam Id The type of the node's id /// @tparam Size The type of the node's capacity class Node { protected: + inline static auto const pConsoleLogger{spdlog::stdout_color_mt("DSM_NODE_CONSOLE")}; Id m_id; std::optional> m_coords; Size m_capacity; @@ -103,7 +108,6 @@ namespace dsm { /// @brief Set the node's capacity /// @param capacity The node's capacity - /// @throws std::runtime_error if the capacity is smaller than the current queue size void setCapacity(Size capacity) override; /// @brief Put an agent in the node @@ -111,14 +115,12 @@ namespace dsm { /// @details The agent's angle difference is used to order the agents in the node. /// The agent with the smallest angle difference is the first one to be /// removed from the node. - /// @throws std::runtime_error if the node is full void addAgent(double angle, Id agentId); /// @brief Put an agent in the node /// @param agentId The agent's id /// @details The agent's angle difference is used to order the agents in the node. /// The agent with the smallest angle difference is the first one to be /// removed from the node. - /// @throws std::runtime_error if the node is full void addAgent(Id agentId); /// @brief Removes an agent from the node /// @param agentId The agent's id @@ -138,7 +140,7 @@ namespace dsm { } /// @brief Returns true if the node is full /// @return bool True if the node is full - bool isFull() const override { return m_agents.size() == this->m_capacity; } + bool isFull() const override { return m_agents.size() == m_capacity; } /// @brief Get the node's street priorities /// @details This function returns a std::set containing the node's street priorities. @@ -155,7 +157,7 @@ namespace dsm { /// since the last time this function was called. It also resets the counter. Size agentCounter(); - virtual bool isIntersection() const noexcept override final { return true; } + bool isIntersection() const noexcept final { return true; } }; template @@ -195,7 +197,6 @@ namespace dsm { void setDelay(std::pair delay); /// @brief Set the node's phase /// @param phase The node's phase - /// @throw std::runtime_error if the delay is not set void setPhase(Delay phase); /// @brief Set the node's left turn ratio /// @param ratio A std::pair containing the left turn ratio @@ -217,7 +218,6 @@ namespace dsm { /// @details This function is used to increase the node's counter /// when the simulation is running. It automatically resets the counter /// when it reaches the double of the delay value. - /// @throw std::runtime_error if the delay is not set void increaseCounter(); /// @brief Set the phase of the node after the current red-green cycle has passed @@ -237,7 +237,7 @@ namespace dsm { /// @return bool True if the traffic light is green bool isGreen() const; bool isGreen(Id streetId) const; - bool isTrafficLight() const noexcept override { return true; } + bool isTrafficLight() const noexcept final { return true; } }; template @@ -284,7 +284,8 @@ namespace dsm { requires(std::unsigned_integral) void TrafficLight::setPhase(Delay phase) { if (!m_delay.has_value()) { - throw std::runtime_error(buildLog("TrafficLight's delay has not been set.")); + pConsoleLogger->critical("TrafficLight's delay has not been set."); + std::abort(); } if (phase > m_delay.value().first + m_delay.value().second) { phase -= m_delay.value().first + m_delay.value().second; @@ -315,7 +316,8 @@ namespace dsm { requires(std::unsigned_integral) void TrafficLight::increaseCounter() { if (!m_delay.has_value()) { - throw std::runtime_error(buildLog("TrafficLight's delay has not been set.")); + pConsoleLogger->critical("TrafficLight's delay has not been set."); + std::abort(); } ++m_counter; if (m_counter == m_delay.value().first + m_delay.value().second) { @@ -332,7 +334,8 @@ namespace dsm { requires(std::unsigned_integral) bool TrafficLight::isGreen() const { if (!m_delay.has_value()) { - throw std::runtime_error(buildLog("TrafficLight's delay has not been set.")); + pConsoleLogger->critical("TrafficLight's delay has not been set."); + std::abort(); } return m_counter < m_delay.value().first; } @@ -341,7 +344,8 @@ namespace dsm { requires(std::unsigned_integral) bool TrafficLight::isGreen(Id streetId) const { if (!m_delay.has_value()) { - throw std::runtime_error(buildLog("TrafficLight's delay has not been set.")); + pConsoleLogger->critical("TrafficLight's delay has not been set."); + std::abort(); } bool hasPriority{this->streetPriorities().contains(streetId)}; if (this->isGreen()) { @@ -374,7 +378,6 @@ namespace dsm { /// @brief Put an agent in the node /// @param agentId The agent's id - /// @throws std::runtime_error if the node is full void enqueue(Id agentId); /// @brief Removes the first agent from the node /// @return Id The agent's id @@ -389,10 +392,10 @@ namespace dsm { } /// @brief Returns true if the node is full /// @return bool True if the node is full - bool isFull() const override { return m_agents.size() == this->m_capacity; } + bool isFull() const override { return m_agents.size() == m_capacity; } /// @brief Returns true if the node is a roundabout /// @return bool True if the node is a roundabout - bool isRoundabout() const noexcept override { return true; } + bool isRoundabout() const noexcept final { return true; } }; }; // namespace dsm diff --git a/src/dsm/headers/Street.cpp b/src/dsm/headers/Street.cpp index f769e2ee0..7c9af228e 100644 --- a/src/dsm/headers/Street.cpp +++ b/src/dsm/headers/Street.cpp @@ -120,20 +120,24 @@ namespace dsm { } void Street::addAgent(Id agentId) { - assert((void("Agent is already on the street."), !m_waitingAgents.contains(agentId))); - for (auto const& queue : m_exitQueues) { - for (auto const& id : queue) { - assert((void("Agent is already in queue."), id != agentId)); - } + if (m_waitingAgents.contains(agentId)) { + pConsoleLogger->critical("Agent {} is already on the street {}.", agentId, m_id); + std::abort(); } m_waitingAgents.insert(agentId); - ; } void Street::enqueue(Id agentId, size_t index) { - assert((void("Agent is not on the street."), m_waitingAgents.contains(agentId))); + if (!m_waitingAgents.contains(agentId)) { + pConsoleLogger->critical("Agent {} is not on the street {}.", agentId, m_id); + std::abort(); + } for (auto const& queue : m_exitQueues) { for (auto const& id : queue) { - assert((void("Agent is already in queue."), id != agentId)); + if (id == agentId) { + pConsoleLogger->critical( + "Agent {} is already in queue on street {}.", agentId, m_id); + std::abort(); + } } } m_waitingAgents.erase(agentId); diff --git a/src/dsm/headers/Street.hpp b/src/dsm/headers/Street.hpp index d3a215963..4663c7ac1 100644 --- a/src/dsm/headers/Street.hpp +++ b/src/dsm/headers/Street.hpp @@ -27,12 +27,18 @@ #include "../utility/Logger.hpp" #include "../utility/Typedef.hpp" +#include +#include +#include + namespace dsm { /// @brief The Street class represents a street in the network. /// @tparam Id, The type of the street's id. It must be an unsigned integral type. /// @tparam Size, The type of the street's capacity. It must be an unsigned integral type. class Street { private: + inline static auto const pConsoleLogger{ + spdlog::stdout_color_mt("DSM_STREET_CONSOLE")}; std::vector> m_exitQueues; std::set m_waitingAgents; std::pair m_nodePair; diff --git a/src/dsm/utility/Logger.hpp b/src/dsm/utility/Logger.hpp index a79053ef8..5191d97db 100644 --- a/src/dsm/utility/Logger.hpp +++ b/src/dsm/utility/Logger.hpp @@ -1,12 +1,31 @@ #pragma once +#include #include #include #include +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +// ~/Library/Application Support/dsm/logs/ +inline static auto const DSM_LOG_FOLDER = + std::format("{}/Library/Application Support/dsm/logs/", std::getenv("HOME")); +#elif __APPLE__ +// %appdata%/dsm/logs +inline static auto const DSM_LOG_FOLDER = + std::format("{}\\dsm\\logs\\", std::getenv("APPDATA")); +#else // Linux +// ~/.local/dsm/logs/ +inline static auto const DSM_LOG_FOLDER = + std::format("{}/.local/dsm/logs/", std::getenv("HOME")); +#endif + namespace dsm { + inline auto const DSM_LOG_FILE = std::format( + "{:%Y-%m-%d_%H-%M-%S}.log", + std::chrono::floor(std::chrono::system_clock::now())); + /// @brief The Logger class is a simple logging class. struct Logger { std::string operator()( diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8c9d91469..cbbaadae3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,17 +23,21 @@ FetchContent_Declare(doctest GIT_REPOSITORY https://github.com/doctest/doctest.git GIT_TAG v2.4.11 ) -FetchContent_GetProperties(doctest) -if(NOT doctest_POPULATED) - FetchContent_Populate(doctest) -endif() +FetchContent_MakeAvailable(doctest) +# Get spdlog +FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.15.0 +) +FetchContent_MakeAvailable(spdlog) # add as executable all cpp files into '.' folder file(GLOB TEST_SOURCES "*.cpp") file(GLOB SRC_SOURCES "../src/dsm/headers/*.cpp") -list(APPEND SOURCES ${TEST_SOURCES} ${SRC_SOURCES}) +list(APPEND SOURCES ${TEST_SOURCES} ${SRC_SOURCES} ) # Define the executable add_executable(dsm_tests.out ${SOURCES}) target_include_directories(dsm_tests.out PRIVATE ../src/dsm/headers/ ../src/dsm/utility/TypeTraits/) -target_include_directories(dsm_tests.out SYSTEM PRIVATE ${doctest_SOURCE_DIR}/doctest) +target_include_directories(dsm_tests.out SYSTEM PRIVATE ${doctest_SOURCE_DIR}/doctest ${spdlog_SOURCE_DIR}/include) diff --git a/test/Test_dynamics.cpp b/test/Test_dynamics.cpp index 3aa49ac5c..43dc137b6 100644 --- a/test/Test_dynamics.cpp +++ b/test/Test_dynamics.cpp @@ -272,13 +272,6 @@ TEST_CASE("Dynamics") { CHECK_EQ(dynamics.agents().at(2)->srcNodeId().value(), 118); } } - WHEN("We add agents without adding itineraries") { - THEN("An exception is thrown") { - std::unordered_map src{{0, 1.}}; - std::unordered_map dst{{10, 1.}}; - CHECK_THROWS_AS(dynamics.addAgentsRandomly(1, src, dst), std::invalid_argument); - } - } } } SUBCASE("addAgents") { @@ -328,8 +321,6 @@ TEST_CASE("Dynamics") { CHECK_THROWS_AS(dynamics.addAgents(0, 1), std::overflow_error); auto dummyAgent = Agent(0, 0); CHECK_THROWS_AS(dynamics.addAgent(dummyAgent), std::overflow_error); - CHECK_THROWS_AS(dynamics.addAgent(std::make_unique(dummyAgent)), - std::overflow_error); } } } diff --git a/test/Test_graph.cpp b/test/Test_graph.cpp index 598b8e014..a9b827854 100644 --- a/test/Test_graph.cpp +++ b/test/Test_graph.cpp @@ -524,11 +524,6 @@ TEST_CASE("Dijkstra") { auto result = graph.street(1, 2); THEN("The street is not found") { CHECK_FALSE(result); } } - WHEN("We search for the opposite of a not existing street") { - THEN("It throws an exception") { - CHECK_THROWS_AS(graph.oppositeStreet(3), std::invalid_argument); - } - } } }