From d4f3058105c3a6b5335361981e3c7fd1d6ccfd8d Mon Sep 17 00:00:00 2001 From: Thomas Horstink Date: Sat, 17 Sep 2022 21:12:48 +0200 Subject: [PATCH 1/6] add a petri net executor example --- src/examples/CMakeLists.txt | 1 + src/examples/petri/CMakeLists.txt | 6 +++ src/examples/petri/petri.cpp | 90 +++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/examples/petri/CMakeLists.txt create mode 100644 src/examples/petri/petri.cpp diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index 7b6a3d133..6f5fc7369 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(basic) add_subdirectory(doxygen) +add_subdirectory(petri) if (RPP_BUILD_SFML_CODE) add_subdirectory(sfml) diff --git a/src/examples/petri/CMakeLists.txt b/src/examples/petri/CMakeLists.txt new file mode 100644 index 000000000..d5426b0a2 --- /dev/null +++ b/src/examples/petri/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(petri_executor + petri.cpp +) + +target_link_libraries(petri_executor PRIVATE rpp) +set_target_properties(petri_executor PROPERTIES FOLDER Examples) diff --git a/src/examples/petri/petri.cpp b/src/examples/petri/petri.cpp new file mode 100644 index 000000000..47b046d3d --- /dev/null +++ b/src/examples/petri/petri.cpp @@ -0,0 +1,90 @@ +// This is a small example in which a 'petri net executor' is implemented using Reactive +// Streams. Petri nets can be seen as a generalization of state machines and are +// particulary effective for modeling concurrent programs. See +// https://en.wikipedia.org/wiki/Petri_net for a brief introduction to Petri nets. + +#include + +#include + +using Transition = int; +using Transitions = std::vector; +using Place = char; +using Places = std::vector; +struct Mutations +{ + Places consume, produce; +}; +using PetriNet = std::map; +using Marking = std::map; +typedef void (*const TransitionFunction)(void); + +using namespace std::ranges; + +void foo() { std::cout << "foo" << std::endl; } +void bar() { std::cout << "bar" << std::endl; } + +// The state of a system is determined by its Petri Net (constant) and its marking (changes) +const PetriNet net = {{0, {{'a', 'b'}, {'c'}}}, {1, {{'c', 'c'}, {'b', 'b', 'd'}}}}; +const Marking m_start = {{'a', 4}, {'b', 2}, {'c', 0}, {'d', 0}}; + +const std::map store = {{0, &foo}, {1, &bar}}; + +const auto& runTransition(Transition t) +{ + store.at(t)(); // calls the function associated with the transition + return net.at(t).produce; // contains the post marking mutations. +} + +std::pair mutateMarking(Marking&& m, const Places& produce) +{ + // Produce tokens + for_each(produce, [&m](const auto& place) { m[place] += 1; }); + // Consume tokens by exeucting enabled transitions + Transitions transitions; + for (const auto& [transition, mutations] : net) + { + const auto& c = mutations.consume; + if (!c.empty() && + all_of(c, [&](const auto& place) { return m[place] >= count(c, place); })) + { + for_each(c, [&m](const auto& place) { m[place] -= 1; }); + transitions.push_back(transition); + } + } + + return std::make_pair(m, transitions); +} + +int main() +{ + // A subject is used to create a stream of 'transitions' that should be 'executed'. + const auto transitions_subject = rpp::subjects::publish_subject{}; + + // In our case, we have a finite program and we expect the program to end up in a + // particular marking. Hence we create a predicate that compares the marking to the + // expected (end) marking. + const Marking m_end = {{'a', 0}, {'b', 2}, {'c', 0}, {'d', 2}}; + const auto end_predicate = [=](const auto& m) + { return !std::equal(m.begin(), m.end(), m_end.begin()); }; + + // Reactive streams lend themselves very nicely to create a 'petri net executor': + transitions_subject.get_observable() + .map(&runTransition) + .start_with(Places{}) // we start with an empty set + .observe_on(rpp::schedulers::trampoline{}) + .scan(m_start, + [&transitions_subject](auto&& m, const auto& p) + { + Transitions new_t; + std::tie(m, new_t) = mutateMarking(std::move(m), p); + for_each(new_t, + [dispatcher = transitions_subject.get_subscriber()](const auto& t) + { dispatcher.on_next(t); }); + return m; + }) + .take_while(end_predicate) + .subscribe([](const auto&) {}, [] { std::cout << "done" << std::endl; }); + + return 0; +} \ No newline at end of file From b2d088030bb5f15d6ee520daafef661a23a21bfe Mon Sep 17 00:00:00 2001 From: Thomas Horstink Date: Sat, 17 Sep 2022 21:18:17 +0200 Subject: [PATCH 2/6] update names to be clear and in line with project code style --- src/examples/petri/petri.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/examples/petri/petri.cpp b/src/examples/petri/petri.cpp index 47b046d3d..4b8cba0e2 100644 --- a/src/examples/petri/petri.cpp +++ b/src/examples/petri/petri.cpp @@ -25,35 +25,35 @@ void foo() { std::cout << "foo" << std::endl; } void bar() { std::cout << "bar" << std::endl; } // The state of a system is determined by its Petri Net (constant) and its marking (changes) -const PetriNet net = {{0, {{'a', 'b'}, {'c'}}}, {1, {{'c', 'c'}, {'b', 'b', 'd'}}}}; -const Marking m_start = {{'a', 4}, {'b', 2}, {'c', 0}, {'d', 0}}; +const PetriNet net = {{0, {{'a', 'b'}, {'c'}}}, {1, {{'c', 'c'}, {'b', 'b', 'd'}}}}; +const Marking marking_start = {{'a', 4}, {'b', 2}, {'c', 0}, {'d', 0}}; const std::map store = {{0, &foo}, {1, &bar}}; -const auto& runTransition(Transition t) +const auto& run_transition(Transition t) { store.at(t)(); // calls the function associated with the transition return net.at(t).produce; // contains the post marking mutations. } -std::pair mutateMarking(Marking&& m, const Places& produce) +std::pair mutate_marking(Marking&& marking, const Places& produce) { // Produce tokens - for_each(produce, [&m](const auto& place) { m[place] += 1; }); + for_each(produce, [&marking](const auto& place) { marking[place] += 1; }); // Consume tokens by exeucting enabled transitions Transitions transitions; for (const auto& [transition, mutations] : net) { const auto& c = mutations.consume; if (!c.empty() && - all_of(c, [&](const auto& place) { return m[place] >= count(c, place); })) + all_of(c, [&](const auto& place) { return marking[place] >= count(c, place); })) { - for_each(c, [&m](const auto& place) { m[place] -= 1; }); + for_each(c, [&marking](const auto& place) { marking[place] -= 1; }); transitions.push_back(transition); } } - return std::make_pair(m, transitions); + return std::make_pair(marking, transitions); } int main() @@ -64,24 +64,24 @@ int main() // In our case, we have a finite program and we expect the program to end up in a // particular marking. Hence we create a predicate that compares the marking to the // expected (end) marking. - const Marking m_end = {{'a', 0}, {'b', 2}, {'c', 0}, {'d', 2}}; - const auto end_predicate = [=](const auto& m) - { return !std::equal(m.begin(), m.end(), m_end.begin()); }; + const Marking marking_end = {{'a', 0}, {'b', 2}, {'c', 0}, {'d', 2}}; + const auto end_predicate = [=](const auto& marking) + { return !std::equal(marking.begin(), marking.end(), marking_end.begin()); }; // Reactive streams lend themselves very nicely to create a 'petri net executor': transitions_subject.get_observable() - .map(&runTransition) + .map(&run_transition) .start_with(Places{}) // we start with an empty set .observe_on(rpp::schedulers::trampoline{}) - .scan(m_start, - [&transitions_subject](auto&& m, const auto& p) + .scan(marking_start, + [&transitions_subject](auto&& marking, const auto& places) { - Transitions new_t; - std::tie(m, new_t) = mutateMarking(std::move(m), p); - for_each(new_t, + Transitions transitions; + std::tie(marking, transitions) = mutate_marking(std::move(marking), places); + for_each(transitions, [dispatcher = transitions_subject.get_subscriber()](const auto& t) { dispatcher.on_next(t); }); - return m; + return marking; }) .take_while(end_predicate) .subscribe([](const auto&) {}, [] { std::cout << "done" << std::endl; }); From 8c34a342af3bb1cbb3ffcad37dd2e617191b663d Mon Sep 17 00:00:00 2001 From: Thomas Horstink Date: Sun, 18 Sep 2022 11:06:17 +0200 Subject: [PATCH 3/6] return with move instead of plain value, as in scan doxygen example --- src/examples/petri/petri.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/petri/petri.cpp b/src/examples/petri/petri.cpp index 4b8cba0e2..385eb13ab 100644 --- a/src/examples/petri/petri.cpp +++ b/src/examples/petri/petri.cpp @@ -81,7 +81,7 @@ int main() for_each(transitions, [dispatcher = transitions_subject.get_subscriber()](const auto& t) { dispatcher.on_next(t); }); - return marking; + return std::move(marking); }) .take_while(end_predicate) .subscribe([](const auto&) {}, [] { std::cout << "done" << std::endl; }); From dd675f074e98bba07a8116c87e1fa8d7037ced10 Mon Sep 17 00:00:00 2001 From: Thomas Horstink Date: Sun, 18 Sep 2022 11:16:09 +0200 Subject: [PATCH 4/6] add empty line at end of file --- src/examples/petri/petri.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/petri/petri.cpp b/src/examples/petri/petri.cpp index 385eb13ab..4c1b78abd 100644 --- a/src/examples/petri/petri.cpp +++ b/src/examples/petri/petri.cpp @@ -87,4 +87,4 @@ int main() .subscribe([](const auto&) {}, [] { std::cout << "done" << std::endl; }); return 0; -} \ No newline at end of file +} From 32904d401c9e385a316a988edd16c81edc359b60 Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 18 Sep 2022 17:52:55 +0300 Subject: [PATCH 5/6] Update src/examples/petri/petri.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/examples/petri/petri.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/petri/petri.cpp b/src/examples/petri/petri.cpp index 4c1b78abd..41dfff9bb 100644 --- a/src/examples/petri/petri.cpp +++ b/src/examples/petri/petri.cpp @@ -81,7 +81,7 @@ int main() for_each(transitions, [dispatcher = transitions_subject.get_subscriber()](const auto& t) { dispatcher.on_next(t); }); - return std::move(marking); + return std::forward(marking); }) .take_while(end_predicate) .subscribe([](const auto&) {}, [] { std::cout << "done" << std::endl; }); From cd15de40d6136d4799cd6d78ac33524b7a86e436 Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Sun, 18 Sep 2022 17:53:24 +0300 Subject: [PATCH 6/6] Update src/examples/petri/petri.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/examples/petri/petri.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/petri/petri.cpp b/src/examples/petri/petri.cpp index 41dfff9bb..cdb07a45a 100644 --- a/src/examples/petri/petri.cpp +++ b/src/examples/petri/petri.cpp @@ -77,7 +77,7 @@ int main() [&transitions_subject](auto&& marking, const auto& places) { Transitions transitions; - std::tie(marking, transitions) = mutate_marking(std::move(marking), places); + std::tie(marking, transitions) = mutate_marking(std::forward(marking), places); for_each(transitions, [dispatcher = transitions_subject.get_subscriber()](const auto& t) { dispatcher.on_next(t); });