diff --git a/Doxyfile b/Doxyfile index 721a4d91..3206b139 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1353,7 +1353,7 @@ HTML_EXTRA_FILES = # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE = AUTO_LIGHT +HTML_COLORSTYLE = LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to @@ -1653,7 +1653,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES # When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the # FULL_SIDEBAR option determines if the side bar is limited to only the treeview @@ -2736,10 +2736,7 @@ GENERATE_LEGEND = YES DOT_CLEANUP = YES IMAGE_PATH += docs -# USE_MDFILE_AS_MAINPAGE = README.md -INPUT += README.md \ - docs/symmetri_nets.md \ - docs/symmetri_lib.md - -DISABLE_INDEX = NO -GENERATE_TREEVIEW = YES +USE_MDFILE_AS_MAINPAGE = docs/main.md +INPUT += docs/symmetri_nets.md \ + docs/symmetri_lib.md \ + docs/main.md diff --git a/docs/architecture.drawio b/docs/architecture.drawio index 4769ac06..e0f40c2d 100644 --- a/docs/architecture.drawio +++ b/docs/architecture.drawio @@ -1 +1,199 @@ -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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/architecture_dark.svg b/docs/img/architecture_dark.svg index e3641ee9..6427927e 100644 --- a/docs/img/architecture_dark.svg +++ b/docs/img/architecture_dark.svg @@ -1,3 +1,3 @@ -Transition queueList Transition -> List Transition This is a queue of transitions.Transition queue...Reducer queueList Reducer --> Marking > MarkingThe queue of 'Reducers'. Reducers are functions that update the marking and return an updated marking. Reducer queue...ThreadpoolTransition -> ReducerA pool of worker threads clears the transition queue. Each time a transition is executed, it creates and queues a Reducer.Threadpool...Fire transitionsMarking -> (List Transitions, Marking) With the most up-to-date marking, we launch as many enabled transitions possible until none are enabled. All fired transitions are queued for execution later.Fire transitions...TransitionsTransi...TransitionTransi...ReducerReducerMarkingMarkingPost conditionsPost condi...Pre conditionsPre condit...Text is not SVG - cannot display +Callback queueThe Callback queue contains all the callbacks associated with the transitions that have been fired.Callback queue...Reducer queueThe reducer queue is dequeued on the Symmetri main-thread. Reducers update the Symmetri state by removing transitions from the active set and potentially updating marking based on the Callbacks' success. Reducer queue...ThreadpoolA fixed-size threadpool dequeues callbacks and executes them. The result of every callback is a Reducer that updates the internal Symmetri state. The reducer is dispatched for later execution.Threadpool...Fire transitionsIn the main Symmetri loop, unless paused or cancelled, the set of active transitions is determined and queued to the callback queue in order of priority. Transitions with Synchronous Callbacks are executed on the Symmetri main-thread and can directly mutate the post-Callback mutations. Fire transitions...Callback queuingCallba...Callback executionCallba...Reducer queuingReduce...MarkingMarkingReducer executionReduce...Text is not SVG - cannot display diff --git a/docs/img/architecture_light.svg b/docs/img/architecture_light.svg index d3168523..ee36e0cf 100644 --- a/docs/img/architecture_light.svg +++ b/docs/img/architecture_light.svg @@ -1,3 +1,3 @@ -Transition queueList Transition -> List Transition This is a queue of transitions.Transition queue...Reducer queueList Reducer --> Marking > MarkingThe queue of 'Reducers'. Reducers are functions that update the marking and return an updated marking. Reducer queue...ThreadpoolTransition -> ReducerA pool of worker threads clears the transition queue. Each time a transition is executed, it creates and queues a Reducer.Threadpool...Fire transitionsMarking -> (List Transitions, Marking) With the most up-to-date marking, we launch as many enabled transitions possible until none are enabled. All fired transitions are queued for execution later.Fire transitions...TransitionsTransi...TransitionTransi...ReducerReducerMarkingMarkingPost conditionsPost condi...Pre conditionsPre condit...Text is not SVG - cannot display +Callback queueThe Callback queue contains all the callbacks associated with the transitions that have been fired.Callback queue...Reducer queueThe reducer queue is dequeued on the Symmetri main-thread. Reducers update the Symmetri state by removing transitions from the active set and potentially updating marking based on the Callbacks' success. Reducer queue...ThreadpoolA fixed-size threadpool dequeues callbacks and executes them. The result of every callback is a Reducer that updates the internal Symmetri state. The reducer is dispatched for later execution.Threadpool...Fire transitionsIn the main Symmetri loop, unless paused or cancelled, the set of active transitions is determined and queued to the callback queue in order of priority. Transitions with Synchronous Callbacks are executed on the Symmetri main-thread and can directly mutate the post-Callback mutations. Fire transitions...Callback queuingCallba...Callback executionCallba...Reducer queuingReduce...MarkingMarkingReducer executionReduce...Text is not SVG - cannot display diff --git a/docs/main.md b/docs/main.md new file mode 100644 index 00000000..d5dce78e --- /dev/null +++ b/docs/main.md @@ -0,0 +1,3 @@ +# Symmetri, Petri Nets in C++ + +Welcome to the documentation for the Symmetri library. diff --git a/docs/symmetri_lib.md b/docs/symmetri_lib.md index 78297363..6d0b7a24 100644 --- a/docs/symmetri_lib.md +++ b/docs/symmetri_lib.md @@ -1,9 +1,47 @@ # Symmetri library -blabla - ## Build +### Vanilla CMake: + +Symmetri can be build as stand-alone CMake-project: + +```bash +git clone --recurse-submodules https://github.com/thorstink/Symmetri.git +mkdir build +cd build +cmake .. +make +``` + +### In a Colcon-workspace +It can also be build as part of a [colcon-workspace](https://colcon.readthedocs.io/en/released/user/what-is-a-workspace.html): + +```bash +mkdir ws/src +cd ws/src +git clone --recurse-submodules https://github.com/thorstink/Symmetri.git +cd .. # back to ws-directory +colcon build +``` + ## Test +Symmetri has a set of tests. There are three build configurations: + +- No sanitizers +- [ASAN](https://github.com/google/sanitizers/wiki/AddressSanitizer) + [UBSAN](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) +- [TSAN](https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual) + +Each sanitizer can be enabled by setting either the 'ASAN_BUILD'- or 'TSAN_BUILD'-flag to true. They can not both be true at the same time. + +The sanitizers generally increase compiltation time quite significantly, hence they are off by default. In the CI-pipeline however they are turned on for testing. + +```bash +# from the build directory... +cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_EXAMPLES=ON -DBUILD_TESTING=ON -DASAN_BUILD=OFF -DTSAN_BUILD=OFF .. +make +make test +``` + ## Architecture diff --git a/docs/symmetri_nets.md b/docs/symmetri_nets.md index 230c845d..558fd63c 100644 --- a/docs/symmetri_nets.md +++ b/docs/symmetri_nets.md @@ -6,7 +6,7 @@ Transition T1 is enabled | Conflict: either T1 #include "symmetri/types.h" diff --git a/symmetri/include/symmetri/parsers.h b/symmetri/include/symmetri/parsers.h index ffbeadfb..4ad03c3e 100644 --- a/symmetri/include/symmetri/parsers.h +++ b/symmetri/include/symmetri/parsers.h @@ -1,4 +1,7 @@ #pragma once + +/** @file parsers.h */ + #include #include diff --git a/symmetri/include/symmetri/symmetri.h b/symmetri/include/symmetri/symmetri.h index e9fad5ff..75d185c8 100644 --- a/symmetri/include/symmetri/symmetri.h +++ b/symmetri/include/symmetri/symmetri.h @@ -1,5 +1,7 @@ #pragma once +/** @file symmetri.h */ + #include #include diff --git a/symmetri/include/symmetri/tasks.h b/symmetri/include/symmetri/tasks.h index 16c20507..93c8cb83 100644 --- a/symmetri/include/symmetri/tasks.h +++ b/symmetri/include/symmetri/tasks.h @@ -1,5 +1,7 @@ #pragma once +/** @file tasks.h */ + #include #include #include diff --git a/symmetri/include/symmetri/types.h b/symmetri/include/symmetri/types.h index 1fbfa548..fd20aa6c 100644 --- a/symmetri/include/symmetri/types.h +++ b/symmetri/include/symmetri/types.h @@ -1,4 +1,7 @@ #pragma once + +/** @file types.h */ + #include #include #include @@ -23,7 +26,7 @@ enum class State { Started, ///< The transition started Completed, ///< The transition completed as expected Deadlock, ///< The transition deadlocked - UserExit, ///< The transition or interrupted and possibly stopped + UserExit, ///< The transition is interrupted and possibly stopped Paused, ///< The transition is paused Error ///< None of the above }; @@ -37,8 +40,7 @@ struct Event { std::string case_id; ///< The case_id of this event std::string transition; ///< The transition that generated the event State state; ///< The resulting state of the event - Clock::time_point stamp; ///< The timestamp when the reducer of this - ///< event was processed + Clock::time_point stamp; ///< The timestamp of the event }; using Eventlog = std::vector; ///< The eventlog is simply a log of @@ -49,9 +51,9 @@ using Result = using Net = std::unordered_map< Transition, std::pair, - std::vector>>; ///< This is the class multiset definition + std::vector>>; ///< This is the multiset definition ///< of a Petri net. For each transition - ///< there is a pair of sets for input and + ///< there is a pair of lists for input and ///< output transitions using Marking = std::unordered_map #include "symmetri/types.h" diff --git a/symmetri/model_utilities.cc b/symmetri/model_utilities.cc index c5fbe6be..5755a9bb 100644 --- a/symmetri/model_utilities.cc +++ b/symmetri/model_utilities.cc @@ -14,11 +14,10 @@ bool canFire(const SmallVector &pre, const std::vector &tokens) { }); } -gch::small_vector possibleTransitions( +gch::small_vector possibleTransitions( const std::vector &tokens, - const std::vector &p_to_ts_n, - const std::vector &priorities) { - gch::small_vector possible_transition_list_n; + const std::vector &p_to_ts_n) { + gch::small_vector possible_transition_list_n; for (const size_t place : tokens) { for (size_t t : p_to_ts_n[place]) { if (std::find(possible_transition_list_n.begin(), @@ -29,16 +28,10 @@ gch::small_vector possibleTransitions( } } - // sort transition list according to priority - std::sort(possible_transition_list_n.begin(), - possible_transition_list_n.end(), - [&](size_t a, size_t b) { return priorities[a] > priorities[b]; }); - return possible_transition_list_n; } -Reducer createReducerForTransition(size_t t_i, const Eventlog &ev, - State result) { +Reducer createReducerForCallback(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. @@ -56,7 +49,7 @@ Reducer createReducerForTransition(size_t t_i, const Eventlog &ev, }; } -Reducer fireTransition( +Reducer scheduleCallback( size_t t_i, const std::string &transition, const Callback &task, const std::string &case_id, const std::shared_ptr> @@ -67,9 +60,9 @@ Reducer fireTransition( {case_id, transition, State::Started, start_time}); }); - auto [ev, res] = fire(task); + auto [ev, res] = ::fire(task); ev.push_back({case_id, transition, res, Clock::now()}); - return createReducerForTransition(t_i, ev, res); + return createReducerForCallback(t_i, ev, res); } } // namespace symmetri diff --git a/symmetri/petri.cc b/symmetri/petri.cc index 881f976a..264de282 100644 --- a/symmetri/petri.cc +++ b/symmetri/petri.cc @@ -112,75 +112,53 @@ std::vector Petri::toTokens(const Marking &marking) const noexcept { return tokens; } -std::vector Petri::getFireableTransitions() const { - auto possible_transition_list_n = - possibleTransitions(tokens, net.p_to_ts_n, net.priority); - std::vector fireable_transitions; - fireable_transitions.reserve(possible_transition_list_n.size()); - for (const size_t t_idx : possible_transition_list_n) { - const auto &pre = net.input_n[t_idx]; - if (canFire(pre, tokens)) { - fireable_transitions.push_back(net.transition[t_idx]); - } +void Petri::fireSynchronous(const size_t t) { + const auto timestamp = Clock::now(); + const auto &task = net.store[t]; + const auto &transition = net.transition[t]; + const auto &lookup_t = net.output_n[t]; + event_log.push_back({case_id, transition, State::Started, timestamp}); + auto result = std::get(::fire(task)); + event_log.push_back({case_id, transition, result, Clock::now()}); + if (result == State::Completed) { + tokens.insert(tokens.begin(), lookup_t.begin(), lookup_t.end()); } - return fireable_transitions; } -bool Petri::Fire( - const size_t t, - const std::shared_ptr> - &reducers, - const std::string &case_id) { - auto timestamp = Clock::now(); - // deduct the marking - for (const size_t place : net.input_n[t]) { +void Petri::fireAsynchronous(const size_t t) { + const auto timestamp = Clock::now(); + const auto &task = net.store[t]; + const auto &transition = net.transition[t]; + active_transitions.push_back(t); + event_log.push_back({case_id, transition, State::Scheduled, timestamp}); + pool->push([=] { + reducer_queue->enqueue( + scheduleCallback(t, transition, task, case_id, reducer_queue)); + }); +} + +void Petri::deductMarking(const SmallVector &inputs) { + for (const size_t place : inputs) { // erase one by one. using std::remove_if would remove all tokens at // a particular place. tokens.erase(std::find(tokens.begin(), tokens.end(), place)); } - - const auto &task = net.store[t]; - - // if the transition is direct, we short-circuit the - // marking mutation and do it immediately. - const auto &transition = net.transition[t]; - const auto &lookup_t = net.output_n[t]; - if (isSynchronous(task)) { - tokens.insert(tokens.begin(), lookup_t.begin(), lookup_t.end()); - event_log.push_back({case_id, transition, State::Started, timestamp}); - event_log.push_back({case_id, transition, - std::get(::fire(task)), timestamp}); - return true; - } else { - active_transitions.push_back(t); - event_log.push_back({case_id, transition, State::Scheduled, timestamp}); - pool->push([=] { - reducers->enqueue(fireTransition(t, transition, task, case_id, reducers)); - }); - return false; - } } -bool Petri::fire( - const Transition &t, - const std::shared_ptr> - &reducers, - const std::string &case_id) { +void Petri::tryFire(const Transition &t) { 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) && - !Fire(std::distance(net.transition.begin(), it), reducers, case_id); + const auto t_idx = std::distance(net.transition.begin(), it); + if (canFire(net.input_n[t_idx], tokens)) { + deductMarking(net.input_n[t_idx]); + isSynchronous(net.store[t_idx]) ? fireSynchronous(t_idx) + : fireAsynchronous(t_idx); + } } -void Petri::fireTransitions( - const std::shared_ptr> - &reducers, - bool run_all, const std::string &case_id) { +void Petri::fireTransitions() { // find possible transitions - auto possible_transition_list_n = - possibleTransitions(tokens, net.p_to_ts_n, net.priority); - auto filter_transitions = [&](const auto &tokens) { + auto possible_transition_list_n = possibleTransitions(tokens, net.p_to_ts_n); + auto remove_inactive_transitions_predicate = [&](const auto &tokens) { possible_transition_list_n.erase( std::remove_if( possible_transition_list_n.begin(), @@ -189,22 +167,41 @@ void Petri::fireTransitions( possible_transition_list_n.end()); }; - // remove transitions that are not fireable; - filter_transitions(tokens); - - if (possible_transition_list_n.empty()) { - return; - } + // remove transitions that are not active; + remove_inactive_transitions_predicate(tokens); + + // sort transition list according to priority + std::sort( + possible_transition_list_n.begin(), possible_transition_list_n.end(), + [&](size_t a, size_t b) { return net.priority[a] > net.priority[b]; }); + + while (!possible_transition_list_n.empty()) { + const auto t_idx = possible_transition_list_n.front(); + deductMarking(net.input_n[t_idx]); + if (isSynchronous(net.store[t_idx])) { + fireSynchronous(t_idx); + // add the output places-connected transitions as new possible + // transitions: + for (const auto &p : net.output_n[t_idx]) { + for (const auto t : net.p_to_ts_n[p]) { + if (canFire(net.input_n[t], tokens)) { + possible_transition_list_n.push_back(t); + } + } + } - do { - // if Fire returns true, update the possible transition list - if (Fire(possible_transition_list_n.front(), reducers, case_id)) { - possible_transition_list_n = - possibleTransitions(tokens, net.p_to_ts_n, net.priority); + // sort again + std::sort(possible_transition_list_n.begin(), + possible_transition_list_n.end(), [&](size_t a, size_t b) { + return net.priority[a] > net.priority[b]; + }); + } else { + fireAsynchronous(t_idx); } - // remove transitions that are not fireable; - filter_transitions(tokens); - } while (run_all && !possible_transition_list_n.empty()); + // remove transitions that are not active anymore because of the marking + // mutation; + remove_inactive_transitions_predicate(tokens); + } return; } @@ -229,16 +226,4 @@ Eventlog Petri::getLog() const { return eventlog; } -std::vector Petri::getActiveTransitions() const { - std::vector transition_list; - if (active_transitions.size() > 0) { - transition_list.reserve(active_transitions.size()); - std::transform(active_transitions.cbegin(), active_transitions.cend(), - std::back_inserter(transition_list), [&](auto place_index) { - return net.transition[place_index]; - }); - } - return transition_list; -} - } // namespace symmetri diff --git a/symmetri/petri.h b/symmetri/petri.h index 082d81d3..2f47e20e 100644 --- a/symmetri/petri.h +++ b/symmetri/petri.h @@ -1,4 +1,7 @@ #pragma once + +/** @file petri.h */ + #include #include @@ -32,47 +35,58 @@ size_t toIndex(const std::vector &m, const std::string &s); * * @param tokens * @param p_to_ts_n - * @param priorities - * @return gch::small_vector + * @return gch::small_vector */ -gch::small_vector possibleTransitions( +gch::small_vector possibleTransitions( const std::vector &tokens, - const std::vector &p_to_ts_n, - const std::vector &priorities); + const std::vector &p_to_ts_n); /** * @brief Takes a vector of input places (pre-conditions) and the current token - * distribution to determine whether the pre-conditions are met. + * distribution to determine whether the pre-conditions are met, e.g. the + * transition is active. * - * @param pre - * @param tokens + * @param pre vector of preconditions + * @param tokens the current token distribution * @return true if the pre-conditions are met * @return false otherwise */ bool canFire(const SmallVector &pre, const std::vector &tokens); +/** + * @brief Forward declaration of the Petri-class + * + */ struct Petri; + +/** + * @brief A Reducer updates the Petri-object. Reducers are used to process the + * post-callback marking mutations. + */ using Reducer = std::function; /** - * @brief Create a Reducer For Transition object + * @brief Schedules the callback associated with transition t_idx/transition. It + * returns a reducer which is to be executed _after_ Callback task completed. * - * @param T_i - * @param task + * @param t_idx the index of the transition as used in the Petri-class + * @param transition the string representation as used in the PetriNet (e.g. + * PNML/GRML) + * @param task the Callback to be scheduled * @param case_id * @param reducers * @return Reducer */ -Reducer fireTransition( - size_t T_i, const std::string &transition, const Callback &task, +Reducer scheduleCallback( + size_t t_idx, const std::string &transition, const Callback &task, const std::string &case_id, const std::shared_ptr> &reducers); /** - * @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. + * @brief Petri is a data structure that encodes the Petri net and holds + * pointers to the thread-pool and the reducer-queue. It is optimized for + * calculating the active transition set and quick lookups in ordered vectors. * */ struct Petri { @@ -82,10 +96,13 @@ struct Petri { * 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. * - * @param net - * @param store - * @param priority - * @param M0 + * @param _net + * @param _store + * @param _priority + * @param _initial_tokens + * @param _final_marking + * @param _case_id + * @param stp */ explicit Petri(const Net &_net, const Store &_store, const PriorityTable &_priority, const Marking &_initial_tokens, @@ -125,54 +142,19 @@ struct Petri { 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. - * In all cases in the future it will produce a reducer which will be - * processed if the Petri net is not halted before the reducer is pop'd from - * the reducer queue. - * - * @return std::vector - */ - std::vector getActiveTransitions() const; - - /** - * @brief Gives a list of unique transitions that are fireable given the - * marking at the time of calling this function, sorted by priority. + * @brief Try to fire a single transition. Does nothing if t is not active. * - * @return std::vector + * @param t string representation of the transition that is tried to fire. */ - std::vector getFireableTransitions() const; + void tryFire(const Transition &t); /** - * @brief Try to fire a single transition. + * @brief Fires all active transitions until it there are none left. + * Associated asynchronous Callbacks are scheduled and synchronous Callback + * are executed immediately. * - * @param t string representation of the transition - * @param reducers pointer reducer queue, needed to construct a reducer - * @param polymorphic_actions pointer to the threadpool, needed to dispatch - * the transition payload - * @param case_id the case id of the Petri instance - * @return true If the transition was fireable, it was queued - * @return false If the transition was not fireable, nothing happenend. */ - bool fire(const Transition &t, - const std::shared_ptr> - &reducers, - const std::string &case_id = "undefined_case_id"); - - /** - * @brief Tries to run a transition (or all) until it 'deadlocks'. The list - * of actions is queued and the function returns. - * - * @param reducers pointer reducer queue, needed to construct the reducer(s) - * @param polymorphic_actions pointer to the threadpool, needed to dispatch - * the transition payload(s) - * @param run_all if true, potentially queues multiple transitions - * @param case_id the case id of the Petri instance - */ - void fireTransitions( - const std::shared_ptr> - &reducers, - bool run_all = true, const std::string &case_id = "undefined_case_id"); + void fireTransitions(); struct { /** @@ -239,19 +221,25 @@ struct Petri { private: /** - * @brief fires a transition. + * @brief deducts the set input from the current token distribution + * + * @param inputs a vector representing the tokens to be removed + */ + void deductMarking(const SmallVector &inputs); + + /** + * @brief Runs the Callback associated with t immediately. + * + * @param t transition as index in transition vector + */ + void fireSynchronous(const size_t t); + + /** + * @brief Schedules the Callback associated with t on the threadpool * * @param t transition as index in transition vector - * @param reducers - * @param polymorphic_actions - * @param case_id - * @return true if the transition is direct. - * @return false if it is only dispatched. */ - bool Fire(const size_t t, - const std::shared_ptr> - &reducers, - const std::string &case_id); + void fireAsynchronous(const size_t t); }; } // namespace symmetri diff --git a/symmetri/symmetri.cc b/symmetri/symmetri.cc index 0d620684..b55f0fe2 100644 --- a/symmetri/symmetri.cc +++ b/symmetri/symmetri.cc @@ -79,9 +79,9 @@ std::function PetriNet::registerTransitionCallback( if (m.thread_id_.load()) { const auto t_index = toIndex(m.net.transition, transition); m.active_transitions.push_back(t_index); - m.reducer_queue->enqueue( - fireTransition(t_index, m.net.transition[t_index], - m.net.store[t_index], m.case_id, m.reducer_queue)); + m.reducer_queue->enqueue(scheduleCallback( + t_index, m.net.transition[t_index], m.net.store[t_index], + m.case_id, m.reducer_queue)); } }); } @@ -123,7 +123,7 @@ symmetri::Result fire(const PetriNet &app) { while (m.reducer_queue->try_dequeue(f)) { /* get rid of old reducers */ } // start! - m.fireTransitions(m.reducer_queue, true, m.case_id); + m.fireTransitions(); while ((m.state == State::Started || m.state == State::Paused) && m.reducer_queue->wait_dequeue_timed(f, -1)) { do { @@ -136,7 +136,7 @@ symmetri::Result fire(const PetriNet &app) { if (m.state == State::Started) { // we're firing - m.fireTransitions(m.reducer_queue, true, m.case_id); + m.fireTransitions(); // if there's nothing to fire; we deadlocked if (m.active_transitions.size() == 0) { m.state = State::Deadlock; diff --git a/symmetri/tests/test_bugs.cc b/symmetri/tests/test_bugs.cc index b69f5403..54ae5490 100644 --- a/symmetri/tests/test_bugs.cc +++ b/symmetri/tests/test_bugs.cc @@ -36,17 +36,14 @@ TEST_CASE("Firing the same transition before it can complete should work") { auto [net, store, priority, m0] = testNet(); auto stp = std::make_shared(2); Petri m(net, store, priority, m0, {}, "s", stp); - auto reducers = - std::make_shared>(4); REQUIRE(m.active_transitions.empty()); - m.fireTransitions(reducers, true); + m.fireTransitions(); REQUIRE(m.getMarking().empty()); - CHECK(m.getActiveTransitions() == - std::vector{"t", "t"}); REQUIRE(m.active_transitions.size() == 2); Reducer r; - while (reducers->wait_dequeue_timed(r, std::chrono::milliseconds(250))) { + while ( + m.reducer_queue->wait_dequeue_timed(r, std::chrono::milliseconds(250))) { r(m); } @@ -57,19 +54,18 @@ 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.reducer_queue->wait_dequeue_timed(r, std::chrono::milliseconds(250)); r(m); REQUIRE(MarkingEquality(m.getMarking(), {"Pb"})); // offending test, but fixed :-) - CHECK(m.getActiveTransitions() == std::vector{"t"}); REQUIRE(m.active_transitions.size() == 1); { std::lock_guard lk(cv_m); is_ready2 = true; } cv.notify_one(); - reducers->wait_dequeue_timed(r, std::chrono::milliseconds(250)); + m.reducer_queue->wait_dequeue_timed(r, std::chrono::milliseconds(250)); r(m); REQUIRE(MarkingEquality(m.getMarking(), {"Pb", "Pb"})); diff --git a/symmetri/tests/test_petri.cc b/symmetri/tests/test_petri.cc index 03d82fee..a68f1651 100644 --- a/symmetri/tests/test_petri.cc +++ b/symmetri/tests/test_petri.cc @@ -53,30 +53,24 @@ TEST_CASE("Run one transition iteration in a petri net") { 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, true, ""); + m.fireTransitions(); // t0 is dispatched but it's reducer has not yet run, so pre-conditions are // processed but post are not: - REQUIRE(m.getActiveTransitions() == - std::vector{"t0", "t0"}); REQUIRE(m.getMarking() == std::vector{"Pa", "Pa"}); // now there should be two reducers; Reducer r1, r2; - REQUIRE(reducers->wait_dequeue_timed(r1, std::chrono::seconds(1))); - REQUIRE(reducers->wait_dequeue_timed(r1, std::chrono::seconds(1))); - REQUIRE(reducers->wait_dequeue_timed(r2, std::chrono::seconds(1))); - REQUIRE(reducers->wait_dequeue_timed(r2, std::chrono::seconds(1))); + REQUIRE(m.reducer_queue->wait_dequeue_timed(r1, std::chrono::seconds(1))); + REQUIRE(m.reducer_queue->wait_dequeue_timed(r1, std::chrono::seconds(1))); + REQUIRE(m.reducer_queue->wait_dequeue_timed(r2, std::chrono::seconds(1))); + REQUIRE(m.reducer_queue->wait_dequeue_timed(r2, std::chrono::seconds(1))); // verify that t0 has actually ran twice. REQUIRE(T0_COUNTER.load() == 2); // the marking should still be the same. REQUIRE(m.getMarking() == std::vector{"Pa", "Pa"}); - CHECK(m.getActiveTransitions() == - std::vector{"t0", "t0"}); - // process the reducers r1(m); r2(m); @@ -93,16 +87,14 @@ TEST_CASE("Run until net dies") { auto stp = std::make_shared(1); Petri m(net, store, priority, m0, {}, "s", stp); - auto reducers = std::make_shared>(4); - Reducer r; Callback a([] {}); // we need to enqueue one 'no-operation' to start the live net. - reducers->enqueue([](Petri&) {}); + m.reducer_queue->enqueue([](Petri&) {}); do { - if (reducers->try_dequeue(r)) { + if (m.reducer_queue->try_dequeue(r)) { r(m); - m.fireTransitions(reducers, true); + m.fireTransitions(); } } while (m.active_transitions.size() > 0); @@ -122,16 +114,14 @@ TEST_CASE("Run until net dies with nullptr") { auto stp = std::make_shared(1); Petri m(net, store, priority, m0, {}, "s", stp); - auto reducers = std::make_shared>(4); - Reducer r; Callback a([] {}); // we need to enqueue one 'no-operation' to start the live net. - reducers->enqueue([](Petri&) {}); + m.reducer_queue->enqueue([](Petri&) {}); do { - if (reducers->try_dequeue(r)) { + if (m.reducer_queue->try_dequeue(r)) { r(m); - m.fireTransitions(reducers, true); + m.fireTransitions(); } } while (m.active_transitions.size() > 0); @@ -140,35 +130,36 @@ TEST_CASE("Run until net dies with nullptr") { m.getMarking(), std::vector{"Pb", "Pb", "Pd", "Pd"})); } -TEST_CASE( - "getFireableTransitions return a vector of unique of unique fireable " - "transitions") { - Net net = {{"a", {{"Pa"}, {}}}, - {"b", {{"Pa"}, {}}}, - {"c", {{"Pa"}, {}}}, - {"d", {{"Pa"}, {}}}, - {"e", {{"Pb"}, {}}}}; - - Store store; - for (auto [t, dm] : net) { - store.insert({t, DirectMutation{}}); - } - // with this initial marking, all but transition e are possible. - Marking m0 = {{"Pa", 1}}; - 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(), - a); - }; - REQUIRE(find("a") != fireable_transitions.end()); - REQUIRE(find("b") != fireable_transitions.end()); - REQUIRE(find("c") != fireable_transitions.end()); - REQUIRE(find("d") != fireable_transitions.end()); - REQUIRE(find("e") == fireable_transitions.end()); -} +// TEST_CASE( +// "getFireableTransitions return a vector of unique of unique fireable " +// "transitions") { +// Net net = {{"a", {{"Pa"}, {}}}, +// {"b", {{"Pa"}, {}}}, +// {"c", {{"Pa"}, {}}}, +// {"d", {{"Pa"}, {}}}, +// {"e", {{"Pb"}, {}}}}; + +// Store store; +// for (auto [t, dm] : net) { +// store.insert({t, DirectMutation{}}); +// } +// // with this initial marking, all but transition e are possible. +// Marking m0 = {{"Pa", 1}}; +// 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(), +// a); +// }; +// REQUIRE(find("a") != fireable_transitions.end()); +// REQUIRE(find("b") != fireable_transitions.end()); +// REQUIRE(find("c") != fireable_transitions.end()); +// REQUIRE(find("d") != fireable_transitions.end()); +// REQUIRE(find("e") == fireable_transitions.end()); +// } TEST_CASE("Step through transitions") { std::map hitmap; @@ -183,32 +174,34 @@ TEST_CASE("Step through transitions") { hitmap.insert({t, 0}); store.insert({t, [&, t = t] { hitmap[t] += 1; }}); } - auto reducers = std::make_shared>(5); auto stp = std::make_shared(1); // with this initial marking, all but transition e are possible. Marking m0 = {{"Pa", 4}}; Petri m(net, store, {}, m0, {}, "s", stp); - REQUIRE(m.getFireableTransitions().size() == 4); // abcd - m.fire("e", reducers); - m.fire("b", reducers); - m.fire("b", reducers); - m.fire("c", reducers); - m.fire("b", reducers); + + // auto active_transitions = m.getActiveTransitions(); + // REQUIRE(active_transitions.size() == 4); // abcd + m.tryFire("e"); + m.tryFire("b"); + m.tryFire("b"); + m.tryFire("c"); + m.tryFire("b"); // there are no reducers ran, so this doesn't update. - REQUIRE(m.getActiveTransitions().size() == 4); + REQUIRE(m.active_transitions.size() == 4); // there should be no markers left. REQUIRE(m.getMarking().size() == 0); // there should be nothing left to fire - REQUIRE(m.getFireableTransitions().size() == 0); + // active_transitions = m.getActiveTransitions(); + // REQUIRE(active_transitions.size() == 0); int j = 0; Reducer r; while (j < 2 * 4 && - reducers->wait_dequeue_timed(r, std::chrono::seconds(1))) { + m.reducer_queue->wait_dequeue_timed(r, std::chrono::seconds(1))) { j++; r(m); } // reducers update, there should be active transitions left. - REQUIRE(m.getActiveTransitions().size() == 0); + REQUIRE(m.active_transitions.size() == 0); } // validate we only ran transition 3 times b, 1 time c and none of the others. diff --git a/symmetri/tests/test_priorities.cc b/symmetri/tests/test_priorities.cc index 8788f2ba..4ce18e97 100644 --- a/symmetri/tests/test_priorities.cc +++ b/symmetri/tests/test_priorities.cc @@ -15,8 +15,6 @@ TEST_CASE( std::list priorities = {{{"t0", 1}, {"t1", 0}}, {{"t0", 0}, {"t1", 1}}}; for (auto priority : priorities) { - auto reducers = std::make_shared>(4); - Net net = {{"t0", {{"Pa"}, {"Pb"}}}, {"t1", {{"Pa"}, {"Pc"}}}}; Store store = {{"t0", [] {}}, {"t1", [] {}}}; @@ -24,10 +22,11 @@ TEST_CASE( auto stp = std::make_shared(1); auto m = Petri(net, store, priority, m0, {}, "s", stp); - m.fireTransitions(reducers, true); + m.fireTransitions(); Reducer r; - while (reducers->wait_dequeue_timed(r, std::chrono::milliseconds(1))) { + while ( + m.reducer_queue->wait_dequeue_timed(r, std::chrono::milliseconds(1))) { r(m); } @@ -45,11 +44,10 @@ TEST_CASE( } } -TEST_CASE("Using nullptr does not queue reducers.") { +TEST_CASE("Using DirectMutation does not queue reducers.") { std::list priorities = {{{"t0", 1}, {"t1", 0}}, {{"t0", 0}, {"t1", 1}}}; for (auto priority : priorities) { - auto reducers = std::make_shared>(4); Net net = {{"t0", {{"Pa"}, {"Pb"}}}, {"t1", {{"Pa"}, {"Pc"}}}}; Store store = {{"t0", DirectMutation{}}, {"t1", DirectMutation{}}}; @@ -57,7 +55,7 @@ TEST_CASE("Using nullptr does not queue reducers.") { auto stp = std::make_shared(1); auto m = Petri(net, store, priority, m0, {}, "s", stp); - m.fireTransitions(reducers, true); + m.fireTransitions(); // 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";
List Transition -> List Transition
This is a queue of transitions.
List Reducer --> Marking > Marking
The queue of 'Reducers'. Reducers are functions that update the marking and return an updated marking.
Marking -> (List Transitions, Marking)
With the most up-to-date marking, we launch as many enabled transitions possible until none are enabled. All fired transitions are queued for execution later.
The Callback queue contains all the callbacks associated with the transitions that have been fired.
The reducer queue is dequeued on the Symmetri main-thread. Reducers update the Symmetri state by removing transitions from the active set and potentially updating marking based on the Callbacks' success.
In the main Symmetri loop, unless paused or cancelled, the set of active transitions is determined and queued to the callback queue in order of priority. Transitions with Synchronous Callbacks are executed on the Symmetri main-thread and can directly mutate the post-Callback mutations.