From 878ad0a3f155a804365fa309f55938659d3c3ea5 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 20 Nov 2024 15:16:16 +0100 Subject: [PATCH] Tabakov-Vardi generator --- include/mata/nfa/builder.hh | 12 +++++ src/nfa/builder.cc | 44 +++++++++++++++++ tests/nfa/builder.cc | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/include/mata/nfa/builder.hh b/include/mata/nfa/builder.hh index 0ad27fae0..0ebf1f912 100644 --- a/include/mata/nfa/builder.hh +++ b/include/mata/nfa/builder.hh @@ -45,6 +45,18 @@ Nfa create_empty_string_nfa(); */ Nfa create_sigma_star_nfa(Alphabet* alphabet = new OnTheFlyAlphabet{}); +/** + * Creates Tabakov-Vardi random NFA. + * + * @param num_of_states Number of states in the automaton. + * @param alphabet_size Size of the alphabet. + * @param transition_density If the density is 1, the automaton will have @p num_of_states transition for each symbol. + * Source and target states are chosen randomly. + * @param final_state_density Density of final states in the automaton. If the density is 1, every state will be final. + * + */ +Nfa create_tabakov_vardi_nfa(const size_t num_of_states, const size_t alphabet_size, const float transition_density, const float final_state_density); + /** Loads an automaton from Parsed object */ // TODO this function should the same thing as the one taking IntermediateAut or be deleted Nfa construct(const mata::parser::ParsedSection& parsec, Alphabet* alphabet, NameStateMap* state_map = nullptr); diff --git a/src/nfa/builder.cc b/src/nfa/builder.cc index c5bcf497b..598302b69 100644 --- a/src/nfa/builder.cc +++ b/src/nfa/builder.cc @@ -4,6 +4,8 @@ #include "mata/parser/mintermization.hh" #include +#include +#include using namespace mata::nfa; using mata::nfa::Nfa; @@ -217,6 +219,48 @@ Nfa builder::create_sigma_star_nfa(mata::Alphabet* alphabet) { return nfa; } +Nfa builder::create_tabakov_vardi_nfa(const size_t num_of_states, const size_t alphabet_size, const float transition_density, const float final_state_density) { + if (transition_density < 0 || static_cast(transition_density) > num_of_states) { + // Maximum of num_of_states^2 unique transitions for one symbol can be created. + throw std::runtime_error("Transition density must be in range [0, num_of_states]"); + } + if (final_state_density < 0 || final_state_density > 1) { + // Maximum of num_of_states final states can be created. + throw std::runtime_error("Final state density must be in range [0, 1]"); + } + + Nfa nfa{ num_of_states, StateSet{ 0 }, StateSet{ 0 }, new OnTheFlyAlphabet{} }; + + // Initialize the random number generator + std::random_device rd; // Seed for the random number engine + std::mt19937 gen(rd()); // Mersenne Twister engine + std::uniform_int_distribution state_dis(0, num_of_states - 1); + std::uniform_int_distribution symbol_dis(0, static_cast(alphabet_size - 1)); + + // Create final states + const size_t num_of_final_states{ static_cast(std::round(static_cast(num_of_states) * final_state_density)) }; + while (nfa.final.size() < num_of_final_states) { + nfa.final.insert(state_dis(gen)); + } + + // Create transitions + const size_t num_of_transitions_per_symbol{ static_cast(std::round(static_cast(num_of_states) * transition_density)) }; + for (Symbol symbol{ 0 }; symbol < alphabet_size; symbol++) { + size_t num_of_added_transitions = 0; + while (num_of_added_transitions < num_of_transitions_per_symbol) { + const State src_state{ state_dis(gen) }; + const State tgt_state{ state_dis(gen) }; + if (nfa.delta.contains(src_state, symbol, tgt_state)) { + continue; + } + nfa.delta.add(src_state, symbol, tgt_state); + num_of_added_transitions++; + } + } + + return nfa; +} + Nfa builder::parse_from_mata(std::istream& nfa_stream) { const std::string nfa_str = "NFA"; parser::Parsed parsed{ parser::parse_mf(nfa_stream) }; diff --git a/tests/nfa/builder.cc b/tests/nfa/builder.cc index 609c86086..541ec861c 100644 --- a/tests/nfa/builder.cc +++ b/tests/nfa/builder.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -141,3 +142,101 @@ TEST_CASE("parse_from_mata()") { } } } + +TEST_CASE("Create Tabakov-Vardi NFA") { + size_t num_of_states; + size_t alphabet_size; + float transition_density; + float final_state_density; + + SECTION("EMPTY") { + num_of_states = 0; + alphabet_size = 0; + transition_density = 0; + final_state_density = 0; + + Nfa nfa = mata::nfa::builder::create_tabakov_vardi_nfa(num_of_states, alphabet_size, transition_density, final_state_density); + CHECK(nfa.num_of_states() == 1); + CHECK(nfa.initial.size() == 1); + CHECK(nfa.final.size() == 1); + CHECK(nfa.delta.empty()); + } + + SECTION("10-5-0.5-0.5") { + num_of_states = 10; + alphabet_size = 5; + transition_density = 0.5; + final_state_density = 0.5; + + Nfa nfa = mata::nfa::builder::create_tabakov_vardi_nfa(num_of_states, alphabet_size, transition_density, final_state_density); + CHECK(nfa.num_of_states() == num_of_states); + CHECK(nfa.initial.size() == 1); + CHECK(nfa.final.size() == static_cast(std::round(final_state_density * static_cast(num_of_states)))); + CHECK(nfa.delta.get_used_symbols().size() <= alphabet_size); + CHECK(nfa.delta.num_of_transitions() == static_cast(std::round(transition_density * static_cast(num_of_states))) * alphabet_size); + } + + SECTION("Max final") { + num_of_states = 10; + alphabet_size = 5; + transition_density = 0.5; + final_state_density = 1; + + Nfa nfa = mata::nfa::builder::create_tabakov_vardi_nfa(num_of_states, alphabet_size, transition_density, final_state_density); + CHECK(nfa.num_of_states() == num_of_states); + CHECK(nfa.initial.size() == 1); + CHECK(nfa.final.size() == num_of_states); + CHECK(nfa.delta.get_used_symbols().size() <= alphabet_size); + CHECK(nfa.delta.num_of_transitions() == static_cast(std::round(transition_density * static_cast(num_of_states))) * alphabet_size); + } + + SECTION("Max transitions") { + num_of_states = 10; + alphabet_size = 5; + transition_density = 10; + final_state_density = 0.5; + + Nfa nfa = mata::nfa::builder::create_tabakov_vardi_nfa(num_of_states, alphabet_size, transition_density, final_state_density); + CHECK(nfa.num_of_states() == num_of_states); + CHECK(nfa.initial.size() == 1); + CHECK(nfa.final.size() == static_cast(std::round(final_state_density * static_cast(num_of_states)))); + CHECK(nfa.delta.get_used_symbols().size() <= alphabet_size); + CHECK(nfa.delta.num_of_transitions() == static_cast(std::round(transition_density * static_cast(num_of_states))) * alphabet_size); + } + + SECTION("Throw runtime_error. transition_density < 0") { + num_of_states = 10; + alphabet_size = 5; + transition_density = static_cast(-0.1); + final_state_density = 0.5; + + CHECK_THROWS_AS(mata::nfa::builder::create_tabakov_vardi_nfa(num_of_states, alphabet_size, transition_density, final_state_density), std::runtime_error); + } + + SECTION("Throw runtime_error. transition_density > num_of_states") { + num_of_states = 10; + alphabet_size = 5; + transition_density = 11; + final_state_density = 0.5; + + CHECK_THROWS_AS(mata::nfa::builder::create_tabakov_vardi_nfa(num_of_states, alphabet_size, transition_density, final_state_density), std::runtime_error); + } + + SECTION("Throw runtime_error. final_state_density < 0") { + num_of_states = 10; + alphabet_size = 5; + transition_density = 0.5; + final_state_density = static_cast(-0.1); + + CHECK_THROWS_AS(mata::nfa::builder::create_tabakov_vardi_nfa(num_of_states, alphabet_size, transition_density, final_state_density), std::runtime_error); + } + + SECTION("Throw runtime_error. final_state_density > 1") { + num_of_states = 10; + alphabet_size = 5; + transition_density = 0.5; + final_state_density = static_cast(1.1); + + CHECK_THROWS_AS(mata::nfa::builder::create_tabakov_vardi_nfa(num_of_states, alphabet_size, transition_density, final_state_density), std::runtime_error); + } +}