From f3060d0c0988b79746737919d3e578dfcd50137a Mon Sep 17 00:00:00 2001 From: Thomas Horstink Date: Thu, 20 Nov 2025 22:52:19 +0100 Subject: [PATCH] get active transitions --- symmetri/include/symmetri/symmetri.h | 7 +++ symmetri/petri.cpp | 77 ++++++++++++++++------------ symmetri/petri.h | 48 +++++++++-------- symmetri/symmetri.cpp | 42 +++++++++------ symmetri/tests/symmetri.cpp | 25 +++++++++ 5 files changed, 130 insertions(+), 69 deletions(-) diff --git a/symmetri/include/symmetri/symmetri.h b/symmetri/include/symmetri/symmetri.h index 85a6a0ca..b62feff1 100644 --- a/symmetri/include/symmetri/symmetri.h +++ b/symmetri/include/symmetri/symmetri.h @@ -106,6 +106,13 @@ class PetriNet final { */ Marking getMarking() const noexcept; + /** + * @brief Get the list of active transitions. This function is thread-safe and be called + * during PetriNet execution. + * + */ + std::vector getActiveTransitions() const noexcept; + /** * @brief reuseApplication resets the PetriNet such that the same net can * be used again after a cancel call or natural termination of the PetriNet. diff --git a/symmetri/petri.cpp b/symmetri/petri.cpp index d78b240c..794c0270 100644 --- a/symmetri/petri.cpp +++ b/symmetri/petri.cpp @@ -3,20 +3,20 @@ namespace symmetri { std::tuple, std::vector, std::vector> -convert(const Net &_net) { +convert(const Net& _net) { const auto transition_count = _net.size(); std::vector transitions; std::vector places; std::vector store; transitions.reserve(transition_count); store.reserve(transition_count); - for (const auto &[t, io] : _net) { + for (const auto& [t, io] : _net) { transitions.push_back(t); store.emplace_back(identity{}); - for (const auto &p : io.first) { + for (const auto& p : io.first) { places.push_back(p.first); } - for (const auto &p : io.second) { + for (const auto& p : io.second) { places.push_back(p.first); } // sort and remove duplicates. @@ -28,16 +28,16 @@ convert(const Net &_net) { } std::tuple, std::vector> -populateIoLookups(const Net &_net, const std::vector &ordered_places) { +populateIoLookups(const Net& _net, const std::vector& ordered_places) { std::vector input_n, output_n; - for (const auto &[t, io] : _net) { + for (const auto& [t, io] : _net) { SmallVectorInput q_in, q_out; - for (const auto &p : io.first) { + for (const auto& p : io.first) { q_in.push_back( std::make_tuple(toIndex(ordered_places, p.first), p.second)); } input_n.push_back(q_in); - for (const auto &p : io.second) { + for (const auto& p : io.second) { q_out.push_back({toIndex(ordered_places, p.first), p.second}); } output_n.push_back(q_out); @@ -47,12 +47,12 @@ populateIoLookups(const Net &_net, const std::vector &ordered_places) { std::vector createReversePlaceToTransitionLookup( size_t place_count, size_t transition_count, - const std::vector &input_transitions) { + const std::vector& input_transitions) { std::vector p_to_ts_n; for (size_t p = 0; p < place_count; p++) { SmallVector q; for (size_t c = 0; c < transition_count; c++) { - for (const auto &[input_place, input_color] : input_transitions[c]) { + for (const auto& [input_place, input_color] : input_transitions[c]) { if (p == input_place && std::find(q.begin(), q.end(), c) == q.end()) { q.push_back(c); } @@ -64,19 +64,19 @@ std::vector createReversePlaceToTransitionLookup( } std::vector createPriorityLookup( - const std::vector transition, const PriorityTable &_priority) { + const std::vector transition, const PriorityTable& _priority) { std::vector priority; - for (const auto &t : transition) { + for (const auto& t : transition) { auto ptr = std::find_if(_priority.begin(), _priority.end(), - [t](const auto &a) { return a.first == t; }); + [t](const auto& a) { return a.first == t; }); priority.push_back(ptr != _priority.end() ? ptr->second : 0); } return priority; } -Petri::Petri(const Net &_net, const PriorityTable &_priority, - const Marking &_initial_tokens, const Marking &_final_marking, - const std::string &_case_id, +Petri::Petri(const Net& _net, const PriorityTable& _priority, + const Marking& _initial_tokens, const Marking& _final_marking, + const std::string& _case_id, std::shared_ptr threadpool) : log({}), state(Scheduled), @@ -100,22 +100,22 @@ Petri::Petri(const Net &_net, const PriorityTable &_priority, } std::vector Petri::toTokens( - const Marking &marking) const noexcept { + const Marking& marking) const noexcept { std::vector tokens; - for (const auto &[p, c] : marking) { + for (const auto& [p, c] : marking) { tokens.push_back({toIndex(net.place, p), c}); } return tokens; } void Petri::fireSynchronous(const size_t t) { - const auto &task = net.store[t]; - const auto &lookup_t = net.output_n[t]; + const auto& task = net.store[t]; + const auto& lookup_t = net.output_n[t]; const auto now = Clock::now(); log.push_back({t, Started, now}); auto result = fire(task); log.push_back({t, result, now}); - for (const auto &[p, c] : lookup_t) { + for (const auto& [p, c] : lookup_t) { tokens.push_back({p, result}); } } @@ -127,26 +127,26 @@ void Petri::fireAsynchronous(const size_t t_i) { // defer execution of the transition to the threadpool pool->push([t_i, this] { // log the start on the petri loop; - reducer_queue->enqueue([t_i, t_start = Clock::now()](Petri &model) { + reducer_queue->enqueue([t_i, t_start = Clock::now()](Petri& model) { model.log.push_back({t_i, Started, t_start}); }); // fire the transition and defer a reducer to the petri loop to update the // marking and log - reducer_queue->enqueue([t_i, result = fire(net.store[t_i])](Petri &model) { + reducer_queue->enqueue([t_i, result = fire(net.store[t_i])](Petri& model) { // if it is in the active transition set it means it is finished and // we should process it. const auto t_end = model.net.store[t_i].getEndTime(); const auto it = std::find(model.scheduled_callbacks.begin(), model.scheduled_callbacks.end(), t_i); if (it != model.scheduled_callbacks.end()) { - const auto &place_list = model.net.output_n[t_i]; + const auto& place_list = model.net.output_n[t_i]; if (model.tokens.size() + place_list.size() > model.tokens.capacity()) { model.tokens.reserve( std::max(2 * model.tokens.size(), model.tokens.size() + place_list.size())); } - for (const auto &[p, c] : place_list) { + for (const auto& [p, c] : place_list) { model.tokens.emplace_back(p, result); } std::swap(*std::prev(model.scheduled_callbacks.end()), *it); @@ -157,9 +157,9 @@ void Petri::fireAsynchronous(const size_t t_i) { }); } -void deductMarking(std::vector &tokens, - const SmallVectorInput &inputs) { - for (const auto &place : inputs) { +void deductMarking(std::vector& tokens, + const SmallVectorInput& inputs) { + for (const auto& place : inputs) { tokens.erase(std::find(tokens.begin(), tokens.end(), place)); } } @@ -194,8 +194,8 @@ void Petri::fireTransitions() { // if the fired transition was synchronous, we have to check if the new // tokens enable possible transitions if (can_fire && is_synchronous) { - for (const auto &[p, c] : net.output_n[t_idx]) { - for (const auto &new_transition : net.p_to_ts_n[p]) { + for (const auto& [p, c] : net.output_n[t_idx]) { + for (const auto& new_transition : net.p_to_ts_n[p]) { if (std::find(ts.begin(), ts.end(), new_transition) == ts.end()) { ts.push_back(new_transition); } @@ -222,15 +222,26 @@ Marking Petri::getMarking() const { return marking; } +std::vector Petri::getActiveTransitions() const { + std::vector active_transitions; + active_transitions.reserve(scheduled_callbacks.size()); + std::transform(scheduled_callbacks.cbegin(), scheduled_callbacks.cend(), + std::back_inserter(active_transitions), + [&](auto transition_index) -> std::string { + return net.transition[transition_index]; + }); + return active_transitions; +} + Eventlog Petri::getLogInternal() const { Eventlog eventlog; eventlog.reserve(log.size()); - for (const auto &[t_i, result, time] : log) { + for (const auto& [t_i, result, time] : log) { eventlog.push_back({case_id, net.transition[t_i], result, time}); } // get event log from parent nets: - for (const auto &callback : net.store) { + for (const auto& callback : net.store) { Eventlog sub_el = getLog(callback); if (!sub_el.empty()) { eventlog.insert(eventlog.end(), sub_el.begin(), sub_el.end()); @@ -238,7 +249,7 @@ Eventlog Petri::getLogInternal() const { } std::sort(eventlog.begin(), eventlog.end(), - [](const auto &a, const auto &b) { return a.stamp < b.stamp; }); + [](const auto& a, const auto& b) { return a.stamp < b.stamp; }); return eventlog; } diff --git a/symmetri/petri.h b/symmetri/petri.h index f8b1f786..173d8fe1 100644 --- a/symmetri/petri.h +++ b/symmetri/petri.h @@ -57,7 +57,7 @@ using SmallVectorInput = gch::small_vector; * @param s * @return size_t */ -size_t toIndex(const std::vector &m, const std::string &s); +size_t toIndex(const std::vector& m, const std::string& s); /** * @brief calculates a list of possible transitions given the current @@ -69,9 +69,9 @@ size_t toIndex(const std::vector &m, const std::string &s); * @return gch::small_vector */ gch::small_vector possibleTransitions( - const std::vector &tokens, - const std::vector &input_n, - const std::vector &p_to_ts_n); + const std::vector& tokens, + const std::vector& input_n, + const std::vector& p_to_ts_n); /** * @brief Takes a vector of input places (pre-conditions) and the current token @@ -83,7 +83,7 @@ gch::small_vector possibleTransitions( * @return true if the pre-conditions are met * @return false otherwise */ -bool canFire(const SmallVectorInput &pre, std::vector &tokens); +bool canFire(const SmallVectorInput& pre, std::vector& tokens); /** * @brief Forward declaration of the Petri-class @@ -95,15 +95,15 @@ struct Petri; * @brief A Reducer updates the Petri-object. Reducers are used to process the * post-callback marking mutations. */ -using Reducer = std::function; +using Reducer = std::function; /** * @brief deducts the set input from the current token distribution * * @param inputs a vector representing the tokens to be removed */ -void deductMarking(std::vector &tokens, - const SmallVectorInput &inputs); +void deductMarking(std::vector& tokens, + const SmallVectorInput& inputs); /** * @brief Petri is a data structure that encodes the Petri net and holds @@ -126,15 +126,15 @@ struct Petri { * @param _case_id * @param threadpool */ - explicit Petri(const Net &_net, const PriorityTable &_priority, - const Marking &_initial_tokens, const Marking &_final_marking, - const std::string &_case_id, + explicit Petri(const Net& _net, const PriorityTable& _priority, + const Marking& _initial_tokens, const Marking& _final_marking, + const std::string& _case_id, std::shared_ptr threadpool); ~Petri() noexcept = default; - Petri(Petri const &) = delete; - Petri(Petri &&) noexcept = delete; - Petri &operator=(Petri const &) = delete; - Petri &operator=(Petri &&) noexcept = delete; + 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 @@ -143,7 +143,7 @@ struct Petri { * @param marking * @return std::vector */ - std::vector toTokens(const Marking &marking) const noexcept; + std::vector toTokens(const Marking& marking) const noexcept; /** * @brief Get the current marking. It is represented by a vector of places: @@ -155,6 +155,12 @@ struct Petri { */ Marking getMarking() const; + /** + * @brief Get the list of active transitions. + * @return std::vector + */ + std::vector getActiveTransitions() const; + /** * @brief get the current eventlog, also copies in all child eventlogs of * active petri nets. @@ -219,7 +225,7 @@ struct Petri { */ std::vector store; - void registerCallback(const std::string &t, Callback &&callback) noexcept { + void registerCallback(const std::string& t, Callback&& callback) noexcept { if (std::find(transition.begin(), transition.end(), t) != transition.end()) { store[toIndex(transition, t)] = std::move(callback); @@ -263,13 +269,13 @@ struct Petri { std::tuple, std::vector, std::vector> -convert(const Net &_net); +convert(const Net& _net); std::tuple, std::vector> -populateIoLookups(const Net &_net, const std::vector &ordered_places); +populateIoLookups(const Net& _net, const std::vector& ordered_places); std::vector createReversePlaceToTransitionLookup( size_t place_count, size_t transition_count, - const std::vector &input_transitions); + const std::vector& input_transitions); std::vector createPriorityLookup( - const std::vector transition, const PriorityTable &_priority); + const std::vector transition, const PriorityTable& _priority); } // namespace symmetri diff --git a/symmetri/symmetri.cpp b/symmetri/symmetri.cpp index 65d4eb22..44552970 100644 --- a/symmetri/symmetri.cpp +++ b/symmetri/symmetri.cpp @@ -8,11 +8,11 @@ namespace symmetri { -PetriNet::PetriNet(const std::set &files, - const std::string &case_id, +PetriNet::PetriNet(const std::set& files, + const std::string& case_id, std::shared_ptr threadpool, - const Marking &final_marking, - const PriorityTable &priorities) + const Marking& final_marking, + const PriorityTable& priorities) : impl([&] { // get the first file; const std::filesystem::path pn_file = *files.begin(); @@ -28,16 +28,16 @@ PetriNet::PetriNet(const std::set &files, }()), s(impl->net.store) {} -PetriNet::PetriNet(const Net &net, const std::string &case_id, +PetriNet::PetriNet(const Net& net, const std::string& case_id, std::shared_ptr threadpool, - const Marking &initial_marking, const Marking &final_marking, - const PriorityTable &priorities) + const Marking& initial_marking, const Marking& final_marking, + const PriorityTable& priorities) : impl(std::make_shared(net, priorities, initial_marking, final_marking, case_id, threadpool)), s(impl->net.store) {} std::function PetriNet::getInputTransitionHandle( - const Transition &transition) const noexcept { + const Transition& transition) const noexcept { const auto t_index = toIndex(impl->net.transition, transition); // if the transition has input places, you can not register a callback like // this, we simply return a non-functioning handle. @@ -49,21 +49,21 @@ std::function PetriNet::getInputTransitionHandle( return [t_index, this]() -> void { if (impl->thread_id_.load()) { impl->reducer_queue->enqueue( - [t_index](Petri &m) { m.fireAsynchronous(t_index); }); + [t_index](Petri& m) { m.fireAsynchronous(t_index); }); } }; } } -void PetriNet::registerCallback(const std::string &transition, - Callback &&callback) const noexcept { +void PetriNet::registerCallback(const std::string& transition, + Callback&& callback) const noexcept { if (!impl->thread_id_.load().has_value()) { impl->net.registerCallback(transition, std::forward(callback)); } } std::vector::iterator PetriNet::getCallbackItr( - const std::string &transition_name) const { - const auto &t = impl->net.transition; + const std::string& transition_name) const { + const auto& t = impl->net.transition; return impl->net.store.begin() + std::distance(t.begin(), std::find(t.begin(), t.end(), transition_name)); @@ -74,14 +74,26 @@ Marking PetriNet::getMarking() const noexcept { std::promise el; std::future el_getter = el.get_future(); impl->reducer_queue->enqueue( - [&](Petri &model) { el.set_value(model.getMarking()); }); + [&](Petri& model) { el.set_value(model.getMarking()); }); return el_getter.get(); } else { return impl->getMarking(); } } -bool PetriNet::reuseApplication(const std::string &new_case_id) { +std::vector PetriNet::getActiveTransitions() const noexcept { + if (impl->thread_id_.load()) { + std::promise> el; + std::future> el_getter = el.get_future(); + impl->reducer_queue->enqueue( + [&](Petri& model) { el.set_value(model.getActiveTransitions()); }); + return el_getter.get(); + } else { + return impl->getActiveTransitions(); + } +} + +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; diff --git a/symmetri/tests/symmetri.cpp b/symmetri/tests/symmetri.cpp index eea66c34..a4100005 100644 --- a/symmetri/tests/symmetri.cpp +++ b/symmetri/tests/symmetri.cpp @@ -1,6 +1,7 @@ #include "symmetri/symmetri.h" #include +#include #include #include "doctest/doctest.h" @@ -155,6 +156,30 @@ TEST_CASE("Error'd transition shows up in marking") { CHECK(has_failed_token); } +TEST_CASE("Get of active transitions") { + auto [net, priority, initial_marking] = SymmetriTestNet(); + Marking goal_marking = {}; + const auto initial_id = "get list of transitions"; + auto threadpool = std::make_shared(4); + PetriNet app(net, initial_id, threadpool, initial_marking, goal_marking, + priority); + std::promise barrier0; + std::promise> barrier1; + threadpool->push([&] { + barrier0.get_future().get(); + barrier1.set_value(app.getActiveTransitions()); + }); + app.registerCallback("t1", [&] { + barrier0.set_value(); + CHECK(barrier1.get_future().get() == std::vector{"t1"}); + return Failed; + }); + + fire(app); + const auto active_transition_list = app.getActiveTransitions(); + CHECK(active_transition_list.empty()); +} + TEST_CASE("Test pause and resume") { std::atomic i = 0; Net net = {{"t0", {{{"Pa", Success}}, {{"Pa", Success}}}},