diff --git a/CMakeLists.txt b/CMakeLists.txt index fd43acf2..fad84c56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,19 @@ cmake_minimum_required(VERSION 3.10) + # Read version from the package.xml file. file(READ ${CMAKE_CURRENT_SOURCE_DIR}/package.xml package_xml_str) + if(NOT package_xml_str MATCHES "([0-9]+.[0-9]+.[0-9]+)") message(FATAL_ERROR "Could not parse project version from package.xml. Aborting.") endif() + project(symmetri VERSION ${CMAKE_MATCH_1}) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-uninitialized -Wno-psabi -Wno-unused-parameter -Werror -Wall -Wextra -O3") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -pedantic") if(BUILD_TESTING) enable_testing() @@ -18,6 +21,8 @@ if(BUILD_TESTING) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address,undefined -fno-omit-frame-pointer -O0") elseif(TSAN_BUILD AND NOT ASAN_BUILD) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=thread -fno-omit-frame-pointer -O0") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -O3") endif() endif() @@ -26,7 +31,7 @@ add_subdirectory(symmetri) # some examples using the lib if(BUILD_EXAMPLES) - add_subdirectory(examples/flight) - add_subdirectory(examples/hello_world) - add_subdirectory(examples/model_benchmark) + add_subdirectory(examples/flight) + add_subdirectory(examples/hello_world) + add_subdirectory(examples/model_benchmark) endif() diff --git a/examples/flight/flight.cc b/examples/flight/flight.cc index 955966be..be88f3d4 100644 --- a/examples/flight/flight.cc +++ b/examples/flight/flight.cc @@ -5,45 +5,32 @@ #include "symmetri/parsers.h" #include "symmetri/symmetri.h" +#include "symmetri/utilities.hpp" #include "transition.hpp" +using namespace symmetri; /** * @brief We want to use the Foo class with Symmetri; Foo has nice * functionalities such as Pause and Resume and it can also get * preempted/cancelled. We need to define functions to let Symmetri use these * functionalities. It is as simple by creating specialized version of the - * fire/cancel/isDirect/pause/resume functions. One does not need to implement + * fire/cancel/pause/resume functions. One does not need to implement * all - if nothing is defined, a default version is used. * */ -namespace symmetri { -template <> Result fire(const Foo &f) { return f.fire() ? Result{{}, State::Error} : Result{{}, State::Completed}; } -template <> Result cancel(const Foo &f) { f.cancel(); return {{}, State::UserExit}; } -template <> -bool isDirect(const Foo &) { - return false; -} - -template <> -void pause(const Foo &f) { - f.pause(); -} +void pause(const Foo &f) { f.pause(); } -template <> -void resume(const Foo &f) { - f.resume(); -} -} // namespace symmetri +void resume(const Foo &f) { f.resume(); } /** * @brief A simple printer for the eventlog @@ -82,17 +69,16 @@ int main(int, char *argv[]) { // Here we create the first PetriNet based on composing pnml1 and pnml2 // using flat composition. The associated transitions are two instance of // the Foo-class. - symmetri::PetriNet subnet({pnml1, pnml2}, {{"P2", 1}}, - {{"T0", Foo("SubFoo")}, {"T1", Foo("SubBar")}}, {}, - "SubNet", pool); + PetriNet subnet({pnml1, pnml2}, {{"P2", 1}}, + {{"T0", Foo("SubFoo")}, {"T1", Foo("SubBar")}}, {}, "SubNet", + pool); // We create another PetriNet by flatly composing all three petri nets. // Again we have 2 Foo-transitions, and the first transition (T0) is the // subnet. This show how you can also nest PetriNets. - symmetri::PetriNet bignet( - {pnml1, pnml2, pnml3}, {{"P3", 5}}, - {{"T0", subnet}, {"T1", Foo("Bar")}, {"T2", Foo("Foo")}}, {}, "RootNet", - pool); + PetriNet bignet({pnml1, pnml2, pnml3}, {{"P3", 5}}, + {{"T0", subnet}, {"T1", Foo("Bar")}, {"T2", Foo("Foo")}}, {}, + "RootNet", pool); // a flag to check if we are running std::atomic running(true); @@ -100,11 +86,12 @@ int main(int, char *argv[]) { // a thread that polls the eventlog and writes it to a file auto gantt = std::thread([&] { while (running) { - writeMermaidHtmlToFile( - symmetri::mermaidFromEventlog(bignet.getEventLog())); std::this_thread::sleep_for(std::chrono::seconds(3)); + if (!running) { + break; + } + writeMermaidHtmlToFile(symmetri::mermaidFromEventlog(getLog(bignet))); } - writeMermaidHtmlToFile(symmetri::mermaidFromEventlog(bignet.getEventLog())); }); // Parallel to the PetriNet execution, we run a thread through which we @@ -141,7 +128,8 @@ int main(int, char *argv[]) { // print the results and eventlog spdlog::info("Result of this net: {0}", printState(result)); printLog(el); - t.join(); // clean up gantt.join(); // clean up + t.join(); // clean up + writeMermaidHtmlToFile(symmetri::mermaidFromEventlog(el)); return 0; } diff --git a/examples/flight/mermaid.html b/examples/flight/mermaid.html deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/hello_world/main.cc b/examples/hello_world/main.cc index f5bc9bd9..f5988965 100644 --- a/examples/hello_world/main.cc +++ b/examples/hello_world/main.cc @@ -9,6 +9,7 @@ #include #include "symmetri/symmetri.h" +#include "symmetri/utilities.hpp" // Before main, we define a bunch of functions that can be bound to // transitions in the petri net. @@ -55,8 +56,8 @@ int main(int, char *argv[]) { // store based on the petri net. You can specifiy a final marking, the amount // of threads it can use (maximum amount of stuff it can do in parallel) and a // name so the net is easy to identifiy in a log. - symmetri::PetriNet net({pnml_path_start, pnml_path_passive}, {}, store, {}, - "CASE_X", pool); + PetriNet net({pnml_path_start, pnml_path_passive}, {}, store, {}, "CASE_X", + pool); // We use a simple boolean flag to terminate the threads once the net // finishes. Without it, these threads would prevent the program from cleanly @@ -84,8 +85,8 @@ int main(int, char *argv[]) { // becomes false. }); - auto [el, result] = symmetri::fire(net); // This function blocks until either - // the net completes, deadlocks + auto [el, result] = fire(net); // This function blocks until either + // the net completes, deadlocks // or user requests exit (ctrl-c) running.store(false); // We set this to false so the thread that we launched // gets interrupted. diff --git a/examples/model_benchmark/model_benchmark.cc b/examples/model_benchmark/model_benchmark.cc index c21ed69d..e166b237 100644 --- a/examples/model_benchmark/model_benchmark.cc +++ b/examples/model_benchmark/model_benchmark.cc @@ -2,6 +2,7 @@ #include "symmetri/parsers.h" #include "symmetri/symmetri.h" +#include "symmetri/utilities.hpp" int main(int, char *argv[]) { spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%f] [%^%l%$] [thread %t] %v"); @@ -16,14 +17,14 @@ int main(int, char *argv[]) { if (bolus) { store.insert({t, []() {}}); } else { - store.insert({t, nullptr}); + store.insert({t, DirectMutation{}}); } } - symmetri::PetriNet bignet(net, m0, {}, store, {}, "pluto", pool); + PetriNet bignet(net, m0, {}, store, {}, "pluto", pool); spdlog::info("start!"); const auto start_time = symmetri::Clock::now(); - auto [el, result] = symmetri::fire(bignet); // infinite loop + auto [el, result] = fire(bignet); // infinite loop const auto end_time = symmetri::Clock::now(); auto trans_count = el.size() / 2; auto delta_t = (double((end_time - start_time).count()) / 1e9); diff --git a/symmetri/CMakeLists.txt b/symmetri/CMakeLists.txt index 5c7ef66d..3c2bb4bb 100644 --- a/symmetri/CMakeLists.txt +++ b/symmetri/CMakeLists.txt @@ -1,5 +1,3 @@ -find_package(spdlog REQUIRED) - include(FetchContent) FetchContent_Declare( @@ -21,17 +19,17 @@ include_directories(include submodules) #lib add_library(${PROJECT_NAME} SHARED tasks.cpp - symmetri.cc model.cc model_utilities.cc pnml_parser.cc grml_parser.cc eventlog_parsers.cc types.cpp + symmetri.cc submodules/tinyxml2/tinyxml2.cpp ) add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) -target_link_libraries(${PROJECT_NAME} PUBLIC pthread PRIVATE concurrentqueue spdlog::spdlog) +target_link_libraries(${PROJECT_NAME} PUBLIC pthread PRIVATE concurrentqueue) include(GenerateExportHeader) generate_export_header(${PROJECT_NAME}) diff --git a/symmetri/eventlog_parsers.cc b/symmetri/eventlog_parsers.cc index f37bc933..febacd1d 100644 --- a/symmetri/eventlog_parsers.cc +++ b/symmetri/eventlog_parsers.cc @@ -31,7 +31,7 @@ std::string mermaidFromEventlog(symmetri::Eventlog el) { std::stringstream mermaid; mermaid << "\n---\ndisplayMode : compact\n---\ngantt\ntitle A Gantt " - "Diagram\ndateFormat x\naxisFormat \%H:\%M:\%S\n"; + "Diagram\ndateFormat x\naxisFormat %H:%M:%S\n"; std::string current_section(""); for (auto it = el.begin(); std::next(it) != el.end(); it = std::next(it)) { const auto &start = *it; diff --git a/symmetri/include/symmetri/polytransition.h b/symmetri/include/symmetri/polytransition.h index 4270b440..0b566b23 100644 --- a/symmetri/include/symmetri/polytransition.h +++ b/symmetri/include/symmetri/polytransition.h @@ -3,7 +3,8 @@ #include #include "symmetri/types.h" -namespace symmetri { + +struct DirectMutation {}; /** * @brief Checks if the transition-function can be invoked. @@ -15,7 +16,7 @@ namespace symmetri { */ template bool isDirect(const T &) { - return !std::is_invocable_v; + return false; } /** @@ -27,8 +28,8 @@ bool isDirect(const T &) { * @return Result */ template -Result cancel(const T &) { - return {{}, State::UserExit}; +symmetri::Result cancel(const T &) { + return {{}, symmetri::State::UserExit}; } /** @@ -58,24 +59,28 @@ void resume(const T &) {} * possible eventlog of the transition. */ template -Result fire(const T &transition) { - if constexpr (!std::is_invocable_v) { - return {{}, State::Completed}; - } else if constexpr (std::is_same_v) { +symmetri::Result fire(const T &transition) { + if constexpr (std::is_same_v) { + return {{}, symmetri::State::Completed}; + } else if constexpr (std::is_same_v) { return {{}, transition()}; - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { return transition(); } else { transition(); - return {{}, State::Completed}; + return {{}, symmetri::State::Completed}; } } template -Eventlog getLog(const T &) { +symmetri::Eventlog getLog(const T &) { return {}; } +namespace symmetri { + /** * @brief PolyTransition is a wrapper around any type that you want to tie to a * transition. Typically this is an invokable object, such as a function, that diff --git a/symmetri/include/symmetri/symmetri.h b/symmetri/include/symmetri/symmetri.h index 7b2a5df2..62924833 100644 --- a/symmetri/include/symmetri/symmetri.h +++ b/symmetri/include/symmetri/symmetri.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -8,6 +7,18 @@ #include "symmetri/tasks.h" #include "symmetri/types.h" +/** + * @brief The PetriNet class is a class that can create, configure and + * run a Petri net. + * + */ +class PetriNet; +symmetri::Result fire(const PetriNet &); +symmetri::Result cancel(const PetriNet &); +void pause(const PetriNet &); +void resume(const PetriNet &); +symmetri::Eventlog getLog(const PetriNet &); + namespace symmetri { /** * @brief A Store is a mapping from Transitions, represented by a string that is @@ -25,11 +36,8 @@ using Store = std::unordered_map; */ struct Petri; -/** - * @brief The PetriNet class is a class that can create, configure and - * run a Petri net. - * - */ +} // namespace symmetri + class PetriNet final { public: /** @@ -43,9 +51,9 @@ class PetriNet final { * @param stp */ PetriNet(const std::set &path_to_pnml, - const Marking &final_marking, const Store &store, - const PriorityTable &priority, const std::string &case_id, - std::shared_ptr stp); + const symmetri::Marking &final_marking, const symmetri::Store &store, + const symmetri::PriorityTable &priority, const std::string &case_id, + std::shared_ptr stp); /** * @brief Construct a new PetriNet object from a set of paths to @@ -58,8 +66,9 @@ class PetriNet final { * @param stp */ PetriNet(const std::set &path_to_grml, - const Marking &final_marking, const Store &store, - const std::string &case_id, std::shared_ptr stp); + const symmetri::Marking &final_marking, const symmetri::Store &store, + const std::string &case_id, + std::shared_ptr stp); /** * @brief Construct a new PetriNet object from a net and initial marking @@ -72,9 +81,10 @@ class PetriNet final { * @param case_id * @param stp */ - PetriNet(const Net &net, const Marking &m0, const Marking &final_marking, - const Store &store, const PriorityTable &priority, - const std::string &case_id, std::shared_ptr stp); + PetriNet(const symmetri::Net &net, const symmetri::Marking &m0, + const symmetri::Marking &final_marking, const symmetri::Store &store, + const symmetri::PriorityTable &priority, const std::string &case_id, + std::shared_ptr stp); /** * @brief register transition gives a handle to manually force a transition to @@ -89,27 +99,7 @@ class PetriNet final { std::function registerTransitionCallback( const std::string &transition) const noexcept; - /** - * @brief Get the Event Log object. If the Petri net is running, this call is - * blocking as it is executed on the Petri net execution loop. Otherwise it - * directly returns the log. - * - * - * @return Eventlog - */ - Eventlog getEventLog() const noexcept; - - /** - * @brief Get the State, represented by a vector of *active* transitions (who - * can still produce reducers and hence marking mutations) and the *current - * marking*. If the Petri net is running, this call is blocking as it is - * executed on the Petri net execution loop. Otherwise it directly returns the - * state. - * - * @return std::pair, std::vector> - */ - std::pair, std::vector> getState() - const noexcept; + std::vector getMarking() const noexcept; /** * @brief reuseApplication resets the application such that the same net can @@ -119,59 +109,14 @@ class PetriNet final { */ bool reuseApplication(const std::string &case_id); - /** - * @brief Fire for a PetriNet means that it executes the Petri net until it - * reaches a final marking, deadlocks or is preempted by a user. - * - * @return Result - */ - friend Result fire(const PetriNet &app); - - /** - * @brief cancel interrupts and stops the Petri net execution and - * calls cancel on all child transitions that are active. If transitions do - * not have a cancel functionality implemented, they will not be cancelled. - * Their reducers however will not be processed. - * - * @return Result - */ - friend Result cancel(const PetriNet &app); - - /** - * @brief pause interrupts and pauses the Petri net execution and - * calls pause on all child transitions that are active. The Petri net will - * still consume reducers produced by finished transitions but it will not - * queue new transitions for execution. This mostly happens when active - * transitions do not have a pause-functionality implemented. - * - * @param app - */ - friend void pause(const PetriNet &app); - - /** - * @brief resume breaks the pause and immediately will try to fire all - * possible transitions. It will also call resume on all active transitions. - * - */ - friend void resume(const PetriNet &app); - - friend Eventlog getLog(const PetriNet &app); + friend symmetri::Result(::fire)(const PetriNet &); + friend symmetri::Result(::cancel)(const PetriNet &); + friend void(::pause)(const PetriNet &); + friend void(::resume)(const PetriNet &); + friend symmetri::Eventlog(::getLog)(const PetriNet &); private: - std::shared_ptr impl; ///< Pointer to the implementation, all - ///< information is stored in Petri - std::function - register_functor; ///< At PetriNet construction this function is - ///< created. It can be used to assign a trigger to - ///< transitions - allowing the user to invoke a - ///< transition without meeting the pre-conditions. + const std::shared_ptr + impl; ///< Pointer to the implementation, all + ///< information is stored in Petri }; - -Result fire(const PetriNet &); -Result cancel(const PetriNet &); -void pause(const PetriNet &); -void resume(const PetriNet &); -bool isDirect(const PetriNet &); -Eventlog getLog(const PetriNet &); - -} // namespace symmetri diff --git a/symmetri/include/symmetri/types.h b/symmetri/include/symmetri/types.h index 228864ff..1fbfa548 100644 --- a/symmetri/include/symmetri/types.h +++ b/symmetri/include/symmetri/types.h @@ -24,6 +24,7 @@ enum class State { Completed, ///< The transition completed as expected Deadlock, ///< The transition deadlocked UserExit, ///< The transition or interrupted and possibly stopped + Paused, ///< The transition is paused Error ///< None of the above }; @@ -59,72 +60,4 @@ using PriorityTable = std::vector>; ///< Priority is limited from ///< -128 to 127 -/** - * @brief Checks if the markings are exactly the same. Note that this uses a - * different type for Marking compared to the Marking type used to - * construct a net (an unordered map of strings). In this format the amount of - * tokens in a particular place is represented by how often that place occurs in - * the vector. For example: {"A","A","B"} is a marking with two tokens in place - * "A" and one token in place "B". This format does not have the overhead of - * mentioning all empty places. - * - * @tparam T - * @param m1 - * @param m2 - * @return true - * @return false - */ -template -bool MarkingEquality(const std::vector& m1, const std::vector& m2); - -/** - * @brief Checks if marking is at least a subset of final_marking. Note - * that this uses a different type for Marking compared to the Marking - * type used to construct a net (an unordered map of strings). In this - * format the amount of tokens in a particular place is represented by how often - * that place occurs in the vector. For example: {"A","A","B"} is a marking with - * two tokens in place "A" and one token in place "B". This format does not have - * the overhead of mentioning all empty places. - * - * @tparam T - * @param marking - * @param final_marking - * @return true - * @return false - */ -template -bool MarkingReached(const std::vector& marking, - const std::vector& final_marking); - -/** - * @brief Checks if two petri-nets have equal amount of arcs between places - * and transitions of the same name. - * - * @param net1 - * @param net2 - * @return true - * @return false - */ -bool stateNetEquality(const Net& net1, const Net& net2); - -/** - * @brief Calculates a hash given an event log. This hash is only influenced by - * the order of the completions of transitions and if the output of those - * transitions is Completed, or something else. - * - * @param event_log An eventlog, can both be from a terminated or a still active - * net. - * @return size_t The hashed result. - */ -size_t calculateTrace(const Eventlog& event_log) noexcept; - -/** - * @brief A convenience function to get a string representation of the - * state-enum. - * - * @param s The State - * @return std::string The State as a human readable string. - */ -std::string printState(State s) noexcept; - } // namespace symmetri diff --git a/symmetri/include/symmetri/utilities.hpp b/symmetri/include/symmetri/utilities.hpp new file mode 100644 index 00000000..895abf5d --- /dev/null +++ b/symmetri/include/symmetri/utilities.hpp @@ -0,0 +1,95 @@ +#pragma once +#include + +#include "symmetri/types.h" + +namespace symmetri { + +/** + * @brief Checks if the markings are exactly the same. Note that this uses a + * different type for Marking compared to the Marking type used to + * construct a net (an unordered map of strings). In this format the amount of + * tokens in a particular place is represented by how often that place occurs in + * the vector. For example: {"A","A","B"} is a marking with two tokens in place + * "A" and one token in place "B". This format does not have the overhead of + * mentioning all empty places. + * + * @tparam T + * @param m1 + * @param m2 + * @return true + * @return false + */ +template +bool MarkingEquality(const std::vector& m1, const std::vector& m2) { + auto m1_sorted = m1; + auto m2_sorted = m2; + std::sort(m1_sorted.begin(), m1_sorted.end()); + std::sort(m2_sorted.begin(), m2_sorted.end()); + return m1_sorted == m2_sorted; +} + +/** + * @brief Checks if marking is at least a subset of final_marking. Note + * that this uses a different type for Marking compared to the Marking + * type used to construct a net (an unordered map of strings). In this + * format the amount of tokens in a particular place is represented by how often + * that place occurs in the vector. For example: {"A","A","B"} is a marking with + * two tokens in place "A" and one token in place "B". This format does not have + * the overhead of mentioning all empty places. + * + * @tparam T + * @param marking + * @param final_marking + * @return true + * @return false + */ +template +bool MarkingReached(const std::vector& marking, + const std::vector& final_marking) { + if (final_marking.empty()) { + return false; + } + auto unique = final_marking; + std::sort(unique.begin(), unique.end()); + auto last = std::unique(unique.begin(), unique.end()); + unique.erase(last, unique.end()); + + return std::all_of(std::begin(unique), std::end(unique), [&](const auto& p) { + return std::count(marking.begin(), marking.end(), p) == + std::count(final_marking.begin(), final_marking.end(), p); + }); +} + +/** + * @brief Checks if two petri-nets have equal amount of arcs between places + * and transitions of the same name. + * + * @param net1 + * @param net2 + * @return true + * @return false + */ +bool stateNetEquality(const Net& net1, const Net& net2); + +/** + * @brief Calculates a hash given an event log. This hash is only influenced by + * the order of the completions of transitions and if the output of those + * transitions is Completed, or something else. + * + * @param event_log An eventlog, can both be from a terminated or a still active + * net. + * @return size_t The hashed result. + */ +size_t calculateTrace(const Eventlog& event_log) noexcept; + +/** + * @brief A convenience function to get a string representation of the + * state-enum. + * + * @param s The State + * @return std::string The State as a human readable string. + */ +std::string printState(State s) noexcept; + +} // namespace symmetri diff --git a/symmetri/model.cc b/symmetri/model.cc index b280ef3d..f204c727 100644 --- a/symmetri/model.cc +++ b/symmetri/model.cc @@ -1,7 +1,12 @@ #include "model.h" -namespace symmetri { +bool isDirect(const DirectMutation &) { return true; } + +symmetri::Result fire(const DirectMutation &) { + return {{}, symmetri::State::Completed}; +} +namespace symmetri { std::tuple, std::vector, std::vector> convert(const Net &_net, const Store &_store) { @@ -76,9 +81,17 @@ std::vector createPriorityLookup( return priority; } -Model::Model(const Net &_net, const Store &store, - const PriorityTable &_priority, const Marking &M0) - : event_log({}), is_paused(false) { +Petri::Petri(const Net &_net, const Store &store, + const PriorityTable &_priority, const Marking &M0, + const Marking &final_marking, const std::string &case_id, + std::shared_ptr stp) + : event_log({}), + state(State::Scheduled), + case_id(case_id), + thread_id_(std::nullopt), + active_reducers( + std::make_shared>(32)), + pool(stp) { event_log.reserve(1000); std::tie(net.transition, net.place, net.store) = convert(_net, store); std::tie(net.input_n, net.output_n) = populateIoLookups(_net, net.place); @@ -87,9 +100,10 @@ Model::Model(const Net &_net, const Store &store, net.priority = createPriorityLookup(net.transition, _priority); initial_tokens = toTokens(M0); tokens_n = initial_tokens; + final_marking_n = toTokens(final_marking); } -std::vector Model::toTokens(const Marking &marking) const noexcept { +std::vector Petri::toTokens(const Marking &marking) const noexcept { std::vector tokens; for (const auto &[p, c] : marking) { for (int i = 0; i < c; i++) { @@ -99,7 +113,7 @@ std::vector Model::toTokens(const Marking &marking) const noexcept { return tokens; } -std::vector Model::getFireableTransitions() const { +std::vector Petri::getFireableTransitions() const { auto possible_transition_list_n = possibleTransitions(tokens_n, net.p_to_ts_n, net.priority); std::vector fireable_transitions; @@ -113,11 +127,11 @@ std::vector Model::getFireableTransitions() const { return fireable_transitions; } -bool Model::Fire( +bool Petri::Fire( const size_t t, const std::shared_ptr> &reducers, - std::shared_ptr pool, const std::string &case_id) { + const std::string &case_id) { auto timestamp = Clock::now(); // deduct the marking for (const size_t place : net.input_n[t]) { @@ -141,31 +155,28 @@ bool Model::Fire( active_transitions_n.push_back(t); event_log.push_back({case_id, transition, State::Scheduled, timestamp}); pool->push([=] { - reducers->enqueue( - createReducerForTransition(t, transition, task, case_id, reducers)); + reducers->enqueue(fireTransition(t, transition, task, case_id, reducers)); }); return false; } } -bool Model::fire( +bool Petri::fire( const Transition &t, const std::shared_ptr> &reducers, - std::shared_ptr pool, const std::string &case_id) { + const std::string &case_id) { auto it = std::find(net.transition.begin(), net.transition.end(), t); return it != net.transition.end() && canFire(net.input_n[std::distance(net.transition.begin(), it)], tokens_n) && - !Fire(std::distance(net.transition.begin(), it), reducers, pool, - case_id); + !Fire(std::distance(net.transition.begin(), it), reducers, case_id); } -void Model::fireTransitions( +void Petri::fireTransitions( const std::shared_ptr> &reducers, - std::shared_ptr pool, bool run_all, - const std::string &case_id) { + bool run_all, const std::string &case_id) { // find possible transitions auto possible_transition_list_n = possibleTransitions(tokens_n, net.p_to_ts_n, net.priority); @@ -187,7 +198,7 @@ void Model::fireTransitions( do { // if Fire returns true, update the possible transition list - if (Fire(possible_transition_list_n.front(), reducers, pool, case_id)) { + if (Fire(possible_transition_list_n.front(), reducers, case_id)) { possible_transition_list_n = possibleTransitions(tokens_n, net.p_to_ts_n, net.priority); } @@ -198,11 +209,7 @@ void Model::fireTransitions( return; } -std::pair, std::vector> Model::getState() const { - return {getActiveTransitions(), getMarking()}; -} - -std::vector Model::getMarking() const { +std::vector Petri::getMarking() const { std::vector marking; marking.reserve(tokens_n.size()); std::transform(tokens_n.cbegin(), tokens_n.cend(), @@ -211,7 +218,19 @@ std::vector Model::getMarking() const { return marking; } -std::vector Model::getActiveTransitions() const { +Eventlog Petri::getLog() const { + Eventlog eventlog = event_log; + // get event log from parent nets: + for (size_t pt_idx : active_transitions_n) { + auto sub_el = ::getLog(net.store[pt_idx]); + if (!sub_el.empty()) { + eventlog.insert(eventlog.end(), sub_el.begin(), sub_el.end()); + } + } + return eventlog; +} + +std::vector Petri::getActiveTransitions() const { std::vector transition_list; if (active_transitions_n.size() > 0) { transition_list.reserve(active_transitions_n.size()); diff --git a/symmetri/model.h b/symmetri/model.h index 154ff504..1ccd3e9d 100644 --- a/symmetri/model.h +++ b/symmetri/model.h @@ -1,15 +1,18 @@ #pragma once #include -#include #include #include +#include #include "small_vector.hpp" #include "symmetri/polytransition.h" #include "symmetri/tasks.h" #include "symmetri/types.h" +bool isDirect(const DirectMutation &); +symmetri::Result fire(const DirectMutation &); + namespace symmetri { using SmallVector = gch::small_vector; @@ -51,8 +54,8 @@ gch::small_vector possibleTransitions( */ bool canFire(const SmallVector &pre, const std::vector &tokens); -struct Model; -using Reducer = std::function; +struct Petri; +using Reducer = std::function; /** * @brief Create a Reducer For Transition object @@ -63,21 +66,21 @@ using Reducer = std::function; * @param reducers * @return Reducer */ -Reducer createReducerForTransition( +Reducer fireTransition( size_t T_i, const std::string &transition, const PolyTransition &task, const std::string &case_id, const std::shared_ptr> &reducers); /** - * @brief Model is a different data structure to encode the Petri net. It is + * @brief Petri is a different data structure to encode the Petri net. It is * optimized for calculating the fireable transitions and quick lookups in * ordered vectors. * */ -struct Model { +struct Petri { /** - * @brief Construct a new Model from a multiset description of a Petri + * @brief Construct a new Petri from a multiset description of a Petri * net, a lookup table for the transitions, optional priorities and an initial * marking. A lot of conversion work is done in the constructor, so you should * avoid creating Models during the run-time of a Petri application. @@ -87,14 +90,23 @@ struct Model { * @param priority * @param M0 */ - explicit Model(const Net &net, const Store &store, - const PriorityTable &priority, const Marking &M0); - ~Model() noexcept = default; - Model(Model const &) = delete; - Model(Model &&) noexcept = delete; - Model &operator=(Model const &) = default; - Model &operator=(Model &&) noexcept = default; + explicit Petri(const Net &net, const Store &store, + const PriorityTable &priority, const Marking &M0, + const Marking &final_marking, const std::string &case_id, + std::shared_ptr stp); + ~Petri() noexcept = default; + Petri(Petri const &) = delete; + Petri(Petri &&) noexcept = delete; + Petri &operator=(Petri const &) = delete; + Petri &operator=(Petri &&) noexcept = delete; + /** + * @brief outputs the marking as a vector of tokens; e.g. [1 1 1 0 5] means 3 + * tokens in place 1, 1 token in place 0 and 1 token in place 5. + * + * @param marking + * @return std::vector + */ std::vector toTokens(const Marking &marking) const noexcept; /** @@ -107,6 +119,14 @@ struct Model { */ std::vector getMarking() const; + /** + * @brief get the current eventlog, also copies in all child eventlogs of + * active petri nets. + * + * @return Eventlog + */ + Eventlog getLog() const; + /** * @brief Get a vector of transitions that are *active*. Active means that * they are either in the transition queue or its transition has been fired. @@ -126,14 +146,6 @@ struct Model { */ std::vector getFireableTransitions() const; - /** - * @brief Get the State; this is a pair of active transitions *and* the - * current marking. - * - * @return std::pair, std::vector> - */ - std::pair, std::vector> getState() const; - /** * @brief Try to fire a single transition. * @@ -148,7 +160,6 @@ struct Model { bool fire(const Transition &t, const std::shared_ptr> &reducers, - std::shared_ptr polymorphic_actions, const std::string &case_id = "undefined_case_id"); /** @@ -164,8 +175,7 @@ struct Model { void fireTransitions( const std::shared_ptr> &reducers, - std::shared_ptr polymorphic_actions, bool run_all = true, - const std::string &case_id = "undefined_case_id"); + bool run_all = true, const std::string &case_id = "undefined_case_id"); struct { std::vector @@ -189,17 +199,24 @@ struct Model { ///< `transition` so it is compatible with index lookup. } net; ///< Is a data-oriented design of a Petri net - std::vector initial_tokens; ///< The intial marking + std::vector initial_tokens; ///< The initial marking std::vector tokens_n; ///< The current marking + std::vector final_marking_n; ///< The final marking std::vector active_transitions_n; ///< List of active transitions Eventlog event_log; ///< The most actual event_log - bool is_paused; + State state; + std::string case_id; + std::atomic> + thread_id_; ///< The id of the thread from which run is called. + + std::shared_ptr> active_reducers; + std::shared_ptr pool; private: /** * @brief fires a transition. * - * @param t + * @param t transition as index in transition vector * @param reducers * @param polymorphic_actions * @param case_id @@ -209,7 +226,6 @@ struct Model { bool Fire(const size_t t, const std::shared_ptr> &reducers, - std::shared_ptr polymorphic_actions, const std::string &case_id); }; diff --git a/symmetri/model_utilities.cc b/symmetri/model_utilities.cc index d99236ff..c11b15c5 100644 --- a/symmetri/model_utilities.cc +++ b/symmetri/model_utilities.cc @@ -12,7 +12,7 @@ bool canFire(const SmallVector &pre, const std::vector &tokens) { return std::count(tokens.begin(), tokens.end(), m_p) >= std::count(pre.begin(), pre.end(), m_p); }); -}; +} gch::small_vector possibleTransitions( const std::vector &tokens, @@ -37,8 +37,9 @@ gch::small_vector possibleTransitions( return possible_transition_list_n; } -Reducer processTransition(size_t t_i, const Eventlog &ev, State result) { - return [=](Model &&model) { +Reducer createReducerForTransition(size_t t_i, const Eventlog &ev, + State result) { + return [=](Petri &model) { // if it is in the active transition set it means it is finished and we // should process it. auto it = std::find(model.active_transitions_n.begin(), @@ -52,25 +53,23 @@ Reducer processTransition(size_t t_i, const Eventlog &ev, State result) { } model.event_log.insert(model.event_log.end(), ev.begin(), ev.end()); }; - return std::ref(model); }; } -Reducer createReducerForTransition( +Reducer fireTransition( size_t t_i, const std::string &transition, const PolyTransition &task, const std::string &case_id, const std::shared_ptr> &reducers) { const auto start_time = Clock::now(); - reducers->enqueue([=](Model &&model) { + reducers->enqueue([=](Petri &model) { model.event_log.push_back( {case_id, transition, State::Started, start_time}); - return std::ref(model); }); auto [ev, res] = fire(task); ev.push_back({case_id, transition, res, Clock::now()}); - return processTransition(t_i, ev, res); + return createReducerForTransition(t_i, ev, res); } } // namespace symmetri diff --git a/symmetri/symmetri.cc b/symmetri/symmetri.cc index 11551255..f8db5888 100644 --- a/symmetri/symmetri.cc +++ b/symmetri/symmetri.cc @@ -1,20 +1,14 @@ #include "symmetri/symmetri.h" -#include -#include - -#include #include #include #include -#include -#include -#include +#include #include -#include #include "model.h" #include "symmetri/parsers.h" +#include "symmetri/utilities.hpp" namespace symmetri { @@ -30,377 +24,179 @@ unsigned int getThreadId() { std::hash()(std::this_thread::get_id())); } -using namespace moodycamel; - -bool areAllTransitionsInStore(const Store &store, const Net &net) noexcept { - return std::all_of(net.cbegin(), net.cend(), [&store](const auto &p) { - const auto &t = std::get<0>(p); +void areAllTransitionsInStore(const Store &store, const Net &net) { + for (const auto &pair : net) { bool store_has_transition = std::find_if(store.begin(), store.end(), [&](const auto &e) { - return e.first == t; + return e.first == pair.first; }) != store.end(); if (!store_has_transition) { - spdlog::error("Transition {0} is not in store", t); - } - return store_has_transition; - }); -} - -/** - * @brief Petri is the class that holds the implementation of the Petri net. It - * holds pointers to the reducer queue and that thread pool. Calling `fire()` on - * this class will do the *actual* execution of the Petri net. - * - */ -struct Petri { - Model m; ///< The Petri net model - std::atomic> - thread_id_; ///< The id of the thread from which run is called. - const Marking m0_; ///< The initial marking for this instance - const Net net_; ///< The net - const PriorityTable priorities_; ///< The priority table for this instance - const std::vector - final_marking; ///< The net will stop queueing reducers - ///< once the marking has been reached - const std::shared_ptr stp; - std::array>, 2> reducers; - unsigned int reducer_selector; - std::string case_id; ///< The case id of this particular Petri instance - std::atomic early_exit; ///< once it is true, no more new transitions - ///< will be queued and the run will exit. - - /** - * @brief Construct a new Petri object. Most importantly, it also creates the - * reducer queue and exposes the `run` function to actually run the petri - * net. - * - * @param net - * @param m0 - * @param stp - * @param final_marking - * @param store - * @param priority - * @param case_id - */ - Petri(const Net &net, const Marking &m0, std::shared_ptr stp, - const Marking &final_marking, const Store &store, - const PriorityTable &priorities, const std::string &case_id) - : m(net, store, priorities, m0), - thread_id_(std::nullopt), - m0_(m0), - net_(net), - priorities_(priorities), - final_marking(m.toTokens(final_marking)), - stp(stp), - reducers({std::make_shared>(32), - std::make_shared>(32)}), - reducer_selector(0), - case_id(case_id), - early_exit(false) {} - - const std::shared_ptr> &setFreshQueue() { - // increment index to get the already prepared queue. - reducer_selector = reducer_selector > 0 ? 0 : 1; - // create a new queue for later use at the next index. - stp->push([reducer = reducers[reducer_selector > 0 ? 0 : 1]]() mutable { - reducer.reset(new BlockingConcurrentQueue{32}); - }); - return reducers[reducer_selector]; - } - - /** - * @brief Get the Event Log object - * - * @return Eventlog - */ - Eventlog getEventLog() const noexcept { - const auto maybe_thread_id = thread_id_.load(); - if (maybe_thread_id && maybe_thread_id.value() != getThreadId()) { - std::promise el; - std::future el_getter = el.get_future(); - reducers[reducer_selector]->enqueue([&](Model &&model) { - auto eventlog = model.event_log; - // get event log from parent nets: - for (size_t pt_idx : model.active_transitions_n) { - auto sub_el = getLog(model.net.store[pt_idx]); - if (!sub_el.empty()) { - eventlog.insert(eventlog.end(), sub_el.begin(), sub_el.end()); - } - } - el.set_value(std::move(eventlog)); - return std::ref(model); - }); - return el_getter.get(); - } else { - return m.event_log; - } - } - - std::pair, std::vector> getState() - const noexcept { - const auto maybe_thread_id = thread_id_.load(); - if (maybe_thread_id && maybe_thread_id.value() != getThreadId()) { - std::promise, std::vector>> - state; - auto getter = state.get_future(); - reducers[reducer_selector]->enqueue([&](Model &&model) { - state.set_value(model.getState()); - return std::ref(model); - }); - return getter.get(); - } else { - return m.getState(); + std::string err = "transition " + pair.first + " is not in store"; + throw std::runtime_error(err); } } +} - std::vector getFireableTransitions() const noexcept { - const auto maybe_thread_id = thread_id_.load(); - if (maybe_thread_id && maybe_thread_id.value() != getThreadId()) { - std::promise> transitions; - std::future> transitions_getter = - transitions.get_future(); - reducers[reducer_selector]->enqueue([&](Model &&model) { - transitions.set_value(model.getFireableTransitions()); - return std::ref(model); - }); - return transitions_getter.get(); - } else { - return m.getFireableTransitions(); - } - }; -}; +} // namespace symmetri -/** - * @brief A factory function that creates a Petri and a handler that allows to - * register triggers to functions. - * - * @param net - * @param m0 - * @param final_marking - * @param store - * @param priority - * @param case_id - * @param stp - * @return std::tuple, std::function> - */ -std::tuple, std::function> -create(const Net &net, const Marking &m0, const Marking &final_marking, - const Store &store, const PriorityTable &priority, - const std::string &case_id, std::shared_ptr stp) { - auto impl = std::make_shared(net, m0, stp, final_marking, store, - priority, case_id); - return {impl, [=](const Transition &t) { - if (impl->thread_id_.load()) { - const auto &reducer = impl->reducers[impl->reducer_selector]; - reducer->enqueue([=](Model &&m) { - const auto t_index = toIndex(m.net.transition, t); - m.active_transitions_n.push_back(t_index); - reducer->enqueue(createReducerForTransition( - t_index, m.net.transition[t_index], m.net.store[t_index], - impl->case_id, reducer)); - return std::ref(m); - }); - } - }}; -} +using namespace symmetri; PetriNet::PetriNet(const std::set &files, const Marking &final_marking, const Store &store, const PriorityTable &priorities, const std::string &case_id, - std::shared_ptr stp) { - const auto [net, m0] = readPnml(files); - if (areAllTransitionsInStore(store, net)) { - std::tie(impl, register_functor) = - create(net, m0, final_marking, store, priorities, case_id, stp); - } -} + std::shared_ptr stp) + : impl([&] { + const auto [net, m0] = readPnml(files); + areAllTransitionsInStore(store, net); + return std::make_shared(net, store, priorities, m0, + final_marking, case_id, stp); + }()) {} PetriNet::PetriNet(const std::set &files, const Marking &final_marking, const Store &store, - const std::string &case_id, - std::shared_ptr stp) { - const auto [net, m0, priorities] = readGrml(files); - if (areAllTransitionsInStore(store, net)) { - std::tie(impl, register_functor) = - create(net, m0, final_marking, store, priorities, case_id, stp); - } -} + const std::string &case_id, std::shared_ptr stp) + : impl([&] { + const auto [net, m0, priorities] = readGrml(files); + areAllTransitionsInStore(store, net); + return std::make_shared(net, store, priorities, m0, + final_marking, case_id, stp); + }()) {} PetriNet::PetriNet(const Net &net, const Marking &m0, const Marking &final_marking, const Store &store, const PriorityTable &priorities, const std::string &case_id, - std::shared_ptr stp) { - if (areAllTransitionsInStore(store, net)) { - std::tie(impl, register_functor) = - create(net, m0, final_marking, store, priorities, case_id, stp); - } -} - -Eventlog PetriNet::getEventLog() const noexcept { return impl->getEventLog(); }; - -std::pair, std::vector> PetriNet::getState() - const noexcept { - return impl->getState(); + std::shared_ptr stp) + : impl(std::make_shared(net, store, priorities, m0, final_marking, + case_id, stp)) { + areAllTransitionsInStore(store, net); } std::function PetriNet::registerTransitionCallback( const Transition &transition) const noexcept { - return [transition, this] { register_functor(transition); }; + return [transition, this] { + if (impl->thread_id_.load()) { + impl->active_reducers->enqueue([=](Petri &m) { + if (m.thread_id_.load()) { + const auto t_index = toIndex(m.net.transition, transition); + m.active_transitions_n.push_back(t_index); + m.active_reducers->enqueue(fireTransition( + t_index, m.net.transition[t_index], m.net.store[t_index], + m.case_id, m.active_reducers)); + } + }); + } + }; } -bool PetriNet::reuseApplication(const std::string &new_case_id) { - if (impl) { - auto &petri = *impl; - if (petri.thread_id_.load().has_value() || new_case_id == petri.case_id) { - return false; - } - petri.case_id = new_case_id; - petri.thread_id_.store(std::nullopt); - return true; +std::vector PetriNet::getMarking() const noexcept { + if (impl->thread_id_.load()) { + std::promise> el; + std::future> el_getter = el.get_future(); + impl->active_reducers->enqueue( + [&](Petri &model) { el.set_value(model.getMarking()); }); + return el_getter.get(); } else { - return false; + return impl->getMarking(); } } -Result fire(const PetriNet &app) { - if (app.impl == nullptr) { - spdlog::error("Something went seriously wrong. Please send a bug report."); - return {{}, State::Error}; +bool PetriNet::reuseApplication(const std::string &new_case_id) { + if (!impl->thread_id_.load().has_value() && new_case_id != impl->case_id) { + impl->case_id = new_case_id; + return true; } + return false; +} - auto &petri = *app.impl; - auto &m = petri.m; - if (app.impl->thread_id_.load()) { - spdlog::warn( - "[{0}] is already active, it can not run it again before it is " - "finished.", - app.impl->case_id); - return {{}, State::Error}; +symmetri::Result fire(const PetriNet &app) { + if (app.impl->thread_id_.load().has_value()) { + return {{}, symmetri::State::Error}; } + auto &m = *app.impl; + m.thread_id_.store(symmetri::getThreadId()); // we are running! - auto &active_reducers = petri.reducers[petri.reducer_selector]; - petri.thread_id_.store(getThreadId()); - petri.early_exit.store(false); m.event_log.clear(); m.tokens_n = m.initial_tokens; - - bool waiting_for_resume = false; - Reducer f; + m.state = State::Started; + symmetri::Reducer f; + while (m.active_reducers->try_dequeue(f)) { /* get rid of old reducers */ + } // start! - m.fireTransitions(active_reducers, petri.stp, true, petri.case_id); - // get a reducer. Immediately, or wait a bit - while ((m.active_transitions_n.size() > 0 || m.is_paused) && - active_reducers->wait_dequeue_timed(f, -1)) { + m.fireTransitions(m.active_reducers, true, m.case_id); + while ((m.state == State::Started || m.state == State::Paused) && + m.active_reducers->wait_dequeue_timed(f, -1)) { do { - m = f(std::move(m)); - } while (!petri.early_exit.load() && active_reducers->try_dequeue(f)); + f(m); + } while (m.active_reducers->try_dequeue(f)); - if (MarkingReached(m.tokens_n, petri.final_marking) || - petri.early_exit.load()) { - // we're done - break; - } else if (!m.is_paused && !waiting_for_resume) { - // we're firing - m.fireTransitions(active_reducers, petri.stp, true, petri.case_id); - } else if (m.is_paused && !waiting_for_resume) { - // we've been asked to pause - waiting_for_resume = true; - for (const auto transition_index : m.active_transitions_n) { - pause(m.net.store.at(transition_index)); - } - } else if (!m.is_paused && waiting_for_resume) { - // we've been asked to resume - waiting_for_resume = false; - m.fireTransitions(active_reducers, petri.stp, true, petri.case_id); - for (const auto transition_index : m.active_transitions_n) { - resume(m.net.store.at(transition_index)); - } - } - } + m.state = symmetri::MarkingReached(m.tokens_n, m.final_marking_n) + ? State::Completed + : m.state; - // determine what was the reason we terminated. - State result; - if (petri.early_exit.load()) { - { - // populate that eventlog with child eventlog and possible cancelations. - for (const auto transition_index : m.active_transitions_n) { - auto [el, state] = cancel(m.net.store.at(transition_index)); - if (!el.empty()) { - m.event_log.insert(m.event_log.end(), el.begin(), el.end()); - } - m.event_log.push_back({petri.case_id, - m.net.transition[transition_index], state, - Clock::now()}); - } - result = State::UserExit; + if (m.state == State::Started) { + // we're firing + m.fireTransitions(m.active_reducers, true, m.case_id); + // if there's nothing to fire; we deadlocked + m.state = m.active_transitions_n.size() == 0 ? State::Deadlock : m.state; } - } else if (MarkingReached(m.tokens_n, petri.final_marking)) { - result = State::Completed; - } else if (m.active_transitions_n.empty()) { - result = State::Deadlock; - } else { - result = State::Error; } - + m.thread_id_.store(std::nullopt); // empty reducers m.active_transitions_n.clear(); - while (active_reducers->try_dequeue(f)) { - m = f(std::move(m)); + while (m.active_reducers->try_dequeue(f)) { + f(m); } - petri.thread_id_.store(std::nullopt); - petri.setFreshQueue(); - - return {m.event_log, result}; -}; + return {m.event_log, m.state}; +} -Result cancel(const PetriNet &app) { - const auto maybe_thread_id = app.impl->thread_id_.load(); - if (maybe_thread_id && maybe_thread_id.value() != getThreadId() && - !app.impl->early_exit.load()) { - std::mutex m; - std::condition_variable cv; - bool ready = false; - app.impl->reducers[app.impl->reducer_selector]->enqueue( - [=, &m, &cv, &ready](Model &&model) { - app.impl->early_exit.store(true); - { - std::lock_guard lk(m); - ready = true; - } - cv.notify_one(); - return std::ref(model); - }); - std::unique_lock lk(m); - cv.wait(lk, [&] { return ready; }); - } else { - app.impl->reducers[app.impl->reducer_selector]->enqueue([=](Model &&model) { - app.impl->early_exit.store(true); - return std::ref(model); - }); - } +symmetri::Result cancel(const PetriNet &app) { + app.impl->active_reducers->enqueue([=](symmetri::Petri &model) { + model.state = symmetri::State::UserExit; + // populate that eventlog with child eventlog and possible + // cancelations. + for (const auto transition_index : model.active_transitions_n) { + auto [el, state] = cancel(model.net.store.at(transition_index)); + if (!el.empty()) { + model.event_log.insert(model.event_log.end(), el.begin(), el.end()); + } + model.event_log.push_back({model.case_id, + model.net.transition[transition_index], state, + symmetri::Clock::now()}); + } + model.active_transitions_n.clear(); + }); - return {app.getEventLog(), State::UserExit}; + return {getLog(app), symmetri::State::UserExit}; } void pause(const PetriNet &app) { - app.impl->reducers[app.impl->reducer_selector]->enqueue([&](Model &&model) { - model.is_paused = true; - return std::ref(model); + app.impl->active_reducers->enqueue([](symmetri::Petri &model) { + model.state = State::Paused; + for (const auto transition_index : model.active_transitions_n) { + pause(model.net.store.at(transition_index)); + } }); -}; +} void resume(const PetriNet &app) { - app.impl->reducers[app.impl->reducer_selector]->enqueue([&](Model &&model) { - model.is_paused = false; - return std::ref(model); + app.impl->active_reducers->enqueue([](symmetri::Petri &model) { + model.state = State::Started; + for (const auto transition_index : model.active_transitions_n) { + resume(model.net.store.at(transition_index)); + } }); -}; - -bool isDirect(const PetriNet &) { return false; }; - -Eventlog getLog(const PetriNet &app) { return app.getEventLog(); } +} -} // namespace symmetri +symmetri::Eventlog getLog(const PetriNet &app) { + if (app.impl->thread_id_.load()) { + std::promise el; + std::future el_getter = el.get_future(); + app.impl->active_reducers->enqueue( + [&](Petri &model) { el.set_value(model.getLog()); }); + return el_getter.get(); + } else { + return app.impl->getLog(); + } +} diff --git a/symmetri/tests/test_bugs.cc b/symmetri/tests/test_bugs.cc index a4f1379c..1b50958a 100644 --- a/symmetri/tests/test_bugs.cc +++ b/symmetri/tests/test_bugs.cc @@ -2,6 +2,7 @@ #include #include "model.h" +#include "symmetri/utilities.hpp" using namespace symmetri; @@ -33,13 +34,12 @@ std::tuple testNet() { TEST_CASE("Firing the same transition before it can complete should work") { auto [net, store, priority, m0] = testNet(); - Model m(net, store, priority, m0); - + auto stp = std::make_shared(2); + Petri m(net, store, priority, m0, {}, "s", stp); auto reducers = std::make_shared>(4); - auto stp = std::make_shared(2); REQUIRE(m.active_transitions_n.empty()); - m.fireTransitions(reducers, stp, true); + m.fireTransitions(reducers, true); REQUIRE(m.getMarking().empty()); CHECK(m.getActiveTransitions() == std::vector{"t", "t"}); @@ -47,7 +47,7 @@ TEST_CASE("Firing the same transition before it can complete should work") { Reducer r; while (reducers->wait_dequeue_timed(r, std::chrono::milliseconds(250))) { - m = r(std::move(m)); + r(m); } { @@ -58,7 +58,7 @@ TEST_CASE("Firing the same transition before it can complete should work") { cv.notify_one(); reducers->wait_dequeue_timed(r, std::chrono::milliseconds(250)); - m = r(std::move(m)); + r(m); REQUIRE(MarkingEquality(m.getMarking(), {"Pb"})); // offending test, but fixed :-) @@ -70,7 +70,7 @@ TEST_CASE("Firing the same transition before it can complete should work") { } cv.notify_one(); reducers->wait_dequeue_timed(r, std::chrono::milliseconds(250)); - m = r(std::move(m)); + r(m); REQUIRE(MarkingEquality(m.getMarking(), {"Pb", "Pb"})); diff --git a/symmetri/tests/test_external_input.cc b/symmetri/tests/test_external_input.cc index d051b765..39228cae 100644 --- a/symmetri/tests/test_external_input.cc +++ b/symmetri/tests/test_external_input.cc @@ -11,7 +11,7 @@ void t() { i_ran.store(true); } TEST_CASE("Test external input.") { Net net = { {"t0", {{}, {"Pa"}}}, {"t1", {{"Pa"}, {"Pb"}}}, {"t2", {{"Pc"}, {"Pb"}}}}; - Store store = {{"t0", nullptr}, + Store store = {{"t0", DirectMutation{}}, {"t1", &t}, {"t2", []() { std::this_thread::sleep_for(std::chrono::milliseconds(15)); @@ -20,8 +20,7 @@ TEST_CASE("Test external input.") { Marking final_m = {{"Pb", 2}}; auto stp = std::make_shared(3); - symmetri::PetriNet app(net, m0, final_m, store, {}, "test_net_ext_input", - stp); + PetriNet app(net, m0, final_m, store, {}, "test_net_ext_input", stp); // enqueue a trigger; stp->push([trigger = app.registerTransitionCallback("t0")]() { @@ -31,7 +30,7 @@ TEST_CASE("Test external input.") { }); // run the net - auto [ev, res] = symmetri::fire(app); + auto [ev, res] = fire(app); REQUIRE(res == State::Completed); REQUIRE(i_ran); diff --git a/symmetri/tests/test_model.cc b/symmetri/tests/test_model.cc index cac024f9..e1de9f33 100644 --- a/symmetri/tests/test_model.cc +++ b/symmetri/tests/test_model.cc @@ -2,6 +2,8 @@ #include #include "model.h" +#include "symmetri/utilities.hpp" + using namespace symmetri; using namespace moodycamel; @@ -49,12 +51,12 @@ TEST_CASE("Test equaliy of nets") { TEST_CASE("Run one transition iteration in a petri net") { auto [net, store, priority, m0] = testNet(); - Model m(net, store, priority, m0); auto stp = std::make_shared(1); + Petri m(net, store, priority, m0, {}, "s", stp); auto reducers = std::make_shared>(4); // t0 is enabled. - m.fireTransitions(reducers, stp, true, ""); + m.fireTransitions(reducers, true, ""); // t0 is dispatched but it's reducer has not yet run, so pre-conditions are // processed but post are not: REQUIRE(m.getActiveTransitions() == @@ -76,8 +78,8 @@ TEST_CASE("Run one transition iteration in a petri net") { std::vector{"t0", "t0"}); // process the reducers - m = r1(std::move(m)); - m = r2(std::move(m)); + r1(m); + r2(m); // and now the post-conditions are processed: REQUIRE(m.active_transitions_n.empty()); REQUIRE(MarkingEquality( @@ -88,20 +90,19 @@ TEST_CASE("Run until net dies") { using namespace moodycamel; auto [net, store, priority, m0] = testNet(); - Model m(net, store, priority, m0); + auto stp = std::make_shared(1); + Petri m(net, store, priority, m0, {}, "s", stp); auto reducers = std::make_shared>(4); - auto stp = std::make_shared(1); - Reducer r; PolyTransition a([] {}); // we need to enqueue one 'no-operation' to start the live net. - reducers->enqueue([](Model&& m) { return std::ref(m); }); + reducers->enqueue([](Petri&) {}); do { if (reducers->try_dequeue(r)) { - m = r(std::move(m)); - m.fireTransitions(reducers, stp, true); + r(m); + m.fireTransitions(reducers, true); } } while (m.active_transitions_n.size() > 0); @@ -117,20 +118,20 @@ TEST_CASE("Run until net dies with nullptr") { using namespace moodycamel; auto [net, store, priority, m0] = testNet(); - store = {{"t0", nullptr}, {"t1", nullptr}}; - Model m(net, store, priority, m0); + store = {{"t0", DirectMutation{}}, {"t1", DirectMutation{}}}; + auto stp = std::make_shared(1); + Petri m(net, store, priority, m0, {}, "s", stp); auto reducers = std::make_shared>(4); - auto stp = std::make_shared(1); Reducer r; PolyTransition a([] {}); // we need to enqueue one 'no-operation' to start the live net. - reducers->enqueue([](Model&& m) { return std::ref(m); }); + reducers->enqueue([](Petri&) {}); do { if (reducers->try_dequeue(r)) { - m = r(std::move(m)); - m.fireTransitions(reducers, stp, true); + r(m); + m.fireTransitions(reducers, true); } } while (m.active_transitions_n.size() > 0); @@ -150,11 +151,13 @@ TEST_CASE( Store store; for (auto [t, dm] : net) { - store.insert({t, nullptr}); + store.insert({t, DirectMutation{}}); } // with this initial marking, all but transition e are possible. Marking m0 = {{"Pa", 1}}; - Model m(net, store, {}, m0); + auto stp = std::make_shared(1); + + Petri m(net, store, {}, m0, {}, "s", stp); auto fireable_transitions = m.getFireableTransitions(); auto find = [&](auto a) { return std::find(fireable_transitions.begin(), fireable_transitions.end(), @@ -184,13 +187,13 @@ TEST_CASE("Step through transitions") { auto stp = std::make_shared(1); // with this initial marking, all but transition e are possible. Marking m0 = {{"Pa", 4}}; - Model m(net, store, {}, m0); + Petri m(net, store, {}, m0, {}, "s", stp); REQUIRE(m.getFireableTransitions().size() == 4); // abcd - m.fire("e", reducers, stp); - m.fire("b", reducers, stp); - m.fire("b", reducers, stp); - m.fire("c", reducers, stp); - m.fire("b", reducers, stp); + m.fire("e", reducers); + m.fire("b", reducers); + m.fire("b", reducers); + m.fire("c", reducers); + m.fire("b", reducers); // there are no reducers ran, so this doesn't update. REQUIRE(m.getActiveTransitions().size() == 4); // there should be no markers left. @@ -202,7 +205,7 @@ TEST_CASE("Step through transitions") { while (j < 2 * 4 && reducers->wait_dequeue_timed(r, std::chrono::seconds(1))) { j++; - m = r(std::move(m)); + r(m); } // reducers update, there should be active transitions left. REQUIRE(m.getActiveTransitions().size() == 0); diff --git a/symmetri/tests/test_parser.cc b/symmetri/tests/test_parser.cc index ffe789e9..9b2590b3 100644 --- a/symmetri/tests/test_parser.cc +++ b/symmetri/tests/test_parser.cc @@ -3,6 +3,8 @@ #include #include "symmetri/parsers.h" +#include "symmetri/utilities.hpp" + using namespace symmetri; TEST_CASE("Load p1.pnml net") { diff --git a/symmetri/tests/test_polytransition.cc b/symmetri/tests/test_polytransition.cc index 514cd284..6dd741c2 100644 --- a/symmetri/tests/test_polytransition.cc +++ b/symmetri/tests/test_polytransition.cc @@ -23,6 +23,8 @@ class Foo { const int copy_constructor; }; +Result fire(const Foo&) { return {{}, State::Completed}; } + void resume(const Foo& f) { REQUIRE(f.constructor == 1); REQUIRE(f.copy_constructor == 0); diff --git a/symmetri/tests/test_priorities.cc b/symmetri/tests/test_priorities.cc index f00c6569..5b9709bd 100644 --- a/symmetri/tests/test_priorities.cc +++ b/symmetri/tests/test_priorities.cc @@ -3,6 +3,8 @@ #include #include "model.h" +#include "symmetri/utilities.hpp" + using namespace symmetri; using namespace moodycamel; @@ -21,12 +23,12 @@ TEST_CASE( Marking m0 = {{"Pa", 1}, {"Pb", 0}, {"Pc", 0}}; auto stp = std::make_shared(1); - auto m = Model(net, store, priority, m0); - m.fireTransitions(reducers, stp, true); + auto m = Petri(net, store, priority, m0, {}, "s", stp); + m.fireTransitions(reducers, true); Reducer r; while (reducers->wait_dequeue_timed(r, std::chrono::milliseconds(1))) { - m = r(std::move(m)); + r(m); } auto prio_t0 = std::find_if(priority.begin(), priority.end(), [](auto e) { @@ -49,13 +51,13 @@ TEST_CASE("Using nullptr does not queue reducers.") { for (auto priority : priorities) { auto reducers = std::make_shared>(4); Net net = {{"t0", {{"Pa"}, {"Pb"}}}, {"t1", {{"Pa"}, {"Pc"}}}}; - Store store = {{"t0", nullptr}, {"t1", nullptr}}; + Store store = {{"t0", DirectMutation{}}, {"t1", DirectMutation{}}}; Marking m0 = {{"Pa", 1}, {"Pb", 0}, {"Pc", 0}}; auto stp = std::make_shared(1); - auto m = Model(net, store, priority, m0); - m.fireTransitions(reducers, stp, true); + auto m = Petri(net, store, priority, m0, {}, "s", stp); + m.fireTransitions(reducers, true); // no reducers needed, as simple transitions are handled within run all. auto prio_t0 = std::find_if(priority.begin(), priority.end(), [](auto e) { return e.first == "t0"; diff --git a/symmetri/tests/test_symmetri.cc b/symmetri/tests/test_symmetri.cc index fbc1cbb2..a51258ad 100644 --- a/symmetri/tests/test_symmetri.cc +++ b/symmetri/tests/test_symmetri.cc @@ -23,10 +23,9 @@ TEST_CASE("Create a using the net constructor without end condition.") { auto [net, store, priority, m0] = testNet(); auto stp = std::make_shared(1); - symmetri::PetriNet app(net, m0, {}, store, priority, "test_net_without_end", - stp); + PetriNet app(net, m0, {}, store, priority, "test_net_without_end", stp); // we can run the net - auto [ev, res] = symmetri::fire(app); + auto [ev, res] = fire(app); // because there's no final marking, but the net is finite, it deadlocks. REQUIRE(res == State::Deadlock); @@ -38,10 +37,10 @@ TEST_CASE("Create a using the net constructor with end condition.") { Marking final_marking({{"Pa", 0}, {"Pb", 2}, {"Pc", 0}, {"Pd", 2}}); auto [net, store, priority, m0] = testNet(); - symmetri::PetriNet app(net, m0, final_marking, store, priority, - "test_net_with_end", stp); + PetriNet app(net, m0, final_marking, store, priority, "test_net_with_end", + stp); // we can run the net - auto [ev, res] = symmetri::fire(app); + auto [ev, res] = fire(app); // now there is an end condition. REQUIRE(res == State::Completed); @@ -51,19 +50,13 @@ TEST_CASE("Create a using the net constructor with end condition.") { TEST_CASE("Create a using pnml constructor.") { const std::string pnml_file = std::filesystem::current_path().append( "../../../symmetri/tests/assets/PT1.pnml"); - { auto stp = std::make_shared(1); // This store is not appropriate for this net, Store store = {{"wrong_id", &t0}}; PriorityTable priority; - symmetri::PetriNet app({pnml_file}, {}, store, priority, "fail", stp); - // however, we can try running it, - auto [ev, res] = symmetri::fire(app); - - // but the result is an error. - REQUIRE(res == State::Error); - REQUIRE(ev.empty()); + // it should throw + REQUIRE_THROWS(PetriNet({pnml_file}, {}, store, priority, "fail", stp)); } { @@ -72,10 +65,9 @@ TEST_CASE("Create a using pnml constructor.") { Store store = symmetri::Store{{"T0", &t0}}; PriorityTable priority; Marking final_marking({{"P1", 1}}); - symmetri::PetriNet app({pnml_file}, final_marking, store, priority, - "success", stp); + PetriNet app({pnml_file}, final_marking, store, priority, "success", stp); // so we can run it, - auto [ev, res] = symmetri::fire(app); + auto [ev, res] = fire(app); // and the result is properly completed. REQUIRE(res == State::Completed); REQUIRE(!ev.empty()); @@ -87,11 +79,11 @@ TEST_CASE("Reuse an application with a new case_id.") { const auto initial_id = "initial0"; const auto new_id = "something_different0"; auto stp = std::make_shared(1); - symmetri::PetriNet app(net, m0, {}, store, priority, initial_id, stp); + PetriNet app(net, m0, {}, store, priority, initial_id, stp); REQUIRE(!app.reuseApplication(initial_id)); REQUIRE(app.reuseApplication(new_id)); // fire a transition so that there's an entry in the eventlog - auto eventlog = symmetri::fire(app).first; + auto eventlog = fire(app).first; // double check that the eventlog is not empty REQUIRE(!eventlog.empty()); // the eventlog should have a different case id. @@ -105,12 +97,12 @@ TEST_CASE("Can not reuse an active application with a new case_id.") { const auto initial_id = "initial1"; const auto new_id = "something_different1"; auto stp = std::make_shared(1); - symmetri::PetriNet app(net, m0, {}, store, priority, initial_id, stp); + PetriNet app(net, m0, {}, store, priority, initial_id, stp); stp->push([&]() mutable { // this should fail because we can not do this while everything is active. REQUIRE(!app.reuseApplication(new_id)); }); - auto [ev, res] = symmetri::fire(app); + auto [ev, res] = fire(app); } TEST_CASE("Test pause and resume") { @@ -119,21 +111,21 @@ TEST_CASE("Test pause and resume") { Store store = {{"t0", [&] { i++; }}, {"t1", [] {}}}; Marking m0 = {{"Pa", 1}}; auto stp = std::make_shared(2); - symmetri::PetriNet app(net, m0, {{"Pb", 1}}, store, {}, "random_id", stp); + PetriNet app(net, m0, {{"Pb", 1}}, store, {}, "random_id", stp); int check1, check2; stp->push([&, app, t1 = app.registerTransitionCallback("t1")]() { const auto dt = std::chrono::milliseconds(5); std::this_thread::sleep_for(dt); - symmetri::pause(app); + pause(app); std::this_thread::sleep_for(dt); check1 = i.load(); std::this_thread::sleep_for(dt); check2 = i.load(); - symmetri::resume(app); + resume(app); std::this_thread::sleep_for(dt); t1(); }); - symmetri::fire(app); + fire(app); REQUIRE(check1 == check2); REQUIRE(i.load() > check2); REQUIRE(i.load() > check2 + 1); diff --git a/symmetri/tests/test_types.cc b/symmetri/tests/test_types.cc index 8cf34c7b..a5c7c251 100644 --- a/symmetri/tests/test_types.cc +++ b/symmetri/tests/test_types.cc @@ -1,6 +1,7 @@ #include #include "symmetri/types.h" +#include "symmetri/utilities.hpp" using namespace symmetri; diff --git a/symmetri/types.cpp b/symmetri/types.cpp index 9a411fff..9ac7dad7 100644 --- a/symmetri/types.cpp +++ b/symmetri/types.cpp @@ -4,59 +4,6 @@ #include namespace symmetri { -template <> -bool MarkingEquality(const std::vector& m1, - const std::vector& m2) { - auto m1_sorted = m1; - auto m2_sorted = m2; - std::sort(m1_sorted.begin(), m1_sorted.end()); - std::sort(m2_sorted.begin(), m2_sorted.end()); - return m1_sorted == m2_sorted; -} -template <> -bool MarkingReached(const std::vector& marking, - const std::vector& final_marking) { - if (final_marking.empty()) { - return false; - } - auto unique = final_marking; - std::sort(unique.begin(), unique.end()); - auto last = std::unique(unique.begin(), unique.end()); - unique.erase(last, unique.end()); - - return std::all_of(std::begin(unique), std::end(unique), [&](const auto& p) { - return std::count(marking.begin(), marking.end(), p) == - std::count(final_marking.begin(), final_marking.end(), p); - }); -} - -template <> -bool MarkingEquality(const std::vector& m1, - const std::vector& m2) { - auto m1_sorted = m1; - auto m2_sorted = m2; - std::sort(m1_sorted.begin(), m1_sorted.end()); - std::sort(m2_sorted.begin(), m2_sorted.end()); - return m1_sorted == m2_sorted; -} -template <> -bool MarkingReached( - const std::vector& marking, - const std::vector& final_marking) { - if (final_marking.empty()) { - return false; - } - auto unique = final_marking; - std::sort(unique.begin(), unique.end()); - auto last = std::unique(unique.begin(), unique.end()); - unique.erase(last, unique.end()); - - return std::all_of(std::begin(unique), std::end(unique), [&](const auto& p) { - return std::count(marking.begin(), marking.end(), p) == - std::count(final_marking.begin(), final_marking.end(), p); - }); -} - bool stateNetEquality(const Net& net1, const Net& net2) { if (net1.size() != net2.size()) { return false; @@ -121,6 +68,9 @@ std::string printState(symmetri::State s) noexcept { case State::UserExit: ret = "UserExit"; break; + case State::Paused: + ret = "Paused"; + break; case State::Error: ret = "Error"; break;