From 3d16e0993acad40768a67e2d8c3d9a0304ee388f Mon Sep 17 00:00:00 2001 From: koniksedy Date: Tue, 24 Jun 2025 16:37:18 +0200 Subject: [PATCH 01/65] wip --- include/mata/nft/nft.hh | 3 + include/mata/nft/types.hh | 3 +- src/nft/composition.cc | 274 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+), 1 deletion(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index 26a0ba28e..4e91f278e 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -776,6 +776,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, */ Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::RepeatSymbol); +Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, + bool are_sync_levels_unwinded = false, JumpMode jump_mode = JumpMode::RepeatSymbol); + /** * @brief Concatenate two NFTs. * diff --git a/include/mata/nft/types.hh b/include/mata/nft/types.hh index c9f031490..c7e329c98 100644 --- a/include/mata/nft/types.hh +++ b/include/mata/nft/types.hh @@ -43,7 +43,8 @@ class Nft; ///< A non-deterministic finite transducer. enum class JumpMode { RepeatSymbol, ///< Repeat the symbol on the jump. - AppendDontCares ///< Append a sequence of DONT_CAREs to the symbol on the jump. + AppendDontCares, ///< Append a sequence of DONT_CAREs to the symbol on the jump. + NoJump ///< No jumps are allowed. }; using ProductFinalStateCondition = mata::nfa::ProductFinalStateCondition; diff --git a/src/nft/composition.cc b/src/nft/composition.cc index aec33faa6..f28b9bae1 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -12,6 +12,280 @@ using namespace mata::utils; namespace mata::nft { +Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { + assert(lhs_sync_levels.size() == rhs_sync_levels.size()); + + // Number of Levels + const size_t num_of_sync_levels = lhs_sync_levels.size(); + const size_t lhs_num_of_levels = lhs.num_of_levels; + const size_t rhs_num_of_levels = rhs.num_of_levels; + const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (2 * num_of_sync_levels); + + // Calculate number of lhs/rhs transitions before/between/after sync levels + auto lhs_sync_levels_it = lhs_sync_levels.cbegin(); + auto rhs_sync_levels_it = rhs_sync_levels.cbegin(); + // Sync levels are on on borders between vector elements. + std::vector lhs_between(num_of_sync_levels + 1, 0); + std::vector rhs_between(num_of_sync_levels + 1, 0); + // Before the first sync level + lhs_between[0] = *lhs_sync_levels_it; + rhs_between[0] = *rhs_sync_levels_it; + // Between sync levels + for (size_t i = 1; i < num_of_sync_levels; ++i) { + const size_t lhs_prev = *lhs_sync_levels_it; + const size_t rhs_prev = *rhs_sync_levels_it; + ++lhs_sync_levels_it; + ++rhs_sync_levels_it; + lhs_between[i] = *lhs_sync_levels_it - lhs_prev - 1; + rhs_between[i] = *rhs_sync_levels_it - rhs_prev - 1; + } + // After the last sync level + lhs_between[num_of_sync_levels] = lhs_num_of_levels - *lhs_sync_levels_it - 1; + rhs_between[num_of_sync_levels] = rhs_num_of_levels - *rhs_sync_levels_it - 1; + + // Is level a sync level? + BoolVector lhs_is_sync_level(result_num_of_levels, false); + BoolVector rhs_is_sync_level(result_num_of_levels, false); + for (const Level level : lhs_sync_levels) { + lhs_is_sync_level[level] = true; + } + for (const Level level : rhs_sync_levels) { + rhs_is_sync_level[level] = true; + } + + // Sync levels inverted + // Element on index i (level i) tells the index of the level in sync_levels vector. + std::vector lhs_sync_levels_inv(lhs_num_of_levels, 0); + std::vector rhs_sync_levels_inv(rhs_num_of_levels, 0); + size_t idx = 0; + const auto lhs_end_it = lhs_sync_levels.cend(); + for (auto lhs_it = lhs_sync_levels.cbegin(), rhs_it = rhs_sync_levels.cbegin(); + lhs_it != lhs_end_it; + ++lhs_it, ++rhs_it, ++idx) + { + lhs_sync_levels_inv[*lhs_it] = idx; + rhs_sync_levels_inv[*rhs_it] = idx; + } + + // Initialize the result NFT, state map, and worklist + Nft result; + result.num_of_levels = result_num_of_levels; + std::unordered_map, State> state_map; + std::unordered_map pred_map; + std::stack> worklist; + + // Function: adds a new state to the result and puts it to the worklist + auto try_to_map_state_add_to_wordlist = [&](const State state, const State orig_a, const State orig_b, const bool is_a_lhs) { + const auto key = is_a_lhs ? std::make_pair(orig_a, orig_b) : std::make_pair(orig_b, orig_a); + auto it = state_map.find(key); + if (it != state_map.end()) { + assert(it->second == state); + return; + } + state_map[key] = state; + result.levels[state] = 0; + worklist.push(key); + if ((is_a_lhs && lhs.final.contains(orig_a) && rhs.final.contains(orig_b)) || + (!is_a_lhs && rhs.final.contains(orig_a) && lhs.final.contains(orig_b))) { + result.final.insert(state); + } + }; + + // Function: returns existing zero-level state or adds a new one and puts it to the worklist + auto get_state_or_add_and_put_to_worklist = [&](const State orig_a, const State orig_b, const bool is_a_lhs) { + const auto key = is_a_lhs ? std::make_pair(orig_a, orig_b) : std::make_pair(orig_b, orig_a); + auto it = state_map.find(key); + if (it != state_map.end()) { + return it->second; + } + const State new_state = result.add_state_with_level(0); + state_map[key] = new_state; + result.levels[new_state] = 0; + worklist.push(key); + if ((is_a_lhs && lhs.final.contains(orig_a) && rhs.final.contains(orig_b)) || + (!is_a_lhs && rhs.final.contains(orig_a) && lhs.final.contains(orig_b))) { + result.final.insert(new_state); + } + return new_state; + }; + + // Function: adds a transition from src to tgt with the given symbol + // TODO: Replace by a method in Nft + auto repeat_transition = [&](const State src, const size_t trans_len, const Symbol symbol) { + if (trans_len == 0) { + return src; // No transition, return the same state + } + if (jump_mode == JumpMode::RepeatSymbol || trans_len == 1) { + assert((result.levels[src] + trans_len) <= result_num_of_levels); + const State tgt = result.add_state_with_level((result.levels[src] + trans_len) % result_num_of_levels); + pred_map[tgt].insert(src); + result.delta.add(src, symbol, tgt); + return tgt; + } + State inner_src = src; + for (size_t i = 0; i < trans_len - 1; ++i) { + assert(result.levels[src] + 1 < result_num_of_levels); + const State tgt = result.add_state_with_level(result.levels[inner_src] + 1); + result.delta.add(inner_src, symbol, tgt); + inner_src = tgt; + } + assert((result.levels[inner_src] + 1) <= result_num_of_levels); + const State tgt = result.add_state_with_level((result.levels[inner_src] + 1) % result_num_of_levels); + pred_map[tgt].insert(inner_src); + result.delta.add(inner_src, symbol, tgt); + return tgt; + }; + + // Function: adds a transition from src to tgt with the given symbol + // TODO: Replace by a method in Nft + auto repeat_transition_with_tgt = [&](const State src, const State tgt, const size_t trans_len, const Symbol symbol) { + if (trans_len == 0) { + return; // No transition, do nothing + } + if (jump_mode == JumpMode::RepeatSymbol || trans_len == 1) { + pred_map[tgt].insert(src); + result.delta.add(src, symbol, tgt); + return; + } + State inner_src = repeat_transition(src, trans_len - 1, symbol); + pred_map[tgt].insert(inner_src); + result.delta.add(inner_src, symbol, tgt); + }; + + // Function: redirects transitions from old_tgt to new_tgt + auto redirect = [&](const State old_tgt, const State new_tgt) { + if (old_tgt == new_tgt) { + return; + } + auto it = pred_map.find(old_tgt); + assert(it != pred_map.end()); + for (const State pred: it->second) { + for (SymbolPost& symbol_post: result.delta.mutable_state_post(pred)) { + if (symbol_post.targets.contains(old_tgt)) { + symbol_post.targets.erase(old_tgt); + symbol_post.targets.insert(new_tgt); + } + } + } + }; + + // MAP EPSILON ON SYNC PATH function + auto map_epsilon_on_sync_path = [&](const State res_root, const State orig_root, const State wait_root, const bool is_orig_lhs) { + const Nft& orig_nft = is_orig_lhs ? lhs : rhs; + const OrdVector& sync_levels = is_orig_lhs ? lhs_sync_levels : rhs_sync_levels; + const std::vector& sync_levels_inv = is_orig_lhs ? lhs_sync_levels_inv : rhs_sync_levels_inv; + const std::vector& between = is_orig_lhs ? rhs_between : lhs_between; + const BoolVector& is_sync_level = is_orig_lhs ? lhs_is_sync_level : rhs_is_sync_level; + const bool add_before = !is_orig_lhs; + + // Worklist contains pairs of (state in the result NFT, and state in the original lhs/rhs NFT). + std::stack> worklist; + worklist.emplace(res_root, orig_root); + StateSet visited; // It is not necessary is the NFT has a valid structure (each cycle contains a zero-level state). + visited.insert(orig_root); + pred_map.clear(); + + while (!worklist.empty()) { + auto [res_src, orig_src] = worklist.top(); + worklist.pop(); + + // Add EPSILON transition from lhs BEFORE the first transition (we are in rhs). + if (result.levels[res_src] == 0 && add_before) { + res_src = repeat_transition(res_src, between[0], EPSILON); + } + + if (is_sync_level[orig_src]) { + // We are at a sync level. We need to match only the epsilon transitions. + const auto epsilon_post_it = orig_nft.delta[orig_src].find(EPSILON); + if (epsilon_post_it == orig_nft.delta[orig_src].end()) { + continue; + } + const size_t sync_level_idx = sync_levels_inv[orig_nft.levels[orig_src]]; + // Add EPSILON transitions from rhs AFTER this last transition before the sync level (we are in lhs). + if (!add_before) { + res_src = repeat_transition(res_src, between[sync_level_idx], EPSILON); + } + // Add EPSILON transitions from lhs BEFORE first transition after the sync level (we are in rhs). + if (add_before) { + res_src = repeat_transition(res_src, between[sync_level_idx + 1], EPSILON); + } + + // Process targets of the epsilon transitions (this transition will be projected out). + for (const State orig_tgt: epsilon_post_it->targets) { + if (orig_nft.levels[orig_tgt] == 0) { + // We are connecting to a zero-level state. + // Add EPSILON transitions from rhs AFTER this last transition (we are in lhs). + if (!add_before && between[num_of_sync_levels] != 0) { + const State tgt_res = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs); + repeat_transition_with_tgt(res_src, tgt_res, between[num_of_sync_levels], EPSILON); + } else { + // There is nothink to add. + // Try to find if there is already a mapping for this state. + const auto key = is_orig_lhs ? std::make_pair(orig_tgt, wait_root) : std::make_pair(wait_root, orig_tgt); + auto it = state_map.find(key); + if (it != state_map.end()) { + // The mapping exists, redirect transition goig to orig_src to the existing state. + redirect(res_src, it->second); + } else { + // The mapping does not exist. Update it. + try_to_map_state_add_to_wordlist(res_src, orig_tgt, wait_root, is_orig_lhs); + } + } + } else { + // We are connecting to a non-zero-level state. + if (!visited.contains(orig_tgt)) { + // Let's contiinue the traversal. + // Project out the transition. Merge orig_scr and orig_tgt, while staying in res_src. + worklist.push({res_src, orig_tgt}); + visited.insert(orig_tgt); + } + } + } + } else { + // We are not at a sync level. We need just to copy the transitions. + const Level orig_src_level = orig_nft.levels[orig_src]; + for (const SymbolPost& symbol_post: orig_nft.delta[orig_src]) { + for (const State orig_tgt: symbol_post.targets) { + const Level orig_tgt_level = orig_nft.levels[orig_tgt]; + + if (orig_tgt_level == 0) { + // We are connecting to a zero-level state. + const Level level_diff = orig_nft.num_of_levels - orig_src_level; + if (!add_before && between[num_of_sync_levels] != 0) { + // We will add EPSILON transitions from rhs AFTER this last transition (we are in lhs). + assert(result.levels[res_src] + level_diff < result_num_of_levels); + const State tgt_res_inner = result.add_state_with_level(result.levels[res_src] + level_diff); + // First process the transition with the symbol_post.symbol. + result.delta.add(res_src, symbol_post.symbol, tgt_res_inner); + // Then add the EPSILON transitions. + State tgt_res = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs); + repeat_transition_with_tgt(tgt_res_inner, tgt_res, between[num_of_sync_levels], EPSILON); + } else { + State tgt_res = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs); + result.delta.add(res_src, symbol_post.symbol, tgt_res); + } + } else { + // We are connecting to a non-zero-level state. + // Just copy the transition. + const Level level_diff = orig_tgt_level - orig_src_level; + assert(result.levels[res_src] + level_diff < result_num_of_levels); + const State tgt_res = result.add_state_with_level(result.levels[res_src] + level_diff); + result.delta.add(res_src, symbol_post.symbol, tgt_res); + if (!visited.contains(orig_tgt)) { + // Not visited, add to the worklist + worklist.push({tgt_res, orig_tgt}); + visited.insert(orig_tgt); + } + } + } + } + } + } + }; +} + + + Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { assert(!lhs_sync_levels.empty()); assert(lhs_sync_levels.size() == rhs_sync_levels.size()); From 516e11d57b5ae06e511867e0c1ae530afae7f3eb Mon Sep 17 00:00:00 2001 From: koniksedy Date: Tue, 24 Jun 2025 20:56:33 +0200 Subject: [PATCH 02/65] draft draft draft --- src/nft/composition.cc | 270 +++++++++++++++++++++++++++++++++++------ 1 file changed, 235 insertions(+), 35 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index f28b9bae1..693e72c62 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -12,7 +12,7 @@ using namespace mata::utils; namespace mata::nft { -Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { +Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { assert(lhs_sync_levels.size() == rhs_sync_levels.size()); // Number of Levels @@ -44,8 +44,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn rhs_between[num_of_sync_levels] = rhs_num_of_levels - *rhs_sync_levels_it - 1; // Is level a sync level? - BoolVector lhs_is_sync_level(result_num_of_levels, false); - BoolVector rhs_is_sync_level(result_num_of_levels, false); + BoolVector lhs_is_sync_level(lhs_num_of_levels, false); + BoolVector rhs_is_sync_level(rhs_num_of_levels, false); for (const Level level : lhs_sync_levels) { lhs_is_sync_level[level] = true; } @@ -55,8 +55,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn // Sync levels inverted // Element on index i (level i) tells the index of the level in sync_levels vector. - std::vector lhs_sync_levels_inv(lhs_num_of_levels, 0); - std::vector rhs_sync_levels_inv(rhs_num_of_levels, 0); + std::vector lhs_sync_levels_inv(lhs_num_of_levels, std::numeric_limits::max()); + std::vector rhs_sync_levels_inv(rhs_num_of_levels, std::numeric_limits::max()); size_t idx = 0; const auto lhs_end_it = lhs_sync_levels.cend(); for (auto lhs_it = lhs_sync_levels.cbegin(), rhs_it = rhs_sync_levels.cbegin(); @@ -70,19 +70,19 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn // Initialize the result NFT, state map, and worklist Nft result; result.num_of_levels = result_num_of_levels; - std::unordered_map, State> state_map; + std::unordered_map, State> main_state_map; std::unordered_map pred_map; std::stack> worklist; // Function: adds a new state to the result and puts it to the worklist auto try_to_map_state_add_to_wordlist = [&](const State state, const State orig_a, const State orig_b, const bool is_a_lhs) { const auto key = is_a_lhs ? std::make_pair(orig_a, orig_b) : std::make_pair(orig_b, orig_a); - auto it = state_map.find(key); - if (it != state_map.end()) { + auto it = main_state_map.find(key); + if (it != main_state_map.end()) { assert(it->second == state); return; } - state_map[key] = state; + main_state_map[key] = state; result.levels[state] = 0; worklist.push(key); if ((is_a_lhs && lhs.final.contains(orig_a) && rhs.final.contains(orig_b)) || @@ -92,15 +92,14 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn }; // Function: returns existing zero-level state or adds a new one and puts it to the worklist - auto get_state_or_add_and_put_to_worklist = [&](const State orig_a, const State orig_b, const bool is_a_lhs) { + auto get_state_or_add_and_put_to_worklist = [&](const State orig_a, const State orig_b, const bool is_a_lhs, std::stack>& worklist) { const auto key = is_a_lhs ? std::make_pair(orig_a, orig_b) : std::make_pair(orig_b, orig_a); - auto it = state_map.find(key); - if (it != state_map.end()) { + auto it = main_state_map.find(key); + if (it != main_state_map.end()) { return it->second; } const State new_state = result.add_state_with_level(0); - state_map[key] = new_state; - result.levels[new_state] = 0; + main_state_map[key] = new_state; worklist.push(key); if ((is_a_lhs && lhs.final.contains(orig_a) && rhs.final.contains(orig_b)) || (!is_a_lhs && rhs.final.contains(orig_a) && lhs.final.contains(orig_b))) { @@ -109,6 +108,22 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn return new_state; }; + auto get_state = [&](const State lhs_state, const State rhs_state, const Level level) { + // Get the state from the map or add a new one. + const auto key = std::make_pair(lhs_state, rhs_state); + auto it = main_state_map.find(key); + if (it != main_state_map.end()) { + return it->second; + } + const State new_state = result.add_state_with_level(level); + main_state_map[key] = new_state; + worklist.push(key); + if (lhs.final.contains(lhs_state) && rhs.final.contains(rhs_state)) { + result.final.insert(new_state); + } + return new_state; + }; + // Function: adds a transition from src to tgt with the given symbol // TODO: Replace by a method in Nft auto repeat_transition = [&](const State src, const size_t trans_len, const Symbol symbol) { @@ -124,7 +139,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn } State inner_src = src; for (size_t i = 0; i < trans_len - 1; ++i) { - assert(result.levels[src] + 1 < result_num_of_levels); + assert(result.levels[inner_src] + 1 < result_num_of_levels); const State tgt = result.add_state_with_level(result.levels[inner_src] + 1); result.delta.add(inner_src, symbol, tgt); inner_src = tgt; @@ -169,14 +184,15 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn } }; - // MAP EPSILON ON SYNC PATH function + // Function: maps epsilon transitions on the sync path auto map_epsilon_on_sync_path = [&](const State res_root, const State orig_root, const State wait_root, const bool is_orig_lhs) { const Nft& orig_nft = is_orig_lhs ? lhs : rhs; const OrdVector& sync_levels = is_orig_lhs ? lhs_sync_levels : rhs_sync_levels; const std::vector& sync_levels_inv = is_orig_lhs ? lhs_sync_levels_inv : rhs_sync_levels_inv; - const std::vector& between = is_orig_lhs ? rhs_between : lhs_between; + const std::vector& between = is_orig_lhs ? rhs_between : lhs_between; // Use rhs_between if we are in lhs NFT, and vice versa. const BoolVector& is_sync_level = is_orig_lhs ? lhs_is_sync_level : rhs_is_sync_level; - const bool add_before = !is_orig_lhs; + const bool add_before = !is_orig_lhs; // On each section within sync levels, lhs transitions go begore rhs transitions. + static std::stack>& main_worklist = worklist; // Worklist contains pairs of (state in the result NFT, and state in the original lhs/rhs NFT). std::stack> worklist; @@ -185,11 +201,24 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn visited.insert(orig_root); pred_map.clear(); + std::unordered_map local_state_map; + auto get_state = [&](const State orig_state, const Level level) { + // Get the state from the map or add a new one. + auto it = local_state_map.find(orig_state); + if (it != local_state_map.end()) { + assert(orig_nft.levels[it->second] == level); + return it->second; + } + const State new_state = result.add_state_with_level(level); + local_state_map[orig_state] = new_state; + return new_state; + }; + while (!worklist.empty()) { auto [res_src, orig_src] = worklist.top(); worklist.pop(); - // Add EPSILON transition from lhs BEFORE the first transition (we are in rhs). + // Add EPSILON transition for those in lhs BEFORE the first transition (we are in rhs). if (result.levels[res_src] == 0 && add_before) { res_src = repeat_transition(res_src, between[0], EPSILON); } @@ -201,11 +230,11 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn continue; } const size_t sync_level_idx = sync_levels_inv[orig_nft.levels[orig_src]]; - // Add EPSILON transitions from rhs AFTER this last transition before the sync level (we are in lhs). + // Add EPSILON transitions for those in rhs AFTER this last transition before the sync level (we are in lhs). if (!add_before) { res_src = repeat_transition(res_src, between[sync_level_idx], EPSILON); } - // Add EPSILON transitions from lhs BEFORE first transition after the sync level (we are in rhs). + // Add EPSILON transitions for those in lhs BEFORE first transition after the sync level (we are in rhs). if (add_before) { res_src = repeat_transition(res_src, between[sync_level_idx + 1], EPSILON); } @@ -214,16 +243,16 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn for (const State orig_tgt: epsilon_post_it->targets) { if (orig_nft.levels[orig_tgt] == 0) { // We are connecting to a zero-level state. - // Add EPSILON transitions from rhs AFTER this last transition (we are in lhs). + // Add EPSILON transitions for those in rhs AFTER this last transition (we are in lhs). if (!add_before && between[num_of_sync_levels] != 0) { - const State tgt_res = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs); - repeat_transition_with_tgt(res_src, tgt_res, between[num_of_sync_levels], EPSILON); + const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); + repeat_transition_with_tgt(res_src, res_tgt, between[num_of_sync_levels], EPSILON); } else { - // There is nothink to add. + // There is nothing to add. // Try to find if there is already a mapping for this state. const auto key = is_orig_lhs ? std::make_pair(orig_tgt, wait_root) : std::make_pair(wait_root, orig_tgt); - auto it = state_map.find(key); - if (it != state_map.end()) { + auto it = main_state_map.find(key); + if (it != main_state_map.end()) { // The mapping exists, redirect transition goig to orig_src to the existing state. redirect(res_src, it->second); } else { @@ -234,7 +263,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn } else { // We are connecting to a non-zero-level state. if (!visited.contains(orig_tgt)) { - // Let's contiinue the traversal. + // Let's continue the traversal. // Project out the transition. Merge orig_scr and orig_tgt, while staying in res_src. worklist.push({res_src, orig_tgt}); visited.insert(orig_tgt); @@ -254,23 +283,25 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn if (!add_before && between[num_of_sync_levels] != 0) { // We will add EPSILON transitions from rhs AFTER this last transition (we are in lhs). assert(result.levels[res_src] + level_diff < result_num_of_levels); - const State tgt_res_inner = result.add_state_with_level(result.levels[res_src] + level_diff); + const State res_tgt_inner = get_state(orig_tgt, result.levels[res_src] + level_diff); // First process the transition with the symbol_post.symbol. - result.delta.add(res_src, symbol_post.symbol, tgt_res_inner); + result.delta.add(res_src, symbol_post.symbol, res_tgt_inner); // Then add the EPSILON transitions. - State tgt_res = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs); - repeat_transition_with_tgt(tgt_res_inner, tgt_res, between[num_of_sync_levels], EPSILON); + const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); + repeat_transition_with_tgt(res_tgt_inner, res_tgt, between[num_of_sync_levels], EPSILON); } else { - State tgt_res = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs); - result.delta.add(res_src, symbol_post.symbol, tgt_res); + assert(result.levels[res_src] + level_diff == result_num_of_levels); + const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); + result.delta.add(res_src, symbol_post.symbol, res_tgt); } } else { // We are connecting to a non-zero-level state. // Just copy the transition. const Level level_diff = orig_tgt_level - orig_src_level; assert(result.levels[res_src] + level_diff < result_num_of_levels); - const State tgt_res = result.add_state_with_level(result.levels[res_src] + level_diff); + const State tgt_res = get_state(orig_tgt, result.levels[res_src] + level_diff); result.delta.add(res_src, symbol_post.symbol, tgt_res); + pred_map[tgt_res].insert(res_src); if (!visited.contains(orig_tgt)) { // Not visited, add to the worklist worklist.push({tgt_res, orig_tgt}); @@ -282,6 +313,175 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_syn } } }; + + auto map_combination_path = [&](const State res_root, const State lhs_root, const State rhs_root) { + static std::stack>& main_worklist = worklist; + pred_map.clear(); + std::stack> worklist; + worklist.push({res_root, lhs_root, rhs_root}); + std::unordered_set> visited; + visited.insert({lhs_root, rhs_root}); + + while (!worklist.empty()) { + auto [res_src, lhs_src, rhs_src] = worklist.top(); + worklist.pop(); + + const Level res_src_level = result.levels[res_src]; + const Level lhs_src_level = lhs.levels[lhs_src]; + const Level rhs_src_level = rhs.levels[rhs_src]; + + // Go in lhs all the way down to the sync level. + if (!lhs_is_sync_level[lhs_src_level]) { + for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { + for (const State lhs_tgt: symbol_post.targets) { + const Level lhs_tgt_level = lhs.levels[lhs_tgt]; + if (lhs_tgt_level == 0) { + const size_t trans_len = lhs_num_of_levels - lhs_src_level; + // We are connecting to a zero-level state. + if (rhs_between[num_of_sync_levels] != 0) { + // There is no transition in rhs. + const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_root, true, main_worklist); + } else { + // There is a transition in rhs. + const State res_tgt = get_state(lhs_tgt, rhs_src, trans_len + res_src_level); + result.delta.add(res_src, symbol_post.symbol, res_tgt); + pred_map[res_tgt].insert(res_src); + } + } else { + // We are connecting to a non-zero-level state. + const Level level_diff = lhs_tgt_level - lhs_src_level; + assert(res_src_level + level_diff < result_num_of_levels); + const State res_tgt = get_state(lhs_tgt, rhs_src, res_src_level + level_diff); + result.delta.add(res_src, symbol_post.symbol, res_tgt); + pred_map[res_tgt].insert(res_src); + if (!visited.contains({lhs_tgt, rhs_src})) { + // Not visited, add to the worklist + worklist.push({res_tgt, lhs_tgt, rhs_src}); + visited.insert({lhs_tgt, rhs_src}); + } + } + } + } + continue; + } + + // Go in rhs all the way down to the sync level. + if (!rhs_is_sync_level[rhs_src_level]) { + for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { + for (const State rhs_tgt: symbol_post.targets) { + const Level rhs_tgt_level = rhs.levels[rhs_tgt]; + if (rhs_tgt_level == 0) { + const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_src, rhs_tgt, true, main_worklist); + result.delta.add(res_src, symbol_post.symbol, res_tgt); + } else { + // We are connecting to a non-zero-level state. + const Level level_diff = rhs_tgt_level - rhs_src_level; + assert(res_src_level + level_diff < result_num_of_levels); + const State res_tgt = get_state(lhs_src, rhs_tgt, res_src_level + level_diff); + result.delta.add(res_src, symbol_post.symbol, res_tgt); + pred_map[res_tgt].insert(res_src); + if (!visited.contains({lhs_src, rhs_tgt})) { + // Not visited, add to the worklist + worklist.push({res_tgt, lhs_src, rhs_tgt}); + visited.insert({lhs_src, rhs_tgt}); + } + } + } + } + continue; + } + + // Everythink is on sync level. + const bool epsilon_in_lhs = lhs.delta[lhs_src].find(EPSILON) != lhs.delta[lhs_src].end(); + const bool epsilon_in_rhs = rhs.delta[rhs_src].find(EPSILON) != rhs.delta[rhs_src].end(); + + auto process_intersection = [&](const StateSet lhs_targets, const StateSet rhs_targets, const std::vector levels) { + for (const State lhs_tgt: lhs_targets) { + const Level tgt_level = levels[lhs_tgt]; + for (const State rhs_tgt: rhs_targets) { + if (tgt_level == 0) { + const auto key = std::make_pair(lhs_tgt, rhs_tgt); + auto it = main_state_map.find(key); + if (it != main_state_map.end()) { + // The mapping exists, redirect transition goig to orig_src to the existing state. + redirect(res_src, it->second); + } else { + // The mapping does not exist. Update it. + try_to_map_state_add_to_wordlist(res_src, lhs_tgt, rhs_tgt, true); + } + } else { + if (!visited.contains({lhs_tgt, rhs_tgt})) { + // We have not visited this pair yet. + visited.insert({lhs_tgt, rhs_tgt}); + main_state_map[std::make_pair(lhs_tgt, rhs_tgt)] = res_src; + worklist.push({res_src, lhs_tgt, rhs_tgt}); + } + } + } + } + }; + + // Intersection + mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); + mata::utils::push_back(sync_iterator, lhs.delta[lhs_src]); + mata::utils::push_back(sync_iterator, rhs.delta[rhs_src]); + while (sync_iterator.advance()) { + const std::vector& same_symbol_posts{ sync_iterator.get_current() }; + assert(same_symbol_posts.size() == 2); // One move per state in the pair. + + process_intersection( + same_symbol_posts[0]->targets, + same_symbol_posts[1]->targets, + lhs.levels + ); + } + + if (lhs.delta[lhs_src].find(DONT_CARE) != lhs.delta[lhs_src].end()) { + for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { + process_intersection( + lhs.delta[lhs_src].find(DONT_CARE)->targets, + symbol_post.targets, + lhs.levels + ); + } + } + + if (rhs.delta[rhs_src].find(DONT_CARE) != rhs.delta[rhs_src].end()) { + for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { + process_intersection( + symbol_post.targets, + rhs.delta[rhs_src].find(DONT_CARE)->targets, + lhs.levels + ); + } + } + + } + + + }; + + // // Test epsilon transition on lhs + // for (const State lhs_root: lhs.initial) { + // for (const State rhs_root: rhs.initial) { + // // Get the root state in the result NFT + // const State res_root = get_state_or_add_and_put_to_worklist(lhs_root, rhs_root, true); + // } + // } + // while (!worklist.empty()) { + // auto [orig_a, orig_b] = worklist.top(); + // worklist.pop(); + + // // Get the state in the result NFT + // const State res_root = state_map.at({orig_a, orig_b}); + + // // Map epsilon transitions on the sync path + // map_epsilon_on_sync_path(res_root, orig_a, orig_b, true); + // } + + + + return result; } From 3a9b951de0e681285262de16570c80ad1460f449 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 25 Jun 2025 17:17:23 +0200 Subject: [PATCH 03/65] map_epsilon_path tested --- include/mata/nft/nft.hh | 2 +- src/nft/composition.cc | 84 +++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index 4e91f278e..4bfea931d 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -776,7 +776,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, */ Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::RepeatSymbol); -Nft compose_fast(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, +Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, bool are_sync_levels_unwinded = false, JumpMode jump_mode = JumpMode::RepeatSymbol); /** diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 693e72c62..e156b33e9 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -12,7 +12,7 @@ using namespace mata::utils; namespace mata::nft { -Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { +Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { assert(lhs_sync_levels.size() == rhs_sync_levels.size()); // Number of Levels @@ -177,7 +177,7 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co for (const State pred: it->second) { for (SymbolPost& symbol_post: result.delta.mutable_state_post(pred)) { if (symbol_post.targets.contains(old_tgt)) { - symbol_post.targets.erase(old_tgt); + // symbol_post.targets.erase(old_tgt); symbol_post.targets.insert(new_tgt); } } @@ -206,7 +206,7 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co // Get the state from the map or add a new one. auto it = local_state_map.find(orig_state); if (it != local_state_map.end()) { - assert(orig_nft.levels[it->second] == level); + assert(result.levels[it->second] == level); return it->second; } const State new_state = result.add_state_with_level(level); @@ -283,12 +283,15 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co if (!add_before && between[num_of_sync_levels] != 0) { // We will add EPSILON transitions from rhs AFTER this last transition (we are in lhs). assert(result.levels[res_src] + level_diff < result_num_of_levels); + const bool exist_res_tgt_inner = local_state_map.contains(orig_tgt); const State res_tgt_inner = get_state(orig_tgt, result.levels[res_src] + level_diff); // First process the transition with the symbol_post.symbol. result.delta.add(res_src, symbol_post.symbol, res_tgt_inner); - // Then add the EPSILON transitions. - const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); - repeat_transition_with_tgt(res_tgt_inner, res_tgt, between[num_of_sync_levels], EPSILON); + if (!exist_res_tgt_inner) { + // Then add the EPSILON transitions. + const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); + repeat_transition_with_tgt(res_tgt_inner, res_tgt, between[num_of_sync_levels], EPSILON); + } } else { assert(result.levels[res_src] + level_diff == result_num_of_levels); const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); @@ -298,7 +301,7 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co // We are connecting to a non-zero-level state. // Just copy the transition. const Level level_diff = orig_tgt_level - orig_src_level; - assert(result.levels[res_src] + level_diff < result_num_of_levels); + assert(result.levels[res_src] + level_diff <= result_num_of_levels); const State tgt_res = get_state(orig_tgt, result.levels[res_src] + level_diff); result.delta.add(res_src, symbol_post.symbol, tgt_res); pred_map[tgt_res].insert(res_src); @@ -330,8 +333,10 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co const Level lhs_src_level = lhs.levels[lhs_src]; const Level rhs_src_level = rhs.levels[rhs_src]; + const bool lhs_in_target_and_rhs_remains = lhs_src_level == 0 || rhs_src_level != 0; + // Go in lhs all the way down to the sync level. - if (!lhs_is_sync_level[lhs_src_level]) { + if (!lhs_is_sync_level[lhs_src_level] && !lhs_in_target_and_rhs_remains) { for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { for (const State lhs_tgt: symbol_post.targets) { const Level lhs_tgt_level = lhs.levels[lhs_tgt]; @@ -341,11 +346,17 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co if (rhs_between[num_of_sync_levels] != 0) { // There is no transition in rhs. const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_root, true, main_worklist); + result.delta.add(res_src, symbol_post.symbol, res_tgt); } else { // There is a transition in rhs. const State res_tgt = get_state(lhs_tgt, rhs_src, trans_len + res_src_level); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); + if (!visited.contains({lhs_tgt, rhs_src})) { + // Not visited, add to the worklist + worklist.push({res_tgt, lhs_tgt, rhs_src}); + visited.insert({lhs_tgt, rhs_src}); + } } } else { // We are connecting to a non-zero-level state. @@ -395,11 +406,12 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co const bool epsilon_in_lhs = lhs.delta[lhs_src].find(EPSILON) != lhs.delta[lhs_src].end(); const bool epsilon_in_rhs = rhs.delta[rhs_src].find(EPSILON) != rhs.delta[rhs_src].end(); - auto process_intersection = [&](const StateSet lhs_targets, const StateSet rhs_targets, const std::vector levels) { + auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets) { for (const State lhs_tgt: lhs_targets) { - const Level tgt_level = levels[lhs_tgt]; + const Level lhs_tgt_level = lhs.levels[lhs_tgt]; for (const State rhs_tgt: rhs_targets) { - if (tgt_level == 0) { + const Level rhs_tgt_level = rhs.levels[rhs_tgt]; + if (lhs_tgt_level == 0 && rhs_tgt_level == 0) { const auto key = std::make_pair(lhs_tgt, rhs_tgt); auto it = main_state_map.find(key); if (it != main_state_map.end()) { @@ -431,8 +443,7 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co process_intersection( same_symbol_posts[0]->targets, - same_symbol_posts[1]->targets, - lhs.levels + same_symbol_posts[1]->targets ); } @@ -440,8 +451,7 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { process_intersection( lhs.delta[lhs_src].find(DONT_CARE)->targets, - symbol_post.targets, - lhs.levels + symbol_post.targets ); } } @@ -450,36 +460,38 @@ Nft compose_fast(Nft& lhs, Nft& rhs, const OrdVector& lhs_sync_levels, co for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { process_intersection( symbol_post.targets, - rhs.delta[rhs_src].find(DONT_CARE)->targets, - lhs.levels + rhs.delta[rhs_src].find(DONT_CARE)->targets ); } } - } - - }; - // // Test epsilon transition on lhs - // for (const State lhs_root: lhs.initial) { - // for (const State rhs_root: rhs.initial) { - // // Get the root state in the result NFT - // const State res_root = get_state_or_add_and_put_to_worklist(lhs_root, rhs_root, true); - // } - // } - // while (!worklist.empty()) { - // auto [orig_a, orig_b] = worklist.top(); - // worklist.pop(); - - // // Get the state in the result NFT - // const State res_root = state_map.at({orig_a, orig_b}); + // Test epsilon transition on lhs + for (const State lhs_root: lhs.initial) { + for (const State rhs_root: rhs.initial) { + // Get the root state in the result NFT + const State res_root = get_state_or_add_and_put_to_worklist(lhs_root, rhs_root, true, worklist); + } + } + while (!worklist.empty()) { + auto [orig_a, orig_b] = worklist.top(); + worklist.pop(); - // // Map epsilon transitions on the sync path - // map_epsilon_on_sync_path(res_root, orig_a, orig_b, true); - // } + // Get the state in the result NFT + const State res_root = main_state_map.at({orig_a, orig_b}); + // Map epsilon transitions on the sync path + map_epsilon_on_sync_path(res_root, orig_a, orig_b, true); + // map_epsilon_on_sync_path(res_root, orig_b, orig_a, false); + break; + } + // Print main_state_map + for (const auto [key, value]: main_state_map) { + const auto [lhs_s, rhs_s] = key; + std::cout << "[" << lhs_s << "," << rhs_s << "] -> " << value << std::endl; + } return result; } From 67da98c9842d68038217d7546b78b722697dc8d9 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 25 Jun 2025 19:38:59 +0200 Subject: [PATCH 04/65] map_common trans tested --- src/nft/composition.cc | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index e156b33e9..46a37da80 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -73,6 +73,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& std::unordered_map, State> main_state_map; std::unordered_map pred_map; std::stack> worklist; + std::unordered_set commited_states; // Function: adds a new state to the result and puts it to the worklist auto try_to_map_state_add_to_wordlist = [&](const State state, const State orig_a, const State orig_b, const bool is_a_lhs) { @@ -177,7 +178,6 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const State pred: it->second) { for (SymbolPost& symbol_post: result.delta.mutable_state_post(pred)) { if (symbol_post.targets.contains(old_tgt)) { - // symbol_post.targets.erase(old_tgt); symbol_post.targets.insert(new_tgt); } } @@ -247,6 +247,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& if (!add_before && between[num_of_sync_levels] != 0) { const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); repeat_transition_with_tgt(res_src, res_tgt, between[num_of_sync_levels], EPSILON); + commited_states.insert(res_tgt); } else { // There is nothing to add. // Try to find if there is already a mapping for this state. @@ -258,6 +259,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } else { // The mapping does not exist. Update it. try_to_map_state_add_to_wordlist(res_src, orig_tgt, wait_root, is_orig_lhs); + commited_states.insert(res_src); } } } else { @@ -291,18 +293,20 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Then add the EPSILON transitions. const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); repeat_transition_with_tgt(res_tgt_inner, res_tgt, between[num_of_sync_levels], EPSILON); + commited_states.insert(res_tgt); } } else { assert(result.levels[res_src] + level_diff == result_num_of_levels); const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); result.delta.add(res_src, symbol_post.symbol, res_tgt); + commited_states.insert(res_tgt); } } else { // We are connecting to a non-zero-level state. // Just copy the transition. const Level level_diff = orig_tgt_level - orig_src_level; assert(result.levels[res_src] + level_diff <= result_num_of_levels); - const State tgt_res = get_state(orig_tgt, result.levels[res_src] + level_diff); + const State tgt_res = get_state(orig_tgt, (result.levels[res_src] + level_diff) % result_num_of_levels); result.delta.add(res_src, symbol_post.symbol, tgt_res); pred_map[tgt_res].insert(res_src); if (!visited.contains(orig_tgt)) { @@ -333,7 +337,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const Level lhs_src_level = lhs.levels[lhs_src]; const Level rhs_src_level = rhs.levels[rhs_src]; - const bool lhs_in_target_and_rhs_remains = lhs_src_level == 0 || rhs_src_level != 0; + const bool lhs_in_target_and_rhs_remains = lhs_src_level == 0 && rhs_src_level != 0; // Go in lhs all the way down to the sync level. if (!lhs_is_sync_level[lhs_src_level] && !lhs_in_target_and_rhs_remains) { @@ -343,10 +347,11 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& if (lhs_tgt_level == 0) { const size_t trans_len = lhs_num_of_levels - lhs_src_level; // We are connecting to a zero-level state. - if (rhs_between[num_of_sync_levels] != 0) { + if (rhs_between[num_of_sync_levels] == 0) { // There is no transition in rhs. - const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_root, true, main_worklist); + const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_src, true, main_worklist); result.delta.add(res_src, symbol_post.symbol, res_tgt); + commited_states.insert(res_tgt); } else { // There is a transition in rhs. const State res_tgt = get_state(lhs_tgt, rhs_src, trans_len + res_src_level); @@ -361,8 +366,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } else { // We are connecting to a non-zero-level state. const Level level_diff = lhs_tgt_level - lhs_src_level; - assert(res_src_level + level_diff < result_num_of_levels); - const State res_tgt = get_state(lhs_tgt, rhs_src, res_src_level + level_diff); + assert(res_src_level + level_diff <= result_num_of_levels); + const State res_tgt = get_state(lhs_tgt, rhs_src, (res_src_level + level_diff) % result_num_of_levels); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); if (!visited.contains({lhs_tgt, rhs_src})) { @@ -384,11 +389,12 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& if (rhs_tgt_level == 0) { const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_src, rhs_tgt, true, main_worklist); result.delta.add(res_src, symbol_post.symbol, res_tgt); + commited_states.insert(res_tgt); } else { // We are connecting to a non-zero-level state. const Level level_diff = rhs_tgt_level - rhs_src_level; - assert(res_src_level + level_diff < result_num_of_levels); - const State res_tgt = get_state(lhs_src, rhs_tgt, res_src_level + level_diff); + assert(res_src_level + level_diff <= result_num_of_levels); + const State res_tgt = get_state(lhs_src, rhs_tgt, (res_src_level + level_diff) % result_num_of_levels); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); if (!visited.contains({lhs_src, rhs_tgt})) { @@ -420,6 +426,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } else { // The mapping does not exist. Update it. try_to_map_state_add_to_wordlist(res_src, lhs_tgt, rhs_tgt, true); + commited_states.insert(res_src); } } else { if (!visited.contains({lhs_tgt, rhs_tgt})) { @@ -467,11 +474,12 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } }; - // Test epsilon transition on lhs + // Test common transitions mapping for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { // Get the root state in the result NFT const State res_root = get_state_or_add_and_put_to_worklist(lhs_root, rhs_root, true, worklist); + commited_states.insert(res_root); } } while (!worklist.empty()) { @@ -482,16 +490,15 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const State res_root = main_state_map.at({orig_a, orig_b}); // Map epsilon transitions on the sync path - map_epsilon_on_sync_path(res_root, orig_a, orig_b, true); - // map_epsilon_on_sync_path(res_root, orig_b, orig_a, false); + map_combination_path(res_root, orig_a, orig_b); break; } // Print main_state_map - for (const auto [key, value]: main_state_map) { - const auto [lhs_s, rhs_s] = key; - std::cout << "[" << lhs_s << "," << rhs_s << "] -> " << value << std::endl; - } + // for (const auto [key, value]: main_state_map) { + // const auto [lhs_s, rhs_s] = key; + // std::cout << "[" << lhs_s << "," << rhs_s << "] -> " << value << std::endl; + // } return result; } From 3d15c665943837692abe741e30697ce3df0448c8 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 25 Jun 2025 19:46:06 +0200 Subject: [PATCH 05/65] rest of the algorithm --- src/nft/composition.cc | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 46a37da80..82e654803 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -329,6 +329,9 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& std::unordered_set> visited; visited.insert({lhs_root, rhs_root}); + bool perform_lhs_wait = false; + bool perform_rhs_wait = false; + while (!worklist.empty()) { auto [res_src, lhs_src, rhs_src] = worklist.top(); worklist.pop(); @@ -411,6 +414,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Everythink is on sync level. const bool epsilon_in_lhs = lhs.delta[lhs_src].find(EPSILON) != lhs.delta[lhs_src].end(); const bool epsilon_in_rhs = rhs.delta[rhs_src].find(EPSILON) != rhs.delta[rhs_src].end(); + perform_lhs_wait ||= (epsilon_in_rhs && !epsilon_in_lhs); + perform_rhs_wait ||= (epsilon_in_lhs && !epsilon_in_rhs); auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets) { for (const State lhs_tgt: lhs_targets) { @@ -472,9 +477,16 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } } } + + if (perform_lhs_wait) { + map_epsilon_on_sync_path(res_root, rhs_root, lhs_root, false); + } + if (perform_rhs_wait) { + map_epsilon_on_sync_path(res_root, lhs_root, rhs_root, true); + } }; - // Test common transitions mapping + // INITIALIZATION for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { // Get the root state in the result NFT @@ -482,24 +494,23 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& commited_states.insert(res_root); } } + + // MAIN LOOP while (!worklist.empty()) { auto [orig_a, orig_b] = worklist.top(); worklist.pop(); // Get the state in the result NFT const State res_root = main_state_map.at({orig_a, orig_b}); + if (!commited_states.contains(res_root)) { + final.erase(res_root); + continue; + } // Map epsilon transitions on the sync path map_combination_path(res_root, orig_a, orig_b); - break; } - // Print main_state_map - // for (const auto [key, value]: main_state_map) { - // const auto [lhs_s, rhs_s] = key; - // std::cout << "[" << lhs_s << "," << rhs_s << "] -> " << value << std::endl; - // } - return result; } From 1e5fec57b41c04007525a519725fddca1a65452f Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 26 Jun 2025 10:38:31 +0200 Subject: [PATCH 06/65] it seems that it works --- include/mata/nft/nft.hh | 2 +- src/nft/composition.cc | 97 ++++++++++++++++++++++--------- src/strings/nfa-noodlification.cc | 1 - tests/nft/nft-composition.cc | 22 ++----- 4 files changed, 73 insertions(+), 49 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index 4bfea931d..92f411207 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -777,7 +777,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::RepeatSymbol); Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, - bool are_sync_levels_unwinded = false, JumpMode jump_mode = JumpMode::RepeatSymbol); + bool are_sync_levels_unwinded = false, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::RepeatSymbol); /** * @brief Concatenate two NFTs. diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 82e654803..35e0b73b2 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -12,14 +12,14 @@ using namespace mata::utils; namespace mata::nft { -Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { +Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const bool project_out_sync_levels, const JumpMode jump_mode) { assert(lhs_sync_levels.size() == rhs_sync_levels.size()); // Number of Levels const size_t num_of_sync_levels = lhs_sync_levels.size(); const size_t lhs_num_of_levels = lhs.num_of_levels; const size_t rhs_num_of_levels = rhs.num_of_levels; - const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (2 * num_of_sync_levels); + const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (project_out_sync_levels ? (2 * num_of_sync_levels) : num_of_sync_levels); // Calculate number of lhs/rhs transitions before/between/after sync levels auto lhs_sync_levels_it = lhs_sync_levels.cbegin(); @@ -118,9 +118,11 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } const State new_state = result.add_state_with_level(level); main_state_map[key] = new_state; - worklist.push(key); - if (lhs.final.contains(lhs_state) && rhs.final.contains(rhs_state)) { - result.final.insert(new_state); + if (level == 0) { + worklist.push(key); + if (lhs.final.contains(lhs_state) && rhs.final.contains(rhs_state)) { + result.final.insert(new_state); + } } return new_state; }; @@ -192,7 +194,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const std::vector& between = is_orig_lhs ? rhs_between : lhs_between; // Use rhs_between if we are in lhs NFT, and vice versa. const BoolVector& is_sync_level = is_orig_lhs ? lhs_is_sync_level : rhs_is_sync_level; const bool add_before = !is_orig_lhs; // On each section within sync levels, lhs transitions go begore rhs transitions. - static std::stack>& main_worklist = worklist; + std::stack>& main_worklist = worklist; // Worklist contains pairs of (state in the result NFT, and state in the original lhs/rhs NFT). std::stack> worklist; @@ -219,11 +221,11 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& worklist.pop(); // Add EPSILON transition for those in lhs BEFORE the first transition (we are in rhs). - if (result.levels[res_src] == 0 && add_before) { + if (res_src == res_root && add_before) { res_src = repeat_transition(res_src, between[0], EPSILON); } - if (is_sync_level[orig_src]) { + if (is_sync_level[orig_nft.levels[orig_src]]) { // We are at a sync level. We need to match only the epsilon transitions. const auto epsilon_post_it = orig_nft.delta[orig_src].find(EPSILON); if (epsilon_post_it == orig_nft.delta[orig_src].end()) { @@ -239,6 +241,10 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& res_src = repeat_transition(res_src, between[sync_level_idx + 1], EPSILON); } + if (!project_out_sync_levels) { + res_src = repeat_transition(res_src, 1, EPSILON); + } + // Process targets of the epsilon transitions (this transition will be projected out). for (const State orig_tgt: epsilon_post_it->targets) { if (orig_nft.levels[orig_tgt] == 0) { @@ -322,7 +328,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& }; auto map_combination_path = [&](const State res_root, const State lhs_root, const State rhs_root) { - static std::stack>& main_worklist = worklist; + std::stack>& main_worklist = worklist; pred_map.clear(); std::stack> worklist; worklist.push({res_root, lhs_root, rhs_root}); @@ -343,7 +349,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const bool lhs_in_target_and_rhs_remains = lhs_src_level == 0 && rhs_src_level != 0; // Go in lhs all the way down to the sync level. - if (!lhs_is_sync_level[lhs_src_level] && !lhs_in_target_and_rhs_remains) { + if (!lhs_is_sync_level[lhs_src_level] && !lhs_in_target_and_rhs_remains && !lhs.delta[lhs_src].empty()) { for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { for (const State lhs_tgt: symbol_post.targets) { const Level lhs_tgt_level = lhs.levels[lhs_tgt]; @@ -414,10 +420,10 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Everythink is on sync level. const bool epsilon_in_lhs = lhs.delta[lhs_src].find(EPSILON) != lhs.delta[lhs_src].end(); const bool epsilon_in_rhs = rhs.delta[rhs_src].find(EPSILON) != rhs.delta[rhs_src].end(); - perform_lhs_wait ||= (epsilon_in_rhs && !epsilon_in_lhs); - perform_rhs_wait ||= (epsilon_in_lhs && !epsilon_in_rhs); + perform_lhs_wait = perform_lhs_wait || (epsilon_in_rhs && !epsilon_in_lhs); + perform_rhs_wait = perform_rhs_wait || (epsilon_in_lhs && !epsilon_in_rhs); - auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets) { + auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets, const Symbol symbol) { for (const State lhs_tgt: lhs_targets) { const Level lhs_tgt_level = lhs.levels[lhs_tgt]; for (const State rhs_tgt: rhs_targets) { @@ -425,20 +431,44 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& if (lhs_tgt_level == 0 && rhs_tgt_level == 0) { const auto key = std::make_pair(lhs_tgt, rhs_tgt); auto it = main_state_map.find(key); - if (it != main_state_map.end()) { - // The mapping exists, redirect transition goig to orig_src to the existing state. - redirect(res_src, it->second); + if (!project_out_sync_levels) { + if (it != main_state_map.end()) { + const State res_tgt = it->second; + // The mapping exists, redirect transition going to orig_src to the existing state. + result.delta.add(res_src, symbol, res_tgt); + } else { + // The mapping does not exist. Update it. + const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_tgt, true, main_worklist); + result.delta.add(res_src, symbol, res_tgt); + commited_states.insert(res_tgt); + } } else { - // The mapping does not exist. Update it. - try_to_map_state_add_to_wordlist(res_src, lhs_tgt, rhs_tgt, true); - commited_states.insert(res_src); + if (it != main_state_map.end()) { + // The mapping exists, redirect transition goig to orig_src to the existing state. + redirect(res_src, it->second); + } else { + // The mapping does not exist. Update it. + try_to_map_state_add_to_wordlist(res_src, lhs_tgt, rhs_tgt, true); + commited_states.insert(res_src); + } } } else { - if (!visited.contains({lhs_tgt, rhs_tgt})) { - // We have not visited this pair yet. - visited.insert({lhs_tgt, rhs_tgt}); - main_state_map[std::make_pair(lhs_tgt, rhs_tgt)] = res_src; - worklist.push({res_src, lhs_tgt, rhs_tgt}); + if (!project_out_sync_levels) { + const State res_tgt = get_state(lhs_tgt, rhs_tgt, (res_src_level + 1) % result_num_of_levels); + result.delta.add(res_src, symbol, res_tgt); + pred_map[res_tgt].insert(res_src); + if (!visited.contains({lhs_tgt, rhs_tgt})) { + // We have not visited this pair yet. + visited.insert({lhs_tgt, rhs_tgt}); + worklist.push({res_tgt, lhs_tgt, rhs_tgt}); + } + } else { + if (!visited.contains({lhs_tgt, rhs_tgt})) { + // We have not visited this pair yet. + visited.insert({lhs_tgt, rhs_tgt}); + main_state_map[std::make_pair(lhs_tgt, rhs_tgt)] = res_src; + worklist.push({res_src, lhs_tgt, rhs_tgt}); + } } } } @@ -455,7 +485,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& process_intersection( same_symbol_posts[0]->targets, - same_symbol_posts[1]->targets + same_symbol_posts[1]->targets, + same_symbol_posts[0]->symbol ); } @@ -463,7 +494,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { process_intersection( lhs.delta[lhs_src].find(DONT_CARE)->targets, - symbol_post.targets + symbol_post.targets, + symbol_post.symbol ); } } @@ -472,7 +504,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { process_intersection( symbol_post.targets, - rhs.delta[rhs_src].find(DONT_CARE)->targets + rhs.delta[rhs_src].find(DONT_CARE)->targets, + symbol_post.symbol ); } } @@ -492,6 +525,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Get the root state in the result NFT const State res_root = get_state_or_add_and_put_to_worklist(lhs_root, rhs_root, true, worklist); commited_states.insert(res_root); + result.initial.insert(res_root); } } @@ -503,15 +537,17 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Get the state in the result NFT const State res_root = main_state_map.at({orig_a, orig_b}); if (!commited_states.contains(res_root)) { - final.erase(res_root); + result.final.erase(res_root); continue; } // Map epsilon transitions on the sync path map_combination_path(res_root, orig_a, orig_b); + // map_epsilon_on_sync_path(res_root, orig_b, orig_a, false); + // break; } - return result; + return result.trim(); } @@ -520,6 +556,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_lev assert(!lhs_sync_levels.empty()); assert(lhs_sync_levels.size() == rhs_sync_levels.size()); + + return compose_fast(lhs.unwind_jumps({ DONT_CARE }, jump_mode), rhs.unwind_jumps({ DONT_CARE }, jump_mode), lhs_sync_levels, rhs_sync_levels, true, project_out_sync_levels, JumpMode::NoJump); + // Inserts loop into the given Nft for each state with level 0. // The loop word is constructed using the EPSILON symbol for all levels, except for the levels // where is_dcare_on_transition is true, in which case the DONT_CARE symbol is used. diff --git a/src/strings/nfa-noodlification.cc b/src/strings/nfa-noodlification.cc index 0ac6e22c6..2e4ab1b2f 100644 --- a/src/strings/nfa-noodlification.cc +++ b/src/strings/nfa-noodlification.cc @@ -461,7 +461,6 @@ std::vector seg_nfa::noodlify_for_transducer( add_self_loop_for_every_default_state(concatenated_output_nft, INPUT_DELIMITER); add_self_loop_for_every_default_state(intersection, OUTPUT_DELIMITER); intersection = mata::nft::compose(concatenated_output_nft, intersection, 0, 1, false); - intersection.trim(); if(intersection.final.empty()) { return {}; diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 71366bb79..51e3ab115 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -73,14 +73,7 @@ TEST_CASE("Mata::nft::compose()") { expected.delta.add(3, 'h', 4); expected.delta.add(4, EPSILON, 5); expected.delta.add(5, 'j', 6); - expected.delta.add(2, EPSILON, 7); - expected.delta.add(7, 'h', 8); - expected.delta.add(8, 'g', 9); - expected.delta.add(9, EPSILON, 4); - expected.delta.add(2, 'g', 10); - expected.delta.add(10, EPSILON, 11); - expected.delta.add(11, EPSILON, 12); - expected.delta.add(12, 'h', 4); + result = compose(lhs, rhs, 1, 0); @@ -236,21 +229,14 @@ TEST_CASE("Mata::nft::compose()") { rhs.delta.add(2, EPSILON, 3); rhs.delta.add(3, 'd', 4); - expected = Nft(13, { 0 }, { 6 }, { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1 }, 2); + expected = Nft(7, { 0 }, { 6 }, { 0, 1, 0, 1, 0, 1, 0 }, 2); expected.delta.add(0, 'x', 1); expected.delta.add(1, EPSILON, 2); expected.delta.add(2, EPSILON, 3); expected.delta.add(3, 'b', 4); expected.delta.add(4, 'x', 5); - expected.delta.add(4, EPSILON, 10); - expected.delta.add(10, 'd', 11); - expected.delta.add(11, 'x', 12); - expected.delta.add(12, EPSILON, 6); - expected.delta.add(4, 'x', 7); - expected.delta.add(7, EPSILON, 8); - expected.delta.add(8, EPSILON, 9); - expected.delta.add(9, 'd', 6); expected.delta.add(5, 'd', 6); + expected.print_to_dot(std::string("expected.dot"), true); result = compose(lhs, rhs); @@ -564,4 +550,4 @@ TEST_CASE("Mata::nft::compose()") { CHECK(are_equivalent(result, expected, JumpMode::AppendDontCares)); } } -} +} \ No newline at end of file From 973c6a4a9adb6341b01626069f1c46053a1c9768 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 26 Jun 2025 12:04:24 +0200 Subject: [PATCH 07/65] before Noodler test --- src/nft/composition.cc | 62 ++--- tests/nft/nft-composition.cc | 435 +++++++++++++++++++++++++++++++++++ 2 files changed, 468 insertions(+), 29 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 35e0b73b2..e6629094b 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -424,6 +424,10 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& perform_rhs_wait = perform_rhs_wait || (epsilon_in_lhs && !epsilon_in_rhs); auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets, const Symbol symbol) { + State local_res_src = res_src; + if (!project_out_sync_levels && !lhs_targets.empty() && !rhs_targets.empty()) { + local_res_src = repeat_transition(local_res_src, 1, symbol); + } for (const State lhs_tgt: lhs_targets) { const Level lhs_tgt_level = lhs.levels[lhs_tgt]; for (const State rhs_tgt: rhs_targets) { @@ -431,45 +435,45 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& if (lhs_tgt_level == 0 && rhs_tgt_level == 0) { const auto key = std::make_pair(lhs_tgt, rhs_tgt); auto it = main_state_map.find(key); - if (!project_out_sync_levels) { - if (it != main_state_map.end()) { - const State res_tgt = it->second; - // The mapping exists, redirect transition going to orig_src to the existing state. - result.delta.add(res_src, symbol, res_tgt); - } else { - // The mapping does not exist. Update it. - const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_tgt, true, main_worklist); - result.delta.add(res_src, symbol, res_tgt); - commited_states.insert(res_tgt); - } - } else { + // if (!project_out_sync_levels) { + // if (it != main_state_map.end()) { + // const State res_tgt = it->second; + // // The mapping exists, redirect transition going to orig_src to the existing state. + // result.delta.add(res_src, symbol, res_tgt); + // } else { + // // The mapping does not exist. Update it. + // const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_tgt, true, main_worklist); + // result.delta.add(res_src, symbol, res_tgt); + // commited_states.insert(res_tgt); + // } + // } else { if (it != main_state_map.end()) { // The mapping exists, redirect transition goig to orig_src to the existing state. - redirect(res_src, it->second); + redirect(local_res_src, it->second); } else { // The mapping does not exist. Update it. - try_to_map_state_add_to_wordlist(res_src, lhs_tgt, rhs_tgt, true); - commited_states.insert(res_src); + try_to_map_state_add_to_wordlist(local_res_src, lhs_tgt, rhs_tgt, true); + commited_states.insert(local_res_src); } - } + // } } else { - if (!project_out_sync_levels) { - const State res_tgt = get_state(lhs_tgt, rhs_tgt, (res_src_level + 1) % result_num_of_levels); - result.delta.add(res_src, symbol, res_tgt); - pred_map[res_tgt].insert(res_src); + // if (!project_out_sync_levels) { + // const State res_tgt = get_state(lhs_tgt, rhs_tgt, (res_src_level + 1) % result_num_of_levels); + // result.delta.add(res_src, symbol, res_tgt); + // pred_map[res_tgt].insert(res_src); + // if (!visited.contains({lhs_tgt, rhs_tgt})) { + // // We have not visited this pair yet. + // visited.insert({lhs_tgt, rhs_tgt}); + // worklist.push({res_tgt, lhs_tgt, rhs_tgt}); + // } + // } else { if (!visited.contains({lhs_tgt, rhs_tgt})) { // We have not visited this pair yet. visited.insert({lhs_tgt, rhs_tgt}); - worklist.push({res_tgt, lhs_tgt, rhs_tgt}); + main_state_map[std::make_pair(lhs_tgt, rhs_tgt)] = local_res_src; + worklist.push({local_res_src, lhs_tgt, rhs_tgt}); } - } else { - if (!visited.contains({lhs_tgt, rhs_tgt})) { - // We have not visited this pair yet. - visited.insert({lhs_tgt, rhs_tgt}); - main_state_map[std::make_pair(lhs_tgt, rhs_tgt)] = res_src; - worklist.push({res_src, lhs_tgt, rhs_tgt}); - } - } + // } } } } diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 51e3ab115..66870c9c8 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -550,4 +550,439 @@ TEST_CASE("Mata::nft::compose()") { CHECK(are_equivalent(result, expected, JumpMode::AppendDontCares)); } } +} + +TEST_CASE("nft::compose_fast()") { + SECTION("project_out == true") { + SECTION("two levels easy match") { + Nft lhs; + lhs.num_of_levels = 2; + lhs.levels = { 0, 1, 0, 1, 0 }; + lhs.initial.insert(0); + lhs.final.insert(4); + lhs.delta.add(0, 'x', 1); + lhs.delta.add(1, 'a', 2); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'y', 3); + lhs.delta.add(3, 'c', 4); + lhs.delta.add(3, 'd', 4); + + Nft rhs; + rhs.num_of_levels = 2; + rhs.levels = { 0, 1, 0, 1, 0 }; + rhs.initial.insert(0); + rhs.final.insert(4); + rhs.delta.add(0, 'x', 1); + rhs.delta.add(1, 'e', 2); + rhs.delta.add(1, 'f', 2); + rhs.delta.add(2, 'y', 3); + rhs.delta.add(3, 'g', 4); + rhs.delta.add(3, 'h', 4); + + Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, true, true, JumpMode::NoJump); + + Nft expected; + expected.num_of_levels = 2; + expected.levels = { 0, 1, 0, 1, 0 }; + expected.initial.insert(0); + expected.final.insert(4); + expected.delta.add(0, 'a', 1); + expected.delta.add(0, 'b', 1); + expected.delta.add(1, 'e', 2); + expected.delta.add(1, 'f', 2); + expected.delta.add(2, 'c', 3); + expected.delta.add(2, 'd', 3); + expected.delta.add(3, 'g', 4); + expected.delta.add(3, 'h', 4); + + CHECK(result.num_of_levels == 2); + CHECK(are_equivalent(result, expected)); + } + + SECTION("four levels with loop") { + Nft lhs; + lhs.num_of_levels = 4; + lhs.levels = { 0, 1, 2, 3, 0 }; + lhs.initial.insert(0); + lhs.final.insert(4); + lhs.delta.add(0, 'x', 1); + lhs.delta.add(1, 'a', 2); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'y', 3); + lhs.delta.add(3, 'c', 4); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(3, 'e', 0); + lhs.delta.add(3, 'f', 0); + + Nft rhs; + rhs.num_of_levels = 4; + rhs.levels = { 0, 1, 2, 3, 0 }; + rhs.initial.insert(0); + rhs.final.insert(4); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(0, 'h', 1); + rhs.delta.add(1, 'x', 2); + rhs.delta.add(2, 'i', 3); + rhs.delta.add(2, 'j', 3); + rhs.delta.add(3, 'y', 4); + rhs.delta.add(3, 'y', 0); + + Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, true, true, JumpMode::NoJump); + + Nft expected; + expected.num_of_levels = 4; + expected.levels = { 0, 1, 2, 3, 0 }; + expected.initial.insert(0); + expected.final.insert(4); + expected.delta.add(0, 'g', 1); + expected.delta.add(0, 'h', 1); + expected.delta.add(1, 'a', 2); + expected.delta.add(1, 'b', 2); + expected.delta.add(2, 'i', 3); + expected.delta.add(2, 'j', 3); + expected.delta.add(3, 'c', 4); + expected.delta.add(3, 'd', 4); + expected.delta.add(3, 'e', 0); + expected.delta.add(3, 'f', 0); + + CHECK(result.num_of_levels == 4); + CHECK(are_equivalent(result, expected)); + } + + SECTION("synchronization of lst two levels with one mismatch") { + Nft lhs; + lhs.num_of_levels = 3; + lhs.levels = { 0, 1, 2 }; + lhs.initial.insert(0); + lhs.final.insert(0); + lhs.delta.add(0, 'u', 1); + lhs.delta.add(0, 'v', 1); + lhs.delta.add(1, 'x', 2); + lhs.delta.add(2, 'y', 0); + + Nft rhs; + rhs.num_of_levels = 4; + rhs.levels = { 0, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0}; + rhs.initial.insert(0); + rhs.final.insert(7); + rhs.final.insert(14); + rhs.final.insert(15); + rhs.delta.add(0, 'a', 1); + rhs.delta.add(0, 'b', 1); + rhs.delta.add(1, 'c', 2); + rhs.delta.add(1, 'd', 2); + rhs.delta.add(1, 'c', 3); + rhs.delta.add(1, 'd', 3); + rhs.delta.add(2, 'x', 4); + rhs.delta.add(4, 'y', 6); + rhs.delta.add(6, 'e', 8); + rhs.delta.add(8, 'f', 10); + rhs.delta.add(10, 'x', 12); + rhs.delta.add(12, 'y', 14); + rhs.delta.add(3, 'x', 5); + rhs.delta.add(5, 'z', 7); + rhs.delta.add(7, 'g', 9); + rhs.delta.add(9, 'h', 11); + rhs.delta.add(11, 'x', 13); + rhs.delta.add(13, 'y', 15); + + Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, true, true, JumpMode::NoJump); + + Nft expected; + expected.num_of_levels = 3; + expected.levels = { 0, 1, 2, 0, 1, 2, 0 }; + expected.initial.insert(0); + expected.final.insert(6); + expected.delta.add(0, 'u', 1); + expected.delta.add(0, 'v', 1); + expected.delta.add(1, 'a', 2); + expected.delta.add(1, 'b', 2); + expected.delta.add(2, 'c', 3); + expected.delta.add(2, 'd', 3); + expected.delta.add(3, 'u', 4); + expected.delta.add(3, 'v', 4); + expected.delta.add(4, 'e', 5); + expected.delta.add(5, 'f', 6); + + CHECK(result.num_of_levels == 3); + CHECK(are_equivalent(result, expected)); + } + + SECTION("epsilon on synchronization") { + Nft lhs; + lhs.num_of_levels = 3; + lhs.levels = { 0, 1, 2, 0 }; + lhs.initial.insert(0); + lhs.final.insert(3); + lhs.delta.add(0, 'u', 1); + lhs.delta.add(0, 'v', 1); + lhs.delta.add(1, 'x', 2); + lhs.delta.add(2, 'y', 3); + + Nft rhs; + rhs.num_of_levels = 3; + rhs.levels = { 0, 1, 2, 2, 0, 2, 0, 1, 1, 2, 2, 0, 0 }; + rhs.initial.insert(0); + rhs.final.insert(11); + rhs.final.insert(12); + rhs.delta.add(0, 'a', 1); + rhs.delta.add(0, 'b', 1); + rhs.delta.add(1, 'c', 2); + rhs.delta.add(1, EPSILON, 3); + rhs.delta.add(1, EPSILON, 5); + rhs.delta.add(2, EPSILON, 4); + rhs.delta.add(3, EPSILON, 4); + rhs.delta.add(4, 'e', 7); + rhs.delta.add(4, 'f', 7); + rhs.delta.add(7, 'x', 9); + rhs.delta.add(9, 'y', 11); + rhs.delta.add(5, 'd', 6); + rhs.delta.add(6, 'g', 8); + rhs.delta.add(8, 'x', 10); + rhs.delta.add(10, 'y', 12); + + Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, true, true, JumpMode::NoJump); + + Nft expected; + expected.num_of_levels = 2; + expected.levels = { 0, 1, 0, 1, 0 }; + expected.initial.insert(0); + expected.final.insert(4); + expected.delta.add(0, EPSILON, 1); + expected.delta.add(1, 'a', 2); + expected.delta.add(1, 'b', 2); + expected.delta.add(2, 'u', 3); + expected.delta.add(2, 'v', 3); + expected.delta.add(3, 'e', 4); + expected.delta.add(3, 'f', 4); + + CHECK(result.num_of_levels == 2); + CHECK(are_equivalent(result, expected)); + } + } + + SECTION("project_out == false") { + SECTION("two levels easy match") { + Nft lhs; + lhs.num_of_levels = 2; + lhs.levels = { 0, 1, 0, 1, 0 }; + lhs.initial.insert(0); + lhs.final.insert(4); + lhs.delta.add(0, 'x', 1); + lhs.delta.add(1, 'a', 2); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'y', 3); + lhs.delta.add(3, 'c', 4); + lhs.delta.add(3, 'd', 4); + + Nft rhs; + rhs.num_of_levels = 2; + rhs.levels = { 0, 1, 0, 1, 0 }; + rhs.initial.insert(0); + rhs.final.insert(4); + rhs.delta.add(0, 'x', 1); + rhs.delta.add(1, 'e', 2); + rhs.delta.add(1, 'f', 2); + rhs.delta.add(2, 'y', 3); + rhs.delta.add(3, 'g', 4); + rhs.delta.add(3, 'h', 4); + + Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, true, false, JumpMode::NoJump); + + Nft expected; + expected.num_of_levels = 3; + expected.levels = { 0, 2, 0, 2, 0, 1, 1 }; + expected.initial.insert(0); + expected.final.insert(4); + expected.delta.add(0, 'x', 5); + expected.delta.add(5, 'a', 1); + expected.delta.add(5, 'b', 1); + expected.delta.add(1, 'e', 2); + expected.delta.add(1, 'f', 2); + expected.delta.add(2, 'y', 6); + expected.delta.add(6, 'c', 3); + expected.delta.add(6, 'd', 3); + expected.delta.add(3, 'g', 4); + expected.delta.add(3, 'h', 4); + + CHECK(result.num_of_levels == 3); + CHECK(are_equivalent(result, expected)); + } + + + SECTION("four levels with loop") { + Nft lhs; + lhs.num_of_levels = 4; + lhs.levels = { 0, 1, 2, 3, 0 }; + lhs.initial.insert(0); + lhs.final.insert(4); + lhs.delta.add(0, 'x', 1); + lhs.delta.add(1, 'a', 2); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'y', 3); + lhs.delta.add(3, 'c', 4); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(3, 'e', 0); + lhs.delta.add(3, 'f', 0); + lhs.print_to_dot(std::string("lhs.dot"), true, true); + + Nft rhs; + rhs.num_of_levels = 4; + rhs.levels = { 0, 1, 2, 3, 0 }; + rhs.initial.insert(0); + rhs.final.insert(4); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(0, 'h', 1); + rhs.delta.add(1, 'x', 2); + rhs.delta.add(2, 'i', 3); + rhs.delta.add(2, 'j', 3); + rhs.delta.add(3, 'y', 4); + rhs.delta.add(3, 'y', 0); + rhs.print_to_dot(std::string("rhs.dot"), true, true); + + Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, true, false, JumpMode::NoJump); + result.print_to_dot(std::string("result.dot"), true, true); + + Nft expected; + expected.num_of_levels = 6; + expected.levels = { 0, 2, 3, 4, 0, 1, 5 }; + expected.initial.insert(0); + expected.final.insert(4); + expected.delta.add(0, 'g', 5); + expected.delta.add(0, 'h', 5); + expected.delta.add(5, 'x', 1); + expected.delta.add(1, 'a', 2); + expected.delta.add(1, 'b', 2); + expected.delta.add(2, 'i', 3); + expected.delta.add(2, 'j', 3); + expected.delta.add(3, 'y', 6); + expected.delta.add(6, 'c', 4); + expected.delta.add(6, 'd', 4); + expected.delta.add(6, 'e', 0); + expected.delta.add(6, 'f', 0); + expected.print_to_dot(std::string("expected.dot"), true, true); + + CHECK(result.num_of_levels == 6); + CHECK(are_equivalent(result, expected)); + } + + SECTION("synchronization of lst two levels with one mismatch") { + Nft lhs; + lhs.num_of_levels = 3; + lhs.levels = { 0, 1, 2 }; + lhs.initial.insert(0); + lhs.final.insert(0); + lhs.delta.add(0, 'u', 1); + lhs.delta.add(0, 'v', 1); + lhs.delta.add(1, 'x', 2); + lhs.delta.add(2, 'y', 0); + + Nft rhs; + rhs.num_of_levels = 4; + rhs.levels = { 0, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0}; + rhs.initial.insert(0); + rhs.final.insert(7); + rhs.final.insert(14); + rhs.final.insert(15); + rhs.delta.add(0, 'a', 1); + rhs.delta.add(0, 'b', 1); + rhs.delta.add(1, 'c', 2); + rhs.delta.add(1, 'd', 2); + rhs.delta.add(1, 'c', 3); + rhs.delta.add(1, 'd', 3); + rhs.delta.add(2, 'x', 4); + rhs.delta.add(4, 'y', 6); + rhs.delta.add(6, 'e', 8); + rhs.delta.add(8, 'f', 10); + rhs.delta.add(10, 'x', 12); + rhs.delta.add(12, 'y', 14); + rhs.delta.add(3, 'x', 5); + rhs.delta.add(5, 'z', 7); + rhs.delta.add(7, 'g', 9); + rhs.delta.add(9, 'h', 11); + rhs.delta.add(11, 'x', 13); + rhs.delta.add(13, 'y', 15); + + Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, true, false, JumpMode::NoJump); + + Nft expected; + expected.num_of_levels = 5; + expected.levels = { 0, 1, 2, 3, 1, 2, 3, 4, 0, 4, 0 }; + expected.initial.insert(0); + expected.final.insert(10); + expected.delta.add(0, 'u', 1); + expected.delta.add(0, 'v', 1); + expected.delta.add(1, 'a', 2); + expected.delta.add(1, 'b', 2); + expected.delta.add(2, 'c', 3); + expected.delta.add(2, 'd', 3); + expected.delta.add(3, 'x', 7); + expected.delta.add(7, 'y', 8); + expected.delta.add(8, 'u', 4); + expected.delta.add(8, 'v', 4); + expected.delta.add(4, 'e', 5); + expected.delta.add(5, 'f', 6); + expected.delta.add(6, 'x', 9); + expected.delta.add(9, 'y', 10); + + CHECK(result.num_of_levels == 5); + CHECK(are_equivalent(result, expected)); + } + + SECTION("epsilon on synchronization") { + Nft lhs; + lhs.num_of_levels = 3; + lhs.levels = { 0, 1, 2, 0 }; + lhs.initial.insert(0); + lhs.final.insert(3); + lhs.delta.add(0, 'u', 1); + lhs.delta.add(0, 'v', 1); + lhs.delta.add(1, 'x', 2); + lhs.delta.add(2, 'y', 3); + + Nft rhs; + rhs.num_of_levels = 3; + rhs.levels = { 0, 1, 2, 2, 0, 2, 0, 1, 1, 2, 2, 0, 0 }; + rhs.initial.insert(0); + rhs.final.insert(11); + rhs.final.insert(12); + rhs.delta.add(0, 'a', 1); + rhs.delta.add(0, 'b', 1); + rhs.delta.add(1, 'c', 2); + rhs.delta.add(1, EPSILON, 3); + rhs.delta.add(1, EPSILON, 5); + rhs.delta.add(2, EPSILON, 4); + rhs.delta.add(3, EPSILON, 4); + rhs.delta.add(4, 'e', 7); + rhs.delta.add(4, 'f', 7); + rhs.delta.add(7, 'x', 9); + rhs.delta.add(9, 'y', 11); + rhs.delta.add(5, 'd', 6); + rhs.delta.add(6, 'g', 8); + rhs.delta.add(8, 'x', 10); + rhs.delta.add(10, 'y', 12); + + Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, true, false, JumpMode::NoJump); + + Nft expected; + expected.num_of_levels = 4; + expected.levels = { 0, 1, 2, 1, 2, 3, 0, 3, 0 }; + expected.initial.insert(0); + expected.final.insert(8); + expected.delta.add(0, EPSILON, 1); + expected.delta.add(1, 'a', 2); + expected.delta.add(1, 'b', 2); + expected.delta.add(2, EPSILON, 5); + expected.delta.add(5, EPSILON, 6); + expected.delta.add(6, 'u', 3); + expected.delta.add(6, 'v', 3); + expected.delta.add(3, 'e', 4); + expected.delta.add(3, 'f', 4); + expected.delta.add(4, 'x', 7); + expected.delta.add(7, 'y', 8); + + CHECK(result.num_of_levels == 4); + CHECK(are_equivalent(result, expected)); + } + } } \ No newline at end of file From c09a22539545d6ed9127675746750396d62790f9 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 26 Jun 2025 14:59:25 +0200 Subject: [PATCH 08/65] helper functions --- src/nft/composition.cc | 136 +++++++++++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 46 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index e6629094b..ee52e1d7e 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -9,6 +9,86 @@ using namespace mata::utils; +namespace { + using mata::Symbol; + using namespace mata::nft; + + template + mata::BoolVector create_mask(const OrdVector& entries, const size_t universum_size) { + mata::BoolVector mask(universum_size, false); + for (const auto& entry : entries) { + mask[entry] = true; + } + return mask; + } + + template + std::vector invert(const OrdVector& entries, const size_t universum_size) { + std::vector inverted(universum_size, std::numeric_limits::max()); + size_t idx = 0; + for (const auto& entry : entries) { + inverted[entry] = idx++; + } + return inverted; + } + + template + std::vector get_partition_sizes(const OrdVector& border_indices, const size_t universum_size) { + assert(!border_indices.empty()); + const size_t num_partitions = border_indices.size(); + std::vector sizes(num_partitions + 1, 0); + auto partition_borders_it = border_indices.cbegin(); + sizes[0] = *partition_borders_it; + for (size_t i = 1; i < num_partitions; ++i) { + const size_t prev_border = *partition_borders_it; + ++partition_borders_it; + sizes[i] = *partition_borders_it - prev_border - 1; + } + sizes[num_partitions] = universum_size - *partition_borders_it - 1; + return sizes; + } + + State add_transition(Nft& nft, const State source, const Symbol symbol, const size_t len, const JumpMode jump_mode) { + assert(nft.levels[source] + len <= nft.num_of_levels); + if (len == 0) { return source; } + + const State target = nft.add_state_with_level((nft.levels[source] + len) % static_cast(nft.num_of_levels)); + if (len == 1 || jump_mode == JumpMode::RepeatSymbol) { + nft.delta.add(source, symbol, target); + return target; + } + State inner_src = source; + for (size_t i = 0; i < len - 1; ++i) { + const State inner_target = nft.add_state_with_level(nft.levels[inner_src] + 1); + nft.delta.add(inner_src, symbol, inner_target); + inner_src = inner_target; + } + nft.delta.add(inner_src, symbol, target); + return target; + } + + State add_transition(Nft& nft, const State source, const Symbol symbol, const State target, const JumpMode jump_mode) { + if (source == target) { return source;} + assert(nft.levels[source] < nft.levels[target] || nft.levels[target] == 0); + + const size_t trans_len = nft.levels[target] == 0 ? nft.num_of_levels - nft.levels[source] : nft.levels[target] - nft.levels[source]; + assert(trans_len > 0); + if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { + nft.delta.add(source, symbol, target); + return target; + } + State inner_src = source; + for (size_t i = 0; i < trans_len - 1; ++i) { + const State inner_target = nft.add_state_with_level(nft.levels[inner_src] + 1); + nft.delta.add(inner_src, symbol, inner_target); + inner_src = inner_target; + } + nft.delta.add(inner_src, symbol, target); + return target; + } +} + + namespace mata::nft { @@ -21,52 +101,6 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const size_t rhs_num_of_levels = rhs.num_of_levels; const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (project_out_sync_levels ? (2 * num_of_sync_levels) : num_of_sync_levels); - // Calculate number of lhs/rhs transitions before/between/after sync levels - auto lhs_sync_levels_it = lhs_sync_levels.cbegin(); - auto rhs_sync_levels_it = rhs_sync_levels.cbegin(); - // Sync levels are on on borders between vector elements. - std::vector lhs_between(num_of_sync_levels + 1, 0); - std::vector rhs_between(num_of_sync_levels + 1, 0); - // Before the first sync level - lhs_between[0] = *lhs_sync_levels_it; - rhs_between[0] = *rhs_sync_levels_it; - // Between sync levels - for (size_t i = 1; i < num_of_sync_levels; ++i) { - const size_t lhs_prev = *lhs_sync_levels_it; - const size_t rhs_prev = *rhs_sync_levels_it; - ++lhs_sync_levels_it; - ++rhs_sync_levels_it; - lhs_between[i] = *lhs_sync_levels_it - lhs_prev - 1; - rhs_between[i] = *rhs_sync_levels_it - rhs_prev - 1; - } - // After the last sync level - lhs_between[num_of_sync_levels] = lhs_num_of_levels - *lhs_sync_levels_it - 1; - rhs_between[num_of_sync_levels] = rhs_num_of_levels - *rhs_sync_levels_it - 1; - - // Is level a sync level? - BoolVector lhs_is_sync_level(lhs_num_of_levels, false); - BoolVector rhs_is_sync_level(rhs_num_of_levels, false); - for (const Level level : lhs_sync_levels) { - lhs_is_sync_level[level] = true; - } - for (const Level level : rhs_sync_levels) { - rhs_is_sync_level[level] = true; - } - - // Sync levels inverted - // Element on index i (level i) tells the index of the level in sync_levels vector. - std::vector lhs_sync_levels_inv(lhs_num_of_levels, std::numeric_limits::max()); - std::vector rhs_sync_levels_inv(rhs_num_of_levels, std::numeric_limits::max()); - size_t idx = 0; - const auto lhs_end_it = lhs_sync_levels.cend(); - for (auto lhs_it = lhs_sync_levels.cbegin(), rhs_it = rhs_sync_levels.cbegin(); - lhs_it != lhs_end_it; - ++lhs_it, ++rhs_it, ++idx) - { - lhs_sync_levels_inv[*lhs_it] = idx; - rhs_sync_levels_inv[*rhs_it] = idx; - } - // Initialize the result NFT, state map, and worklist Nft result; result.num_of_levels = result_num_of_levels; @@ -75,6 +109,16 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& std::stack> worklist; std::unordered_set commited_states; + // Calculate number of lhs/rhs transitions before/between/after sync levels + const std::vector lhs_between = get_partition_sizes(lhs_sync_levels, lhs_num_of_levels); + const std::vector rhs_between = get_partition_sizes(rhs_sync_levels, rhs_num_of_levels); + + BoolVector lhs_is_sync_level = create_mask(lhs_sync_levels, lhs_num_of_levels); + BoolVector rhs_is_sync_level = create_mask(rhs_sync_levels, rhs_num_of_levels); + + const std::vector lhs_sync_levels_inv = invert(lhs_sync_levels, lhs_num_of_levels); + const std::vector rhs_sync_levels_inv = invert(rhs_sync_levels, rhs_num_of_levels); + // Function: adds a new state to the result and puts it to the worklist auto try_to_map_state_add_to_wordlist = [&](const State state, const State orig_a, const State orig_b, const bool is_a_lhs) { const auto key = is_a_lhs ? std::make_pair(orig_a, orig_b) : std::make_pair(orig_b, orig_a); From 4da5c9d9f165367bfb79e6bc7237bd1a29fb19c2 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 27 Jun 2025 11:57:08 +0200 Subject: [PATCH 09/65] add storage from intersection to composition --- src/nft/composition.cc | 303 +++++++++++++++++++---------------------- 1 file changed, 139 insertions(+), 164 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index ee52e1d7e..2bf9b3855 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -12,6 +12,12 @@ using namespace mata::utils; namespace { using mata::Symbol; using namespace mata::nft; + using PredMap = std::unordered_map; + + using ProductMap = std::unordered_map,State>; + using MatrixProductStorage = std::vector>; + using VecMapProductStorage = std::vector>; + using InvertedProductStorage = std::vector; template mata::BoolVector create_mask(const OrdVector& entries, const size_t universum_size) { @@ -48,43 +54,62 @@ namespace { return sizes; } - State add_transition(Nft& nft, const State source, const Symbol symbol, const size_t len, const JumpMode jump_mode) { + State add_transition(Nft& nft, const State source, const Symbol symbol, const size_t len, const JumpMode jump_mode, PredMap& pred_map) { assert(nft.levels[source] + len <= nft.num_of_levels); if (len == 0) { return source; } const State target = nft.add_state_with_level((nft.levels[source] + len) % static_cast(nft.num_of_levels)); if (len == 1 || jump_mode == JumpMode::RepeatSymbol) { nft.delta.add(source, symbol, target); + pred_map[target].insert(source); return target; } State inner_src = source; for (size_t i = 0; i < len - 1; ++i) { const State inner_target = nft.add_state_with_level(nft.levels[inner_src] + 1); nft.delta.add(inner_src, symbol, inner_target); + // pred_map[inner_target].insert(inner_src); // Optimization: we do not need to track inner states in pred_map. inner_src = inner_target; } nft.delta.add(inner_src, symbol, target); + pred_map[target].insert(inner_src); return target; } - State add_transition(Nft& nft, const State source, const Symbol symbol, const State target, const JumpMode jump_mode) { - if (source == target) { return source;} + void add_transition_with_target(Nft& nft, const State source, const Symbol symbol, const State target, const JumpMode jump_mode, PredMap& pred_map) { + if (source == target) { return;} assert(nft.levels[source] < nft.levels[target] || nft.levels[target] == 0); const size_t trans_len = nft.levels[target] == 0 ? nft.num_of_levels - nft.levels[source] : nft.levels[target] - nft.levels[source]; assert(trans_len > 0); if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { nft.delta.add(source, symbol, target); - return target; + pred_map[target].insert(source); + return; } State inner_src = source; for (size_t i = 0; i < trans_len - 1; ++i) { const State inner_target = nft.add_state_with_level(nft.levels[inner_src] + 1); nft.delta.add(inner_src, symbol, inner_target); + // pred_map[inner_target].insert(inner_src); // Optimization: we do not need to track inner states in pred_map. inner_src = inner_target; } nft.delta.add(inner_src, symbol, target); - return target; + pred_map[target].insert(inner_src); + } + + void redirect_transitions(Nft& nft, const State old_target, const State new_target, const PredMap& pred_map) { + if (old_target == new_target) { return; } + + auto it = pred_map.find(old_target); + assert(it != pred_map.end()); + for (const State pred: it->second) { + for (SymbolPost& symbol_post: nft.delta.mutable_state_post(pred)) { + if (symbol_post.targets.contains(old_target)) { + symbol_post.targets.insert(new_target); + } + } + } } } @@ -95,16 +120,68 @@ namespace mata::nft Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const bool project_out_sync_levels, const JumpMode jump_mode) { assert(lhs_sync_levels.size() == rhs_sync_levels.size()); - // Number of Levels + // Number of Levels and States const size_t num_of_sync_levels = lhs_sync_levels.size(); const size_t lhs_num_of_levels = lhs.num_of_levels; const size_t rhs_num_of_levels = rhs.num_of_levels; + const size_t lhs_num_of_states = lhs.num_of_states(); + const size_t rhs_num_of_states = rhs.num_of_states(); const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (project_out_sync_levels ? (2 * num_of_sync_levels) : num_of_sync_levels); + // FAST STORAGE OF COMPOSITION STATES + // The largest matrix (product_matrix) of pairs of states we are brave enough to allocate. + // Let's say we are fine with allocating larget_composition * (about 8 Bytes) space. + // So ten million cells is close to 100 MB. + // If the number is larger, then we do not allocate a matrix, but use a vector of unordered maps (composition_vec_map). + // The unordered_map seems to be about twice slower. + constexpr size_t MAX_COMPOSITION_MATRIX_SIZE = 50'000'000; + // constexpr size_t MAX_COMPOSITION_MATRIX_SIZE = 0; + const bool larget_composition = lhs_num_of_states * rhs_num_of_states > MAX_COMPOSITION_MATRIX_SIZE; + assert(lhs_num_of_states < Limits::max_state); + assert(rhs_num_of_states < Limits::max_state); + + //Two variants of storage for the mapping from pairs of lhs and rhs states to composition state, for large and non-large products. + MatrixProductStorage matrix_composition_storage; + VecMapProductStorage vec_map_composition_storage; + InvertedProductStorage composition_to_lhs(lhs_num_of_states+rhs_num_of_states); + InvertedProductStorage composition_to_rhs(lhs_num_of_states+rhs_num_of_states); + + //Initialize the storage, according to the number of possible state pairs. + if (!larget_composition) + matrix_composition_storage = MatrixProductStorage(lhs_num_of_states, std::vector(rhs_num_of_states, Limits::max_state)); + else + vec_map_composition_storage = VecMapProductStorage(lhs_num_of_states); + + /// Give me the composition state for the pair of lhs and rhs states. + /// Returns Limits::max_state if not found. + auto get_state_from_product_storage = [&](State lhs_state, State rhs_state) { + if (!larget_composition) + return matrix_composition_storage[lhs_state][rhs_state]; + else { + auto it = vec_map_composition_storage[lhs_state].find(rhs_state); + if (it == vec_map_composition_storage[lhs_state].end()) + return Limits::max_state; + else + return it->second; + } + }; + + /// Insert new mapping lhs rhs state pair to composition state. + auto insert_to_product_storage = [&](State lhs_state, State rhs_state, State composition_state) { + if (!larget_composition) + matrix_composition_storage[lhs_state][rhs_state] = composition_state; + else + vec_map_composition_storage[lhs_state][rhs_state] = composition_state; + + composition_to_lhs.resize(composition_state+1); + composition_to_rhs.resize(composition_state+1); + composition_to_lhs[composition_state] = lhs_state; + composition_to_rhs[composition_state] = rhs_state; + }; + // Initialize the result NFT, state map, and worklist Nft result; result.num_of_levels = result_num_of_levels; - std::unordered_map, State> main_state_map; std::unordered_map pred_map; std::stack> worklist; std::unordered_set commited_states; @@ -119,115 +196,34 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const std::vector lhs_sync_levels_inv = invert(lhs_sync_levels, lhs_num_of_levels); const std::vector rhs_sync_levels_inv = invert(rhs_sync_levels, rhs_num_of_levels); - // Function: adds a new state to the result and puts it to the worklist - auto try_to_map_state_add_to_wordlist = [&](const State state, const State orig_a, const State orig_b, const bool is_a_lhs) { - const auto key = is_a_lhs ? std::make_pair(orig_a, orig_b) : std::make_pair(orig_b, orig_a); - auto it = main_state_map.find(key); - if (it != main_state_map.end()) { - assert(it->second == state); - return; + auto create_composition_state = [&](const State state_a, const State state_b, const Level level, const bool is_a_lhs = true, const State composition_state_to_add = Limits::max_state) { + assert(composition_state_to_add == Limits::max_state || result.levels[composition_state_to_add] == level); + + // Try to find the entry in the state map. + const auto key = is_a_lhs ? std::make_pair(state_a, state_b) : std::make_pair(state_b, state_a); + const State lhs_state = key.first; + const State rhs_state = key.second; + const State found_state = get_state_from_product_storage(lhs_state, rhs_state); + if (found_state != Limits::max_state) { + assert(found_state == composition_state_to_add || composition_state_to_add == Limits::max_state); + return found_state; } - main_state_map[key] = state; - result.levels[state] = 0; - worklist.push(key); - if ((is_a_lhs && lhs.final.contains(orig_a) && rhs.final.contains(orig_b)) || - (!is_a_lhs && rhs.final.contains(orig_a) && lhs.final.contains(orig_b))) { - result.final.insert(state); - } - }; - // Function: returns existing zero-level state or adds a new one and puts it to the worklist - auto get_state_or_add_and_put_to_worklist = [&](const State orig_a, const State orig_b, const bool is_a_lhs, std::stack>& worklist) { - const auto key = is_a_lhs ? std::make_pair(orig_a, orig_b) : std::make_pair(orig_b, orig_a); - auto it = main_state_map.find(key); - if (it != main_state_map.end()) { - return it->second; - } - const State new_state = result.add_state_with_level(0); - main_state_map[key] = new_state; - worklist.push(key); - if ((is_a_lhs && lhs.final.contains(orig_a) && rhs.final.contains(orig_b)) || - (!is_a_lhs && rhs.final.contains(orig_a) && lhs.final.contains(orig_b))) { - result.final.insert(new_state); - } - return new_state; - }; + // If not found, add a new state to the result NFT. + State new_state = (composition_state_to_add != Limits::max_state) ? composition_state_to_add : result.add_state_with_level(level); + insert_to_product_storage(lhs_state, rhs_state, new_state); - auto get_state = [&](const State lhs_state, const State rhs_state, const Level level) { - // Get the state from the map or add a new one. - const auto key = std::make_pair(lhs_state, rhs_state); - auto it = main_state_map.find(key); - if (it != main_state_map.end()) { - return it->second; - } - const State new_state = result.add_state_with_level(level); - main_state_map[key] = new_state; + // If the level is zero, we need to check for final states and add to the worklist. if (level == 0) { + // Because the key pair was not found in the map, we are sure that the state was not in the worklist yet either. worklist.push(key); - if (lhs.final.contains(lhs_state) && rhs.final.contains(rhs_state)) { + if ((is_a_lhs && lhs.final.contains(state_a) && rhs.final.contains(state_b)) || + (!is_a_lhs && rhs.final.contains(state_a) && lhs.final.contains(state_b))) { result.final.insert(new_state); } } - return new_state; - }; - - // Function: adds a transition from src to tgt with the given symbol - // TODO: Replace by a method in Nft - auto repeat_transition = [&](const State src, const size_t trans_len, const Symbol symbol) { - if (trans_len == 0) { - return src; // No transition, return the same state - } - if (jump_mode == JumpMode::RepeatSymbol || trans_len == 1) { - assert((result.levels[src] + trans_len) <= result_num_of_levels); - const State tgt = result.add_state_with_level((result.levels[src] + trans_len) % result_num_of_levels); - pred_map[tgt].insert(src); - result.delta.add(src, symbol, tgt); - return tgt; - } - State inner_src = src; - for (size_t i = 0; i < trans_len - 1; ++i) { - assert(result.levels[inner_src] + 1 < result_num_of_levels); - const State tgt = result.add_state_with_level(result.levels[inner_src] + 1); - result.delta.add(inner_src, symbol, tgt); - inner_src = tgt; - } - assert((result.levels[inner_src] + 1) <= result_num_of_levels); - const State tgt = result.add_state_with_level((result.levels[inner_src] + 1) % result_num_of_levels); - pred_map[tgt].insert(inner_src); - result.delta.add(inner_src, symbol, tgt); - return tgt; - }; - - // Function: adds a transition from src to tgt with the given symbol - // TODO: Replace by a method in Nft - auto repeat_transition_with_tgt = [&](const State src, const State tgt, const size_t trans_len, const Symbol symbol) { - if (trans_len == 0) { - return; // No transition, do nothing - } - if (jump_mode == JumpMode::RepeatSymbol || trans_len == 1) { - pred_map[tgt].insert(src); - result.delta.add(src, symbol, tgt); - return; - } - State inner_src = repeat_transition(src, trans_len - 1, symbol); - pred_map[tgt].insert(inner_src); - result.delta.add(inner_src, symbol, tgt); - }; - // Function: redirects transitions from old_tgt to new_tgt - auto redirect = [&](const State old_tgt, const State new_tgt) { - if (old_tgt == new_tgt) { - return; - } - auto it = pred_map.find(old_tgt); - assert(it != pred_map.end()); - for (const State pred: it->second) { - for (SymbolPost& symbol_post: result.delta.mutable_state_post(pred)) { - if (symbol_post.targets.contains(old_tgt)) { - symbol_post.targets.insert(new_tgt); - } - } - } + return new_state; }; // Function: maps epsilon transitions on the sync path @@ -266,7 +262,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Add EPSILON transition for those in lhs BEFORE the first transition (we are in rhs). if (res_src == res_root && add_before) { - res_src = repeat_transition(res_src, between[0], EPSILON); + res_src = add_transition(result, res_src, EPSILON, between[0], jump_mode, pred_map); } if (is_sync_level[orig_nft.levels[orig_src]]) { @@ -278,15 +274,15 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const size_t sync_level_idx = sync_levels_inv[orig_nft.levels[orig_src]]; // Add EPSILON transitions for those in rhs AFTER this last transition before the sync level (we are in lhs). if (!add_before) { - res_src = repeat_transition(res_src, between[sync_level_idx], EPSILON); + res_src = add_transition(result, res_src, EPSILON, between[sync_level_idx], jump_mode, pred_map); } // Add EPSILON transitions for those in lhs BEFORE first transition after the sync level (we are in rhs). if (add_before) { - res_src = repeat_transition(res_src, between[sync_level_idx + 1], EPSILON); + res_src = add_transition(result, res_src, EPSILON, between[sync_level_idx + 1], jump_mode, pred_map); } if (!project_out_sync_levels) { - res_src = repeat_transition(res_src, 1, EPSILON); + res_src = add_transition(result, res_src, EPSILON, 1, jump_mode, pred_map); } // Process targets of the epsilon transitions (this transition will be projected out). @@ -295,20 +291,22 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // We are connecting to a zero-level state. // Add EPSILON transitions for those in rhs AFTER this last transition (we are in lhs). if (!add_before && between[num_of_sync_levels] != 0) { - const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); - repeat_transition_with_tgt(res_src, res_tgt, between[num_of_sync_levels], EPSILON); + const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); + assert((result.levels[res_src] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); + add_transition_with_target(result, res_src, EPSILON, res_tgt, jump_mode, pred_map); commited_states.insert(res_tgt); } else { // There is nothing to add. // Try to find if there is already a mapping for this state. const auto key = is_orig_lhs ? std::make_pair(orig_tgt, wait_root) : std::make_pair(wait_root, orig_tgt); - auto it = main_state_map.find(key); - if (it != main_state_map.end()) { + const State found_state = get_state_from_product_storage(key.first, key.second); + if (found_state != Limits::max_state) { // The mapping exists, redirect transition goig to orig_src to the existing state. - redirect(res_src, it->second); + redirect_transitions(result, res_src, found_state, pred_map); } else { + assert(found_state == Limits::max_state); // The mapping does not exist. Update it. - try_to_map_state_add_to_wordlist(res_src, orig_tgt, wait_root, is_orig_lhs); + create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs, res_src); commited_states.insert(res_src); } } @@ -341,13 +339,15 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& result.delta.add(res_src, symbol_post.symbol, res_tgt_inner); if (!exist_res_tgt_inner) { // Then add the EPSILON transitions. - const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); - repeat_transition_with_tgt(res_tgt_inner, res_tgt, between[num_of_sync_levels], EPSILON); + const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); + // const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); + assert((result.levels[res_tgt_inner] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); + add_transition_with_target(result, res_tgt_inner, EPSILON, res_tgt, jump_mode, pred_map); commited_states.insert(res_tgt); } } else { assert(result.levels[res_src] + level_diff == result_num_of_levels); - const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); + const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); result.delta.add(res_src, symbol_post.symbol, res_tgt); commited_states.insert(res_tgt); } @@ -402,12 +402,12 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // We are connecting to a zero-level state. if (rhs_between[num_of_sync_levels] == 0) { // There is no transition in rhs. - const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_src, true, main_worklist); + const State res_tgt = create_composition_state(lhs_tgt, rhs_src, 0); result.delta.add(res_src, symbol_post.symbol, res_tgt); commited_states.insert(res_tgt); } else { // There is a transition in rhs. - const State res_tgt = get_state(lhs_tgt, rhs_src, trans_len + res_src_level); + const State res_tgt = create_composition_state(lhs_tgt, rhs_src, trans_len + res_src_level); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); if (!visited.contains({lhs_tgt, rhs_src})) { @@ -420,7 +420,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // We are connecting to a non-zero-level state. const Level level_diff = lhs_tgt_level - lhs_src_level; assert(res_src_level + level_diff <= result_num_of_levels); - const State res_tgt = get_state(lhs_tgt, rhs_src, (res_src_level + level_diff) % result_num_of_levels); + const State res_tgt = create_composition_state(lhs_tgt, rhs_src, (res_src_level + level_diff) % result_num_of_levels); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); if (!visited.contains({lhs_tgt, rhs_src})) { @@ -440,14 +440,14 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const State rhs_tgt: symbol_post.targets) { const Level rhs_tgt_level = rhs.levels[rhs_tgt]; if (rhs_tgt_level == 0) { - const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_src, rhs_tgt, true, main_worklist); + const State res_tgt = create_composition_state(lhs_src, rhs_tgt, 0); result.delta.add(res_src, symbol_post.symbol, res_tgt); commited_states.insert(res_tgt); } else { // We are connecting to a non-zero-level state. const Level level_diff = rhs_tgt_level - rhs_src_level; assert(res_src_level + level_diff <= result_num_of_levels); - const State res_tgt = get_state(lhs_src, rhs_tgt, (res_src_level + level_diff) % result_num_of_levels); + const State res_tgt = create_composition_state(lhs_src, rhs_tgt, (res_src_level + level_diff) % result_num_of_levels); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); if (!visited.contains({lhs_src, rhs_tgt})) { @@ -470,7 +470,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets, const Symbol symbol) { State local_res_src = res_src; if (!project_out_sync_levels && !lhs_targets.empty() && !rhs_targets.empty()) { - local_res_src = repeat_transition(local_res_src, 1, symbol); + local_res_src = add_transition(result, local_res_src, symbol, 1, jump_mode, pred_map); } for (const State lhs_tgt: lhs_targets) { const Level lhs_tgt_level = lhs.levels[lhs_tgt]; @@ -478,46 +478,23 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const Level rhs_tgt_level = rhs.levels[rhs_tgt]; if (lhs_tgt_level == 0 && rhs_tgt_level == 0) { const auto key = std::make_pair(lhs_tgt, rhs_tgt); - auto it = main_state_map.find(key); - // if (!project_out_sync_levels) { - // if (it != main_state_map.end()) { - // const State res_tgt = it->second; - // // The mapping exists, redirect transition going to orig_src to the existing state. - // result.delta.add(res_src, symbol, res_tgt); - // } else { - // // The mapping does not exist. Update it. - // const State res_tgt = get_state_or_add_and_put_to_worklist(lhs_tgt, rhs_tgt, true, main_worklist); - // result.delta.add(res_src, symbol, res_tgt); - // commited_states.insert(res_tgt); - // } - // } else { - if (it != main_state_map.end()) { + const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); + if (found_state != Limits::max_state) { // The mapping exists, redirect transition goig to orig_src to the existing state. - redirect(local_res_src, it->second); + redirect_transitions(result, local_res_src, found_state, pred_map); } else { + assert(found_state == Limits::max_state); // The mapping does not exist. Update it. - try_to_map_state_add_to_wordlist(local_res_src, lhs_tgt, rhs_tgt, true); + create_composition_state(lhs_tgt, rhs_tgt, 0, true, local_res_src); commited_states.insert(local_res_src); } - // } } else { - // if (!project_out_sync_levels) { - // const State res_tgt = get_state(lhs_tgt, rhs_tgt, (res_src_level + 1) % result_num_of_levels); - // result.delta.add(res_src, symbol, res_tgt); - // pred_map[res_tgt].insert(res_src); - // if (!visited.contains({lhs_tgt, rhs_tgt})) { - // // We have not visited this pair yet. - // visited.insert({lhs_tgt, rhs_tgt}); - // worklist.push({res_tgt, lhs_tgt, rhs_tgt}); - // } - // } else { - if (!visited.contains({lhs_tgt, rhs_tgt})) { - // We have not visited this pair yet. - visited.insert({lhs_tgt, rhs_tgt}); - main_state_map[std::make_pair(lhs_tgt, rhs_tgt)] = local_res_src; - worklist.push({local_res_src, lhs_tgt, rhs_tgt}); - } - // } + if (!visited.contains({lhs_tgt, rhs_tgt})) { + // We have not visited this pair yet. + visited.insert({lhs_tgt, rhs_tgt}); + insert_to_product_storage(lhs_tgt, rhs_tgt, local_res_src); + worklist.push({local_res_src, lhs_tgt, rhs_tgt}); + } } } } @@ -571,7 +548,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { // Get the root state in the result NFT - const State res_root = get_state_or_add_and_put_to_worklist(lhs_root, rhs_root, true, worklist); + const State res_root = create_composition_state(lhs_root, rhs_root, 0); commited_states.insert(res_root); result.initial.insert(res_root); } @@ -583,7 +560,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& worklist.pop(); // Get the state in the result NFT - const State res_root = main_state_map.at({orig_a, orig_b}); + const State res_root = get_state_from_product_storage(orig_a, orig_b); if (!commited_states.contains(res_root)) { result.final.erase(res_root); continue; @@ -591,8 +568,6 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Map epsilon transitions on the sync path map_combination_path(res_root, orig_a, orig_b); - // map_epsilon_on_sync_path(res_root, orig_b, orig_a, false); - // break; } return result.trim(); From 195f20a4094fab168c8426b1331074140cece823 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 27 Jun 2025 13:53:19 +0200 Subject: [PATCH 10/65] before Noodler --- include/mata/nft/nft.hh | 12 +- src/nft/composition.cc | 247 +++++++++++++++-------------------- tests/nft/nft-composition.cc | 8 +- 3 files changed, 114 insertions(+), 153 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index 92f411207..a9f2cf95d 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -621,7 +621,7 @@ public: */ Nft apply( const nfa::Nfa& nfa, Level level_to_apply_on = 0, bool project_out_applied_level = true, - JumpMode jump_mode = JumpMode::RepeatSymbol) const; + JumpMode jump_mode = JumpMode::NoJump) const; /** * @brief Apply @p word to @c this. @@ -639,7 +639,7 @@ public: */ Nft apply( const Word& word, Level level_to_apply_on = 0, bool project_out_applied_level = true, - JumpMode jump_mode = JumpMode::RepeatSymbol) const; + JumpMode jump_mode = JumpMode::NoJump) const; /** * @brief Copy NFT as NFA. @@ -755,7 +755,7 @@ Nft intersection(const Nft& lhs, const Nft& rhs, Nft compose(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, bool project_out_sync_levels = true, - JumpMode jump_mode = JumpMode::RepeatSymbol); + JumpMode jump_mode = JumpMode::NoJump); /** * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs"). @@ -774,10 +774,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, * is interpreted as a sequence repeating the same symbol or as a single instance of the symbol followed by a sequence of @c DONT_CARE. * @return A new NFT after the composition. */ -Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::RepeatSymbol); +inline Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::NoJump) { + return compose(lhs, rhs, utils::OrdVector{ lhs_sync_level }, utils::OrdVector{ rhs_sync_level }, project_out_sync_levels, jump_mode); +} Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, - bool are_sync_levels_unwinded = false, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::RepeatSymbol); + bool project_out_sync_levels = true, bool are_sync_levels_unwinded = false, JumpMode jump_mode = JumpMode::NoJump); /** * @brief Concatenate two NFTs. diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 2bf9b3855..b708c7a1a 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -117,7 +117,9 @@ namespace { namespace mata::nft { -Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool are_sync_levels_unwinded, const bool project_out_sync_levels, const JumpMode jump_mode) { +// TODO: Refactor this function to be more readable and maintainable. +// There is a shitton of code duplication in this function. +Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool project_out_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { assert(lhs_sync_levels.size() == rhs_sync_levels.size()); // Number of Levels and States @@ -227,6 +229,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& }; // Function: maps epsilon transitions on the sync path + // TODO: Refactor - too many similarities in the code and with the code of map_combination_path. auto map_epsilon_on_sync_path = [&](const State res_root, const State orig_root, const State wait_root, const bool is_orig_lhs) { const Nft& orig_nft = is_orig_lhs ? lhs : rhs; const OrdVector& sync_levels = is_orig_lhs ? lhs_sync_levels : rhs_sync_levels; @@ -234,7 +237,6 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const std::vector& between = is_orig_lhs ? rhs_between : lhs_between; // Use rhs_between if we are in lhs NFT, and vice versa. const BoolVector& is_sync_level = is_orig_lhs ? lhs_is_sync_level : rhs_is_sync_level; const bool add_before = !is_orig_lhs; // On each section within sync levels, lhs transitions go begore rhs transitions. - std::stack>& main_worklist = worklist; // Worklist contains pairs of (state in the result NFT, and state in the original lhs/rhs NFT). std::stack> worklist; @@ -243,6 +245,9 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& visited.insert(orig_root); pred_map.clear(); + // TODO: Use more optimal date structure. + // TODO: I can maybe use the main memory storage (matrix), + // I'll just look up the pair (root, orig_state). Maybe. std::unordered_map local_state_map; auto get_state = [&](const State orig_state, const Level level) { // Get the state from the map or add a new one. @@ -288,6 +293,11 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Process targets of the epsilon transitions (this transition will be projected out). for (const State orig_tgt: epsilon_post_it->targets) { if (orig_nft.levels[orig_tgt] == 0) { + if (orig_src == orig_root && orig_nft.num_of_levels > 1) { + // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // It has been already handled in the main loop. + continue; + } // We are connecting to a zero-level state. // Add EPSILON transitions for those in rhs AFTER this last transition (we are in lhs). if (!add_before && between[num_of_sync_levels] != 0) { @@ -326,8 +336,13 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const SymbolPost& symbol_post: orig_nft.delta[orig_src]) { for (const State orig_tgt: symbol_post.targets) { const Level orig_tgt_level = orig_nft.levels[orig_tgt]; - if (orig_tgt_level == 0) { + if (orig_src == orig_root && symbol_post.symbol == EPSILON && orig_nft.num_of_levels > 1) { + // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // It has been already handled in the main loop. + continue; + } + // We are connecting to a zero-level state. const Level level_diff = orig_nft.num_of_levels - orig_src_level; if (!add_before && between[num_of_sync_levels] != 0) { @@ -340,7 +355,6 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& if (!exist_res_tgt_inner) { // Then add the EPSILON transitions. const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); - // const State res_tgt = get_state_or_add_and_put_to_worklist(orig_tgt, wait_root, is_orig_lhs, main_worklist); assert((result.levels[res_tgt_inner] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); add_transition_with_target(result, res_tgt_inner, EPSILON, res_tgt, jump_mode, pred_map); commited_states.insert(res_tgt); @@ -371,13 +385,12 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } }; + // Maps result of a composition starting from zero-level states lhs_root and rhs_root. + // This mapping ends in next zero-level states. auto map_combination_path = [&](const State res_root, const State lhs_root, const State rhs_root) { - std::stack>& main_worklist = worklist; pred_map.clear(); std::stack> worklist; worklist.push({res_root, lhs_root, rhs_root}); - std::unordered_set> visited; - visited.insert({lhs_root, rhs_root}); bool perform_lhs_wait = false; bool perform_rhs_wait = false; @@ -389,7 +402,6 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const Level res_src_level = result.levels[res_src]; const Level lhs_src_level = lhs.levels[lhs_src]; const Level rhs_src_level = rhs.levels[rhs_src]; - const bool lhs_in_target_and_rhs_remains = lhs_src_level == 0 && rhs_src_level != 0; // Go in lhs all the way down to the sync level. @@ -397,6 +409,12 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { for (const State lhs_tgt: symbol_post.targets) { const Level lhs_tgt_level = lhs.levels[lhs_tgt]; + if (symbol_post.symbol == EPSILON && lhs_src_level == 0 && lhs_tgt_level == 0 && lhs_num_of_levels > 1) { + // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // It has been already handled in the main loop. + continue; + } + if (lhs_tgt_level == 0) { const size_t trans_len = lhs_num_of_levels - lhs_src_level; // We are connecting to a zero-level state. @@ -404,29 +422,31 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // There is no transition in rhs. const State res_tgt = create_composition_state(lhs_tgt, rhs_src, 0); result.delta.add(res_src, symbol_post.symbol, res_tgt); + // There are no unprocessed synchronization transitions, so we can commit the state. + // TODO: is it really necessary? commited_states.insert(res_tgt); } else { // There is a transition in rhs. + const bool visited = get_state_from_product_storage(lhs_tgt, rhs_src) != Limits::max_state; const State res_tgt = create_composition_state(lhs_tgt, rhs_src, trans_len + res_src_level); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); - if (!visited.contains({lhs_tgt, rhs_src})) { + if (!visited) { // Not visited, add to the worklist worklist.push({res_tgt, lhs_tgt, rhs_src}); - visited.insert({lhs_tgt, rhs_src}); } } } else { // We are connecting to a non-zero-level state. const Level level_diff = lhs_tgt_level - lhs_src_level; assert(res_src_level + level_diff <= result_num_of_levels); + bool visited = get_state_from_product_storage(lhs_tgt, rhs_src) != Limits::max_state; const State res_tgt = create_composition_state(lhs_tgt, rhs_src, (res_src_level + level_diff) % result_num_of_levels); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); - if (!visited.contains({lhs_tgt, rhs_src})) { + if (!visited) { // Not visited, add to the worklist worklist.push({res_tgt, lhs_tgt, rhs_src}); - visited.insert({lhs_tgt, rhs_src}); } } } @@ -439,7 +459,14 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { for (const State rhs_tgt: symbol_post.targets) { const Level rhs_tgt_level = rhs.levels[rhs_tgt]; + if (symbol_post.symbol == EPSILON && rhs_src_level == 0 && rhs_tgt_level == 0 && rhs_num_of_levels > 1) { + // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // It has been already handled in the main loop. + continue; + } + if (rhs_tgt_level == 0) { + // We are connecting to a zero-level state. const State res_tgt = create_composition_state(lhs_src, rhs_tgt, 0); result.delta.add(res_src, symbol_post.symbol, res_tgt); commited_states.insert(res_tgt); @@ -447,13 +474,13 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // We are connecting to a non-zero-level state. const Level level_diff = rhs_tgt_level - rhs_src_level; assert(res_src_level + level_diff <= result_num_of_levels); + const bool visited = get_state_from_product_storage(lhs_src, rhs_tgt) != Limits::max_state; const State res_tgt = create_composition_state(lhs_src, rhs_tgt, (res_src_level + level_diff) % result_num_of_levels); result.delta.add(res_src, symbol_post.symbol, res_tgt); pred_map[res_tgt].insert(res_src); - if (!visited.contains({lhs_src, rhs_tgt})) { + if (!visited) { // Not visited, add to the worklist worklist.push({res_tgt, lhs_src, rhs_tgt}); - visited.insert({lhs_src, rhs_tgt}); } } } @@ -467,6 +494,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& perform_lhs_wait = perform_lhs_wait || (epsilon_in_rhs && !epsilon_in_lhs); perform_rhs_wait = perform_rhs_wait || (epsilon_in_lhs && !epsilon_in_rhs); + // Processes the intersection of transitions in lhs and rhs. auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets, const Symbol symbol) { State local_res_src = res_src; if (!project_out_sync_levels && !lhs_targets.empty() && !rhs_targets.empty()) { @@ -474,24 +502,34 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } for (const State lhs_tgt: lhs_targets) { const Level lhs_tgt_level = lhs.levels[lhs_tgt]; + if (symbol == EPSILON && lhs_src_level == 0 && lhs_tgt_level == 0 && lhs_num_of_levels > 1) { + // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // It has been already handled in the main loop. + continue; + } for (const State rhs_tgt: rhs_targets) { const Level rhs_tgt_level = rhs.levels[rhs_tgt]; + if (symbol == EPSILON && rhs_src_level == 0 && rhs_tgt_level == 0 && rhs_num_of_levels > 1) { + // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // It has been already handled in the main loop. + continue; + } + if (lhs_tgt_level == 0 && rhs_tgt_level == 0) { - const auto key = std::make_pair(lhs_tgt, rhs_tgt); + // We are connecting to a zero-level state. const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); if (found_state != Limits::max_state) { - // The mapping exists, redirect transition goig to orig_src to the existing state. - redirect_transitions(result, local_res_src, found_state, pred_map); - } else { - assert(found_state == Limits::max_state); - // The mapping does not exist. Update it. - create_composition_state(lhs_tgt, rhs_tgt, 0, true, local_res_src); - commited_states.insert(local_res_src); - } + // The mapping exists, redirect transition goig to orig_src to the existing state. + redirect_transitions(result, local_res_src, found_state, pred_map); + } else { + assert(found_state == Limits::max_state); + // The mapping does not exist. Update it. + create_composition_state(lhs_tgt, rhs_tgt, 0, true, local_res_src); + commited_states.insert(local_res_src); + } } else { - if (!visited.contains({lhs_tgt, rhs_tgt})) { + if (get_state_from_product_storage(lhs_tgt, rhs_tgt) == Limits::max_state) { // We have not visited this pair yet. - visited.insert({lhs_tgt, rhs_tgt}); insert_to_product_storage(lhs_tgt, rhs_tgt, local_res_src); worklist.push({local_res_src, lhs_tgt, rhs_tgt}); } @@ -500,7 +538,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } }; - // Intersection + // Process symbol intersection mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); mata::utils::push_back(sync_iterator, lhs.delta[lhs_src]); mata::utils::push_back(sync_iterator, rhs.delta[rhs_src]); @@ -515,6 +553,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& ); } + // Process DONT_CARE symbol in lhs. if (lhs.delta[lhs_src].find(DONT_CARE) != lhs.delta[lhs_src].end()) { for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { process_intersection( @@ -525,6 +564,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } } + // Process DONT_CARE symbol in rhs. if (rhs.delta[rhs_src].find(DONT_CARE) != rhs.delta[rhs_src].end()) { for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { process_intersection( @@ -536,6 +576,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } } + // Perform waiting if necessary. if (perform_lhs_wait) { map_epsilon_on_sync_path(res_root, rhs_root, lhs_root, false); } @@ -544,7 +585,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } }; - // INITIALIZATION + // INITIALIZATION on a worklist (side effect of a create_composition_state) for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { // Get the root state in the result NFT @@ -556,142 +597,60 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // MAIN LOOP while (!worklist.empty()) { - auto [orig_a, orig_b] = worklist.top(); + auto [orig_lhs, orig_rhs] = worklist.top(); worklist.pop(); // Get the state in the result NFT - const State res_root = get_state_from_product_storage(orig_a, orig_b); + const State res_root = get_state_from_product_storage(orig_lhs, orig_rhs); + // TODO - is commited_states really necessary? if (!commited_states.contains(res_root)) { result.final.erase(res_root); continue; } - // Map epsilon transitions on the sync path - map_combination_path(res_root, orig_a, orig_b); - } - - return result.trim(); -} - - - -Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { - assert(!lhs_sync_levels.empty()); - assert(lhs_sync_levels.size() == rhs_sync_levels.size()); - - - return compose_fast(lhs.unwind_jumps({ DONT_CARE }, jump_mode), rhs.unwind_jumps({ DONT_CARE }, jump_mode), lhs_sync_levels, rhs_sync_levels, true, project_out_sync_levels, JumpMode::NoJump); - - // Inserts loop into the given Nft for each state with level 0. - // The loop word is constructed using the EPSILON symbol for all levels, except for the levels - // where is_dcare_on_transition is true, in which case the DONT_CARE symbol is used. - auto insert_self_loops = [&](Nft &nft, const BoolVector &is_dcare_on_transition) { - Word loop_word(nft.num_of_levels, EPSILON); - for (size_t i{ 0 }; i < nft.num_of_levels; i++) { - if (is_dcare_on_transition[i]) { - loop_word[i] = DONT_CARE; + // Process "long" EPSILON transitions leading from zero-level state to zero-level state. + auto epsilon_post_a = lhs.delta[orig_lhs].find(EPSILON); + if (epsilon_post_a != lhs.delta[orig_lhs].end()) { + for (const State target: epsilon_post_a->targets) { + if (lhs.levels[target] == 0) { + const State res_tgt = create_composition_state(target, orig_rhs, 0); + result.delta.add(res_root, EPSILON, res_tgt); + commited_states.insert(res_tgt); + } } } - - size_t original_num_of_states = nft.num_of_states(); - for (State s{ 0 }; s < original_num_of_states; s++) { - if (nft.levels[s] == 0) { - nft.insert_word(s, loop_word, s); + auto epsilon_post_b = rhs.delta[orig_rhs].find(EPSILON); + if (epsilon_post_b != rhs.delta[orig_rhs].end()) { + for (const State target: epsilon_post_b->targets) { + if (rhs.levels[target] == 0) { + const State res_tgt = create_composition_state(orig_lhs, target, 0); + result.delta.add(res_root, EPSILON, res_tgt); + commited_states.insert(res_tgt); + } } } - }; - // Calculate the number of non-synchronization levels in lhs and rhs before/after each/last synchronization level. - // Example: - // Lets suppose we have 2 synchnonization levels. - // The vector lhs_nonsync_levels_cnt = { 2, 0, 1 } indicates that - // - before the first synchronization level (i == 0) there are 2 non-synchronization levels - // - before the second synchronization level (i == 1) there are 0 non-synchronization levels - // - after the last synchronization level (i == |sync|) there is 1 non-synchronization level - std::vector lhs_nonsync_levels_cnt; - std::vector rhs_nonsync_levels_cnt; - const size_t num_of_sync_levels = lhs_sync_levels.size(); - lhs_nonsync_levels_cnt.reserve(num_of_sync_levels + 1); - rhs_nonsync_levels_cnt.reserve(num_of_sync_levels + 1); - auto lhs_sync_levels_it = lhs_sync_levels.cbegin(); - auto rhs_sync_levels_it = rhs_sync_levels.cbegin(); - // Before the first synchronization level (i == 0) - lhs_nonsync_levels_cnt.push_back(*lhs_sync_levels_it++); - rhs_nonsync_levels_cnt.push_back(*rhs_sync_levels_it++); - // Before each synchronization level (i < |sync|) - for (size_t i = 1; i < num_of_sync_levels; ++i) { - lhs_nonsync_levels_cnt.push_back(*lhs_sync_levels_it - *(lhs_sync_levels_it - 1) - 1); - rhs_nonsync_levels_cnt.push_back(*rhs_sync_levels_it - *(rhs_sync_levels_it - 1) - 1); - ++lhs_sync_levels_it; - ++rhs_sync_levels_it; - } - // After the last synchronization level (i == |sync|) - lhs_nonsync_levels_cnt.push_back(static_cast(lhs.num_of_levels) - *(lhs_sync_levels_it - 1) - 1); - rhs_nonsync_levels_cnt.push_back(static_cast(rhs.num_of_levels) - *(rhs_sync_levels_it - 1) - 1); - - // Construct a mask for new levels in lhs and rhs. - // For each synchronization block (non-synchronization levels up to a synchronization transition), - // first insert the non-synchronization levels from lhs, then from rhs, and finally the synchronization level itself. - // For the last block of non-synchronization levels after the last synchronization level, - // insert the non-synchronization levels from lhs first, followed by the levels from rhs. - // Example: - // LHS | RHS - // --------------------------------------------+--------------------------------------------- - // num_of_levels = 5 | num_of_levels = 4 - // sync_levels = { 2, 4 } | sync_levels = { 1, 2 } - // --------------------------------------------|--------------------------------------------- - // nonsync_levels_cnt = { 2, 1, 0 } | nonsync_levels_cnt = { 1, 0, 1 } - // new_levels_mask = { 0, 0, 1, 0, 0, 0, 1 } | new_levels_mask = { 1, 1, 0, 0, 1, 0, 0 } - auto lhs_nonsync_levels_cnt_it = lhs_nonsync_levels_cnt.cbegin(); - auto rhs_nonsync_levels_cnt_it = rhs_nonsync_levels_cnt.cbegin(); - BoolVector lhs_new_levels_mask; - BoolVector rhs_new_levels_mask; - OrdVector sync_levels_to_project_out; - Level level = 0; - const size_t nonsync_levels_cnt_size = lhs_nonsync_levels_cnt.size(); - for (size_t i = 0; i < nonsync_levels_cnt_size; ++i) { - // LHS goes first -> make space (insert new levels) in RHS - for (unsigned j = 0; j < *lhs_nonsync_levels_cnt_it; ++j, ++level) { - lhs_new_levels_mask.push_back(false); - rhs_new_levels_mask.push_back(true); - } - // RHS goes first -> make space (insert new levels) in LHS - for (unsigned j = 0; j < *rhs_nonsync_levels_cnt_it; ++j, ++level) { - lhs_new_levels_mask.push_back(true); - rhs_new_levels_mask.push_back(false); - } - // The synchronization level goes last - if (i < num_of_sync_levels) { - sync_levels_to_project_out.push_back(level); - lhs_new_levels_mask.push_back(false); - rhs_new_levels_mask.push_back(false); - ++level; - } - ++lhs_nonsync_levels_cnt_it; - ++rhs_nonsync_levels_cnt_it; + // Map path created by the composition of lhs and rhs NFTs. + // Each iteration starts in zero-level states orig_lhs and orig_rhs + // and ends in next zero-level states. + map_combination_path(res_root, orig_lhs, orig_rhs); } - // Insert new levels into lhs and rhs. - Nft lhs_synced = insert_levels(lhs, lhs_new_levels_mask, jump_mode); - Nft rhs_synced = insert_levels(rhs, rhs_new_levels_mask, jump_mode); - - // Two auxiliary states (states from inserted loops) can not create a product state. - const State lhs_first_aux_state = lhs_synced.num_of_states(); - const State rhs_first_aux_state = rhs_synced.num_of_states(); + return result.trim(); +} - // Insert self-loops into lhs and rhs to ensure synchronization on epsilon transitions. - insert_self_loops(lhs_synced, lhs_new_levels_mask); - insert_self_loops(rhs_synced, rhs_new_levels_mask); +Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { + assert(!lhs_sync_levels.empty()); + assert(lhs_sync_levels.size() == rhs_sync_levels.size()); - Nft result{ intersection(lhs_synced, rhs_synced, nullptr, jump_mode, lhs_first_aux_state, rhs_first_aux_state) }; - if (project_out_sync_levels) { - result = project_out(result, sync_levels_to_project_out, jump_mode); + if (jump_mode == JumpMode::NoJump) { + // If we are not jumping, we can use the fast composition. + return compose_fast(lhs, rhs, lhs_sync_levels, rhs_sync_levels, project_out_sync_levels, true, jump_mode); } - return result; -} -Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, bool project_out_sync_levels, const JumpMode jump_mode) { - return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels, jump_mode); + // TODO - add better unwinding + // throw std::runtime_error("Noodler should not go here. Noodler should always use JumpMode::NoJump."); + return compose_fast(lhs.unwind_jumps({ DONT_CARE }, jump_mode), rhs.unwind_jumps({ DONT_CARE }, jump_mode), lhs_sync_levels, rhs_sync_levels, project_out_sync_levels, true, JumpMode::NoJump); } } // mata::nft diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 66870c9c8..29bb2f15c 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -787,7 +787,7 @@ TEST_CASE("nft::compose_fast()") { rhs.delta.add(3, 'g', 4); rhs.delta.add(3, 'h', 4); - Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, true, false, JumpMode::NoJump); + Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, false, true, JumpMode::NoJump); Nft expected; expected.num_of_levels = 3; @@ -840,7 +840,7 @@ TEST_CASE("nft::compose_fast()") { rhs.delta.add(3, 'y', 0); rhs.print_to_dot(std::string("rhs.dot"), true, true); - Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, true, false, JumpMode::NoJump); + Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, false, true, JumpMode::NoJump); result.print_to_dot(std::string("result.dot"), true, true); Nft expected; @@ -903,7 +903,7 @@ TEST_CASE("nft::compose_fast()") { rhs.delta.add(11, 'x', 13); rhs.delta.add(13, 'y', 15); - Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, true, false, JumpMode::NoJump); + Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, false, true, JumpMode::NoJump); Nft expected; expected.num_of_levels = 5; @@ -962,7 +962,7 @@ TEST_CASE("nft::compose_fast()") { rhs.delta.add(8, 'x', 10); rhs.delta.add(10, 'y', 12); - Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, true, false, JumpMode::NoJump); + Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, false, true, JumpMode::NoJump); Nft expected; expected.num_of_levels = 4; From 739501a539ec3ef1c76b63ef0860b8716965b72e Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 27 Jun 2025 13:57:17 +0200 Subject: [PATCH 11/65] throw --- src/nft/composition.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index b708c7a1a..af0e81b82 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -649,7 +649,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_lev } // TODO - add better unwinding - // throw std::runtime_error("Noodler should not go here. Noodler should always use JumpMode::NoJump."); + throw std::runtime_error("Noodler should not go here. Noodler should always use JumpMode::NoJump."); return compose_fast(lhs.unwind_jumps({ DONT_CARE }, jump_mode), rhs.unwind_jumps({ DONT_CARE }, jump_mode), lhs_sync_levels, rhs_sync_levels, project_out_sync_levels, true, JumpMode::NoJump); } From 1a1a963c355a7076b09ce904a5b7ab2e9e5abca8 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sat, 28 Jun 2025 01:59:05 +0200 Subject: [PATCH 12/65] no jump --- src/nft/composition.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index b708c7a1a..065b62c89 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -614,7 +614,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const State target: epsilon_post_a->targets) { if (lhs.levels[target] == 0) { const State res_tgt = create_composition_state(target, orig_rhs, 0); - result.delta.add(res_root, EPSILON, res_tgt); + add_transition_with_target(result, res_root, EPSILON, res_tgt, jump_mode, pred_map); + // result.delta.add(res_root, EPSILON, res_tgt); commited_states.insert(res_tgt); } } @@ -624,7 +625,8 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const State target: epsilon_post_b->targets) { if (rhs.levels[target] == 0) { const State res_tgt = create_composition_state(orig_lhs, target, 0); - result.delta.add(res_root, EPSILON, res_tgt); + add_transition_with_target(result, res_root, EPSILON, res_tgt, jump_mode, pred_map); + // result.delta.add(res_root, EPSILON, res_tgt); commited_states.insert(res_tgt); } } From b20501cef65510b2691513d08954c9f8663c1372 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sun, 29 Jun 2025 00:39:35 +0200 Subject: [PATCH 13/65] repare visit condition --- src/nft/composition.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 065b62c89..102bae3ca 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -528,10 +528,13 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& commited_states.insert(local_res_src); } } else { - if (get_state_from_product_storage(lhs_tgt, rhs_tgt) == Limits::max_state) { + const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); + if (found_state == Limits::max_state) { // We have not visited this pair yet. insert_to_product_storage(lhs_tgt, rhs_tgt, local_res_src); worklist.push({local_res_src, lhs_tgt, rhs_tgt}); + } else { + redirect_transitions(result, local_res_src, found_state, pred_map); } } } From 1282c59a93563edd71d8c6af9e323940688df875 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sun, 29 Jun 2025 01:14:33 +0200 Subject: [PATCH 14/65] ifs --- src/nft/composition.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 102bae3ca..a0812304a 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -101,8 +101,16 @@ namespace { void redirect_transitions(Nft& nft, const State old_target, const State new_target, const PredMap& pred_map) { if (old_target == new_target) { return; } + if (nft.initial.contains(old_target)) { + nft.initial.insert(new_target); + } + auto it = pred_map.find(old_target); - assert(it != pred_map.end()); + if (it == pred_map.end()) { + // No predecessors for the old target, nothing to redirect. + return; + } + // assert(it != pred_map.end()); for (const State pred: it->second) { for (SymbolPost& symbol_post: nft.delta.mutable_state_post(pred)) { if (symbol_post.targets.contains(old_target)) { From e01c21a626c9a6a12e68238b9edad23867550823 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 16 Jul 2025 15:49:11 +0200 Subject: [PATCH 15/65] update anonymous helper functions --- src/nft/composition.cc | 128 +++++++++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 30 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index af0e81b82..d5b640711 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -19,8 +19,16 @@ namespace { using VecMapProductStorage = std::vector>; using InvertedProductStorage = std::vector; + /** + * Create a mask from the given entries and universum size. + * The mask will have `true` for each entry in @p entries and `false` for all other indices. + * @param entries The entries to be set in the mask. + * @param universum_size The size of the universum. + * @return A BoolVector representing the mask. + */ template mata::BoolVector create_mask(const OrdVector& entries, const size_t universum_size) { + assert(entries.empty() || entries.back() < universum_size); mata::BoolVector mask(universum_size, false); for (const auto& entry : entries) { mask[entry] = true; @@ -28,77 +36,137 @@ namespace { return mask; } + /** + * Get a vector of indices for the entries in the given OrdVector. + * This is useful for quickly finding the position of an entry in the OrdVector. + * + * @param entries The OrdVector containing the entries. + * @param universum_size The size of the universum. + * @return A vector of indices where each index corresponds to the position of the entry in @p entries. + * If an entry is not present, its index will be set to `std::numeric_limits::max()`. + */ template - std::vector invert(const OrdVector& entries, const size_t universum_size) { - std::vector inverted(universum_size, std::numeric_limits::max()); + std::vector get_entry_indices_vec(const OrdVector& entries, const size_t universum_size) { + assert(entries.empty() || entries.back() < universum_size); + std::vector entry_indices_vec(universum_size, std::numeric_limits::max()); size_t idx = 0; for (const auto& entry : entries) { - inverted[entry] = idx++; + entry_indices_vec[entry] = idx++; } - return inverted; + return entry_indices_vec; } + /** + * Get the sizes of intervals between borders in the given OrdVector. The border on index 0 is before + * the first element, the border on index 1 is after the first element, and so on. + * + * @param border_indices The OrdVector containing the indices of the borders. + * @param universum_size The size of the universum. + * @return A vector containing the sizes of intervals between the entries in @p border_indices. + */ template - std::vector get_partition_sizes(const OrdVector& border_indices, const size_t universum_size) { + std::vector get_interval_sizes(const OrdVector& border_indices, const size_t universum_size) { assert(!border_indices.empty()); - const size_t num_partitions = border_indices.size(); - std::vector sizes(num_partitions + 1, 0); - auto partition_borders_it = border_indices.cbegin(); - sizes[0] = *partition_borders_it; - for (size_t i = 1; i < num_partitions; ++i) { - const size_t prev_border = *partition_borders_it; - ++partition_borders_it; - sizes[i] = *partition_borders_it - prev_border - 1; + const size_t num_of_intervals = border_indices.size() + 1; + std::vector sizes(num_of_intervals, 0); + auto border_indices_it = border_indices.cbegin(); + + sizes[0] = *border_indices_it; + const size_t last_interval_idx = num_of_intervals - 1; + for (size_t i = 1; i < last_interval_idx; ++i) { + const size_t prev_border = *border_indices_it; + ++border_indices_it; + sizes[i] = *border_indices_it - prev_border - 1; } - sizes[num_partitions] = universum_size - *partition_borders_it - 1; + sizes[num_of_intervals - 1] = universum_size - *border_indices_it - 1; + return sizes; } + /** Add a transition to the NFT from source state over symbol and length len to a new target state. + * + * @param nft The NFT to which the transition will be added. + * @param source The source state of the transition. + * @param symbol The symbol of the transition. + * @param len The length of the transition. + * @param jump_mode The mode of the jump. + * @param pred_map A map to keep track of predecessors for each target state. + * @return The target state of the transition. + */ State add_transition(Nft& nft, const State source, const Symbol symbol, const size_t len, const JumpMode jump_mode, PredMap& pred_map) { - assert(nft.levels[source] + len <= nft.num_of_levels); if (len == 0) { return source; } + + assert(nft.levels[source] + len <= nft.num_of_levels); + const Level target_level = static_cast((nft.levels[source] + len) % nft.num_of_levels); + const State target = nft.add_state_with_level(target_level); - const State target = nft.add_state_with_level((nft.levels[source] + len) % static_cast(nft.num_of_levels)); if (len == 1 || jump_mode == JumpMode::RepeatSymbol) { nft.delta.add(source, symbol, target); pred_map[target].insert(source); return target; } + State inner_src = source; - for (size_t i = 0; i < len - 1; ++i) { - const State inner_target = nft.add_state_with_level(nft.levels[inner_src] + 1); + Level inner_level = nft.levels[inner_src] + 1; + for (size_t i = 0; i < len - 1; ++i, ++inner_level) { + assert(inner_level < nft.num_of_levels); + const State inner_target = nft.add_state_with_level(inner_level); nft.delta.add(inner_src, symbol, inner_target); // pred_map[inner_target].insert(inner_src); // Optimization: we do not need to track inner states in pred_map. inner_src = inner_target; } + assert(inner_level == nft.levels[source] + len); nft.delta.add(inner_src, symbol, target); pred_map[target].insert(inner_src); + return target; } + /** + * Add a transition from source state to target state with the given symbol. + * + * @param nft The NFT to which the transition will be added. + * @param source The source state of the transition. + * @param symbol The symbol of the transition. + * @param target The target state of the transition. + * @param jump_mode The mode of the jump transitions. + * @param pred_map A map to keep track of predecessors for each target state. + */ void add_transition_with_target(Nft& nft, const State source, const Symbol symbol, const State target, const JumpMode jump_mode, PredMap& pred_map) { - if (source == target) { return;} - assert(nft.levels[source] < nft.levels[target] || nft.levels[target] == 0); + if (source == target) { return; } - const size_t trans_len = nft.levels[target] == 0 ? nft.num_of_levels - nft.levels[source] : nft.levels[target] - nft.levels[source]; + assert(nft.levels[source] < nft.levels[target] || nft.levels[target] == 0); + const size_t trans_len = (nft.levels[target] == 0 ? nft.num_of_levels : nft.levels[target]) - nft.levels[source]; assert(trans_len > 0); + if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { nft.delta.add(source, symbol, target); pred_map[target].insert(source); return; } + State inner_src = source; - for (size_t i = 0; i < trans_len - 1; ++i) { - const State inner_target = nft.add_state_with_level(nft.levels[inner_src] + 1); + Level inner_level = nft.levels[inner_src] + 1; + for (size_t i = 0; i < trans_len - 1; ++i, ++inner_level) { + assert(inner_level < nft.num_of_levels); + const State inner_target = nft.add_state_with_level(inner_level); nft.delta.add(inner_src, symbol, inner_target); // pred_map[inner_target].insert(inner_src); // Optimization: we do not need to track inner states in pred_map. inner_src = inner_target; } + assert(inner_level == nft.levels[source] + trans_len); nft.delta.add(inner_src, symbol, target); pred_map[target].insert(inner_src); } - void redirect_transitions(Nft& nft, const State old_target, const State new_target, const PredMap& pred_map) { + /** Duplicate transitions that lead to the old target state and map them to the new target state. + * + * @param nft The NFT in which the transitions will be duplicated. + * @param old_target The original target state of the transitions. + * @param new_target The new target state to which the transitions will be duplicated. + * @param pred_map A map that keeps track of predecessors for each target state. + */ + void duplicate_transitions(Nft& nft, const State old_target, const State new_target, const PredMap& pred_map) { if (old_target == new_target) { return; } auto it = pred_map.find(old_target); @@ -189,14 +257,14 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& std::unordered_set commited_states; // Calculate number of lhs/rhs transitions before/between/after sync levels - const std::vector lhs_between = get_partition_sizes(lhs_sync_levels, lhs_num_of_levels); - const std::vector rhs_between = get_partition_sizes(rhs_sync_levels, rhs_num_of_levels); + const std::vector lhs_between = get_interval_sizes(lhs_sync_levels, lhs_num_of_levels); + const std::vector rhs_between = get_interval_sizes(rhs_sync_levels, rhs_num_of_levels); BoolVector lhs_is_sync_level = create_mask(lhs_sync_levels, lhs_num_of_levels); BoolVector rhs_is_sync_level = create_mask(rhs_sync_levels, rhs_num_of_levels); - const std::vector lhs_sync_levels_inv = invert(lhs_sync_levels, lhs_num_of_levels); - const std::vector rhs_sync_levels_inv = invert(rhs_sync_levels, rhs_num_of_levels); + const std::vector lhs_sync_levels_inv = get_entry_indices_vec(lhs_sync_levels, lhs_num_of_levels); + const std::vector rhs_sync_levels_inv = get_entry_indices_vec(rhs_sync_levels, rhs_num_of_levels); auto create_composition_state = [&](const State state_a, const State state_b, const Level level, const bool is_a_lhs = true, const State composition_state_to_add = Limits::max_state) { assert(composition_state_to_add == Limits::max_state || result.levels[composition_state_to_add] == level); @@ -312,7 +380,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const State found_state = get_state_from_product_storage(key.first, key.second); if (found_state != Limits::max_state) { // The mapping exists, redirect transition goig to orig_src to the existing state. - redirect_transitions(result, res_src, found_state, pred_map); + duplicate_transitions(result, res_src, found_state, pred_map); } else { assert(found_state == Limits::max_state); // The mapping does not exist. Update it. @@ -520,7 +588,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); if (found_state != Limits::max_state) { // The mapping exists, redirect transition goig to orig_src to the existing state. - redirect_transitions(result, local_res_src, found_state, pred_map); + duplicate_transitions(result, local_res_src, found_state, pred_map); } else { assert(found_state == Limits::max_state); // The mapping does not exist. Update it. From 5edb4cb37f42ea2d65434d0da9e852d15c0feff1 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 6 Aug 2025 16:44:13 +0200 Subject: [PATCH 16/65] wip --- src/nft/composition.cc | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 02addbf51..2a6ab192e 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -169,16 +169,20 @@ namespace { void duplicate_transitions(Nft& nft, const State old_target, const State new_target, const PredMap& pred_map) { if (old_target == new_target) { return; } - if (nft.initial.contains(old_target)) { - nft.initial.insert(new_target); - } + // NODE: The state new_target is a "product-like" state, so if old_target is a final state, + // than new_target should already be a final state. + // if (nft.final.contains(old_target)) { + // nft.final.insert(new_target); + // } + + // NODE: Do we need this? Isn't it the same as the previous check? + // Also, how does the state become initial? Does both states have to be initial? + // if (nft.initial.contains(old_target)) { + // nft.initial.insert(new_target); + // } auto it = pred_map.find(old_target); - if (it == pred_map.end()) { - // No predecessors for the old target, nothing to redirect. - return; - } - // assert(it != pred_map.end()); + assert(it != pred_map.end()); for (const State pred: it->second) { for (SymbolPost& symbol_post: nft.delta.mutable_state_post(pred)) { if (symbol_post.targets.contains(old_target)) { @@ -207,10 +211,10 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (project_out_sync_levels ? (2 * num_of_sync_levels) : num_of_sync_levels); // FAST STORAGE OF COMPOSITION STATES - // The largest matrix (product_matrix) of pairs of states we are brave enough to allocate. + // The largest matrix of pairs of states we are brave enough to allocate. // Let's say we are fine with allocating larget_composition * (about 8 Bytes) space. // So ten million cells is close to 100 MB. - // If the number is larger, then we do not allocate a matrix, but use a vector of unordered maps (composition_vec_map). + // If the number is larger, then we do not allocate a matrix, but use a vector of unordered maps. // The unordered_map seems to be about twice slower. constexpr size_t MAX_COMPOSITION_MATRIX_SIZE = 50'000'000; // constexpr size_t MAX_COMPOSITION_MATRIX_SIZE = 0; @@ -218,13 +222,13 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& assert(lhs_num_of_states < Limits::max_state); assert(rhs_num_of_states < Limits::max_state); - //Two variants of storage for the mapping from pairs of lhs and rhs states to composition state, for large and non-large products. + // Two variants of storage for the mapping from pairs of lhs and rhs states to composition state, for large and non-large products. MatrixProductStorage matrix_composition_storage; VecMapProductStorage vec_map_composition_storage; - InvertedProductStorage composition_to_lhs(lhs_num_of_states+rhs_num_of_states); - InvertedProductStorage composition_to_rhs(lhs_num_of_states+rhs_num_of_states); + InvertedProductStorage composition_to_lhs(lhs_num_of_states + rhs_num_of_states); + InvertedProductStorage composition_to_rhs(lhs_num_of_states + rhs_num_of_states); - //Initialize the storage, according to the number of possible state pairs. + // Initialize the storage, according to the number of possible state pairs. if (!larget_composition) matrix_composition_storage = MatrixProductStorage(lhs_num_of_states, std::vector(rhs_num_of_states, Limits::max_state)); else @@ -610,7 +614,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& insert_to_product_storage(lhs_tgt, rhs_tgt, local_res_src); worklist.push({local_res_src, lhs_tgt, rhs_tgt}); } else { - redirect_transitions(result, local_res_src, found_state, pred_map); + duplicate_transitions(result, local_res_src, found_state, pred_map); } } } From 97a6eb844ae69dd71f6d5aecd870a700f3567704 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sun, 10 Aug 2025 22:21:08 +0200 Subject: [PATCH 17/65] notes --- src/nft/composition.cc | 50 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 2a6ab192e..580dbb502 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -95,7 +95,7 @@ namespace { */ State add_transition(Nft& nft, const State source, const Symbol symbol, const size_t len, const JumpMode jump_mode, PredMap& pred_map) { if (len == 0) { return source; } - + assert(nft.levels[source] + len <= nft.num_of_levels); const Level target_level = static_cast((nft.levels[source] + len) % nft.num_of_levels); const State target = nft.add_state_with_level(target_level); @@ -167,6 +167,7 @@ namespace { * @param pred_map A map that keeps track of predecessors for each target state. */ void duplicate_transitions(Nft& nft, const State old_target, const State new_target, const PredMap& pred_map) { + assert(nft.levels[old_target] == nft.levels[new_target]); if (old_target == new_target) { return; } // NODE: The state new_target is a "product-like" state, so if old_target is a final state, @@ -191,8 +192,55 @@ namespace { } } } + + StateSet get_epsilon_on_sync_levels_succ(const Nft& nft, const State source, const mata::BoolVector& is_sync_level) { + StateSet succ; + std::queue worklist; + worklist.push(source); + while (!worklist.empty()) { + const State current = worklist.front(); + const Level current_level = nft.levels[current]; + worklist.pop(); + for (const SymbolPost& symbol_post: nft.delta[current]) { + if (is_sync_level[current_level] && symbol_post.symbol != EPSILON) { + continue; // Skip non-EPSILON transitions on sync levels. + } + for (const State target: symbol_post.targets) { + const Level target_level = nft.levels[target]; + // We suppose that there are no cycles between zero levels. + assert(target_level == 0 || current_level < target_level); + if (target_level == 0) { + succ.insert(target); + } else { + worklist.push(target); + } + } + } + } + + return succ; + } + + void make_waiting_cycle(Nft& nft, const State state, const JumpMode jump_mode, PredMap& pred_map) { + assert(nft.levels[state] == 0); + add_transition_with_target(nft, state, EPSILON, state, jump_mode, pred_map); + } } +// TODO +// Use dequeue with nonzero state pairs on the left and pairs on zero levels on the right. +// At the end ack if need to add a waiting cycle in lhs or rhs. +// Using get_epsilon_on_sync_levels_succ() to get successors that are going to be paired with the other. +// Potentialy there is no need to manualy do the nonwaiting path, maybe we dont even need the get_epsilonb_on_sync_level_succ function. +// Just create waiting loop and then try to do it again. Because pairs that does not lead anywere weare already visided, we will be foreced to go +// through the waiting loop. Although, there can be a problem with the memory storage. We can not reshape it during the computation. +// No, we can not use those two functions. We have to do the wait in place. Meaning that the function get_epsilon_on_sync_levels_succ() is goig to need +// an access to the memory storage and create the path. +// We need to calculate the last level to avoid usage of pred_map. + + + + namespace mata::nft { From 87f87c4731a027114606512b7580ba60ba211faa Mon Sep 17 00:00:00 2001 From: koniksedy Date: Mon, 11 Aug 2025 13:42:33 +0200 Subject: [PATCH 18/65] wip --- src/nft/composition.cc | 999 +++++++++++++++++++++++++---------------- 1 file changed, 607 insertions(+), 392 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 580dbb502..248d3fb70 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -4,6 +4,7 @@ // MATA headers #include "mata/nft/nft.hh" #include +#include using namespace mata::utils; @@ -22,8 +23,10 @@ namespace { /** * Create a mask from the given entries and universum size. * The mask will have `true` for each entry in @p entries and `false` for all other indices. + * * @param entries The entries to be set in the mask. * @param universum_size The size of the universum. + * * @return A BoolVector representing the mask. */ template @@ -42,6 +45,7 @@ namespace { * * @param entries The OrdVector containing the entries. * @param universum_size The size of the universum. + * * @return A vector of indices where each index corresponds to the position of the entry in @p entries. * If an entry is not present, its index will be set to `std::numeric_limits::max()`. */ @@ -56,12 +60,25 @@ namespace { return entry_indices_vec; } + Level get_last_useful_level(const Nft& nft, const mata::BoolVector& is_sync_level) { + assert(std::any_of(is_sync_level.begin(), is_sync_level.end(), [](bool val) { return val; })); + Level last_useful_level = 0; + const size_t num_of_levels = is_sync_level.size(); + for (Level level = 0; level < num_of_levels; ++level) { + if (!is_sync_level[level]) { + last_useful_level = level; + } + } + return last_useful_level; + } + /** * Get the sizes of intervals between borders in the given OrdVector. The border on index 0 is before * the first element, the border on index 1 is after the first element, and so on. * * @param border_indices The OrdVector containing the indices of the borders. * @param universum_size The size of the universum. + * * @return A vector containing the sizes of intervals between the entries in @p border_indices. */ template @@ -91,9 +108,10 @@ namespace { * @param len The length of the transition. * @param jump_mode The mode of the jump. * @param pred_map A map to keep track of predecessors for each target state. + * * @return The target state of the transition. */ - State add_transition(Nft& nft, const State source, const Symbol symbol, const size_t len, const JumpMode jump_mode, PredMap& pred_map) { + State add_transition(Nft& nft, const State source, const Symbol symbol, const size_t len, const JumpMode jump_mode){//, PredMap& pred_map) { if (len == 0) { return source; } assert(nft.levels[source] + len <= nft.num_of_levels); @@ -102,7 +120,7 @@ namespace { if (len == 1 || jump_mode == JumpMode::RepeatSymbol) { nft.delta.add(source, symbol, target); - pred_map[target].insert(source); + // pred_map[target].insert(source); return target; } @@ -117,7 +135,7 @@ namespace { } assert(inner_level == nft.levels[source] + len); nft.delta.add(inner_src, symbol, target); - pred_map[target].insert(inner_src); + // pred_map[target].insert(inner_src); return target; } @@ -132,7 +150,7 @@ namespace { * @param jump_mode The mode of the jump transitions. * @param pred_map A map to keep track of predecessors for each target state. */ - void add_transition_with_target(Nft& nft, const State source, const Symbol symbol, const State target, const JumpMode jump_mode, PredMap& pred_map) { + void add_transition_with_target(Nft& nft, const State source, const Symbol symbol, const State target, const JumpMode jump_mode){//, PredMap& pred_map) { if (source == target) { return; } assert(nft.levels[source] < nft.levels[target] || nft.levels[target] == 0); @@ -141,7 +159,7 @@ namespace { if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { nft.delta.add(source, symbol, target); - pred_map[target].insert(source); + // pred_map[target].insert(source); return; } @@ -156,7 +174,7 @@ namespace { } assert(inner_level == nft.levels[source] + trans_len); nft.delta.add(inner_src, symbol, target); - pred_map[target].insert(inner_src); + // pred_map[target].insert(inner_src); } /** Duplicate transitions that lead to the old target state and map them to the new target state. @@ -221,10 +239,43 @@ namespace { return succ; } - void make_waiting_cycle(Nft& nft, const State state, const JumpMode jump_mode, PredMap& pred_map) { - assert(nft.levels[state] == 0); - add_transition_with_target(nft, state, EPSILON, state, jump_mode, pred_map); + StateSet get_epsilon_succ(const Nft& nft, const State source) { + if (nft.levels[source] == 0) { + return { source }; // If the source is a zero-level state, return it as the only successor. + } + + StateSet succ; + std::queue worklist; + worklist.push(source); + while (!worklist.empty()) { + const State current = worklist.front(); + const Level current_level = nft.levels[current]; + worklist.pop(); + + for (const SymbolPost& symbol_post: nft.delta[current]) { + if (symbol_post.symbol != EPSILON) { + continue; // Skip non-EPSILON transitions. + } + for (const State target: symbol_post.targets) { + const Level target_level = nft.levels[target]; + // We suppose that there are no cycles between zero levels. + assert(target_level == 0 || current_level < target_level); + if (target_level == 0) { + succ.insert(target); + } else { + worklist.push(target); + } + } + } + } + + return succ; } + + // void make_waiting_cycle(Nft& nft, const State state, const JumpMode jump_mode, PredMap& pred_map) { + // assert(nft.levels[state] == 0); + // add_transition_with_target(nft, state, EPSILON, state, jump_mode, pred_map); + // } } // TODO @@ -249,6 +300,7 @@ namespace mata::nft // There is a shitton of code duplication in this function. Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool project_out_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { assert(lhs_sync_levels.size() == rhs_sync_levels.size()); + assert(lhs_sync_levels.size() < lhs.num_of_levels && rhs_sync_levels.size() < rhs.num_of_levels); // Number of Levels and States const size_t num_of_sync_levels = lhs_sync_levels.size(); @@ -309,12 +361,10 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& composition_to_rhs[composition_state] = rhs_state; }; - // Initialize the result NFT, state map, and worklist + Nft result; result.num_of_levels = result_num_of_levels; - std::unordered_map pred_map; - std::stack> worklist; - std::unordered_set commited_states; + std::deque worklist; // Calculate number of lhs/rhs transitions before/between/after sync levels const std::vector lhs_between = get_interval_sizes(lhs_sync_levels, lhs_num_of_levels); @@ -326,6 +376,21 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const std::vector lhs_sync_levels_inv = get_entry_indices_vec(lhs_sync_levels, lhs_num_of_levels); const std::vector rhs_sync_levels_inv = get_entry_indices_vec(rhs_sync_levels, rhs_num_of_levels); + const Level lhs_last_useful_level = get_last_useful_level(lhs, lhs_is_sync_level); + const Level rhs_last_useful_level = get_last_useful_level(rhs, rhs_is_sync_level); + + /** + * @brief Create a new composition state for the given pair of states, if it does not already exist. + * + * @param state_a The first state (lhs or rhs). + * @param state_b The second state (rhs or lhs). + * @param level The level of the new composition state. + * @param is_a_lhs If true, state_a is from the lhs NFT, otherwise it is from the rhs NFT. + * @param composition_state_to_add If provided, this is the state to be added to the result NFT. + * If not provided, a new state will be created. + * + * @return The composition state for the given pair of states. + */ auto create_composition_state = [&](const State state_a, const State state_b, const Level level, const bool is_a_lhs = true, const State composition_state_to_add = Limits::max_state) { assert(composition_state_to_add == Limits::max_state || result.levels[composition_state_to_add] == level); @@ -346,373 +411,71 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // If the level is zero, we need to check for final states and add to the worklist. if (level == 0) { // Because the key pair was not found in the map, we are sure that the state was not in the worklist yet either. - worklist.push(key); + worklist.push_back(new_state); if ((is_a_lhs && lhs.final.contains(state_a) && rhs.final.contains(state_b)) || (!is_a_lhs && rhs.final.contains(state_a) && lhs.final.contains(state_b))) { result.final.insert(new_state); } + } else { + worklist.push_front(new_state); // Push to the front for non-zero levels. } return new_state; }; - // Function: maps epsilon transitions on the sync path - // TODO: Refactor - too many similarities in the code and with the code of map_combination_path. - auto map_epsilon_on_sync_path = [&](const State res_root, const State orig_root, const State wait_root, const bool is_orig_lhs) { - const Nft& orig_nft = is_orig_lhs ? lhs : rhs; - const OrdVector& sync_levels = is_orig_lhs ? lhs_sync_levels : rhs_sync_levels; - const std::vector& sync_levels_inv = is_orig_lhs ? lhs_sync_levels_inv : rhs_sync_levels_inv; - const std::vector& between = is_orig_lhs ? rhs_between : lhs_between; // Use rhs_between if we are in lhs NFT, and vice versa. - const BoolVector& is_sync_level = is_orig_lhs ? lhs_is_sync_level : rhs_is_sync_level; - const bool add_before = !is_orig_lhs; // On each section within sync levels, lhs transitions go begore rhs transitions. - - // Worklist contains pairs of (state in the result NFT, and state in the original lhs/rhs NFT). - std::stack> worklist; - worklist.emplace(res_root, orig_root); - StateSet visited; // It is not necessary is the NFT has a valid structure (each cycle contains a zero-level state). - visited.insert(orig_root); - pred_map.clear(); - - // TODO: Use more optimal date structure. - // TODO: I can maybe use the main memory storage (matrix), - // I'll just look up the pair (root, orig_state). Maybe. - std::unordered_map local_state_map; - auto get_state = [&](const State orig_state, const Level level) { - // Get the state from the map or add a new one. - auto it = local_state_map.find(orig_state); - if (it != local_state_map.end()) { - assert(result.levels[it->second] == level); - return it->second; - } - const State new_state = result.add_state_with_level(level); - local_state_map[orig_state] = new_state; - return new_state; - }; + auto simulate_waiting_cycle = [&](const Nft& other_nft, const State root_state, const Level last_useful_level, const State waiting_state, const std::vector inverse_sync, const State other_state, const std::vector &insert_cnt, const BoolVector& other_is_sync_level, const bool is_lhs_waiting) { + const bool insert_before = is_lhs_waiting; + std::stack> worklist; + worklist.push({ other_state, root_state }); + State last_useful_state = Limits::max_state; + SparseSet symbols_from_last_useful_state_to_map; while (!worklist.empty()) { - auto [res_src, orig_src] = worklist.top(); + auto [current_state, result_state] = worklist.top(); + const Level current_level = other_nft.levels[current_state]; + Level result_level = result.levels[result_state]; worklist.pop(); - // Add EPSILON transition for those in lhs BEFORE the first transition (we are in rhs). - if (res_src == res_root && add_before) { - res_src = add_transition(result, res_src, EPSILON, between[0], jump_mode, pred_map); - } - - if (is_sync_level[orig_nft.levels[orig_src]]) { - // We are at a sync level. We need to match only the epsilon transitions. - const auto epsilon_post_it = orig_nft.delta[orig_src].find(EPSILON); - if (epsilon_post_it == orig_nft.delta[orig_src].end()) { - continue; - } - const size_t sync_level_idx = sync_levels_inv[orig_nft.levels[orig_src]]; - // Add EPSILON transitions for those in rhs AFTER this last transition before the sync level (we are in lhs). - if (!add_before) { - res_src = add_transition(result, res_src, EPSILON, between[sync_level_idx], jump_mode, pred_map); - } - // Add EPSILON transitions for those in lhs BEFORE first transition after the sync level (we are in rhs). - if (add_before) { - res_src = add_transition(result, res_src, EPSILON, between[sync_level_idx + 1], jump_mode, pred_map); - } - - if (!project_out_sync_levels) { - res_src = add_transition(result, res_src, EPSILON, 1, jump_mode, pred_map); + if (insert_before && current_level == 0) { + result_state = add_transition(result, result_state, EPSILON, insert_cnt[0], jump_mode); + } else if (other_is_sync_level[current_level]) { + if (insert_before) { + // Insert after this sync and before the next section. + result_state = add_transition(result, result_state, EPSILON, insert_cnt[inverse_sync[current_level] + 1], jump_mode); + } else { + // Insert after this section preceding the sync level. + result_state = add_transition(result, result_state, EPSILON, insert_cnt[inverse_sync[current_level]], jump_mode); } + } - // Process targets of the epsilon transitions (this transition will be projected out). - for (const State orig_tgt: epsilon_post_it->targets) { - if (orig_nft.levels[orig_tgt] == 0) { - if (orig_src == orig_root && orig_nft.num_of_levels > 1) { - // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // It has been already handled in the main loop. - continue; - } - // We are connecting to a zero-level state. - // Add EPSILON transitions for those in rhs AFTER this last transition (we are in lhs). - if (!add_before && between[num_of_sync_levels] != 0) { - const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); - assert((result.levels[res_src] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); - add_transition_with_target(result, res_src, EPSILON, res_tgt, jump_mode, pred_map); - commited_states.insert(res_tgt); - } else { - // There is nothing to add. - // Try to find if there is already a mapping for this state. - const auto key = is_orig_lhs ? std::make_pair(orig_tgt, wait_root) : std::make_pair(wait_root, orig_tgt); - const State found_state = get_state_from_product_storage(key.first, key.second); - if (found_state != Limits::max_state) { - // The mapping exists, redirect transition goig to orig_src to the existing state. - duplicate_transitions(result, res_src, found_state, pred_map); - } else { - assert(found_state == Limits::max_state); - // The mapping does not exist. Update it. - create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs, res_src); - commited_states.insert(res_src); - } - } - } else { - // We are connecting to a non-zero-level state. - if (!visited.contains(orig_tgt)) { - // Let's continue the traversal. - // Project out the transition. Merge orig_scr and orig_tgt, while staying in res_src. - worklist.push({res_src, orig_tgt}); - visited.insert(orig_tgt); - } + for (const SymbolPost& symbol_post: other_nft.delta[current_state]) { + if (other_is_sync_level[current_level]) { + if (symbol_post.symbol != EPSILON) { + continue; // Skip non-EPSILON transitions on sync levels. } - } - } else { - // We are not at a sync level. We need just to copy the transitions. - const Level orig_src_level = orig_nft.levels[orig_src]; - for (const SymbolPost& symbol_post: orig_nft.delta[orig_src]) { - for (const State orig_tgt: symbol_post.targets) { - const Level orig_tgt_level = orig_nft.levels[orig_tgt]; - if (orig_tgt_level == 0) { - if (orig_src == orig_root && symbol_post.symbol == EPSILON && orig_nft.num_of_levels > 1) { - // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // It has been already handled in the main loop. - continue; - } - - // We are connecting to a zero-level state. - const Level level_diff = orig_nft.num_of_levels - orig_src_level; - if (!add_before && between[num_of_sync_levels] != 0) { - // We will add EPSILON transitions from rhs AFTER this last transition (we are in lhs). - assert(result.levels[res_src] + level_diff < result_num_of_levels); - const bool exist_res_tgt_inner = local_state_map.contains(orig_tgt); - const State res_tgt_inner = get_state(orig_tgt, result.levels[res_src] + level_diff); - // First process the transition with the symbol_post.symbol. - result.delta.add(res_src, symbol_post.symbol, res_tgt_inner); - if (!exist_res_tgt_inner) { - // Then add the EPSILON transitions. - const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); - assert((result.levels[res_tgt_inner] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); - add_transition_with_target(result, res_tgt_inner, EPSILON, res_tgt, jump_mode, pred_map); - commited_states.insert(res_tgt); + for (const State target: symbol_post.targets) { + const Level target_level = other_nft.levels[target]; + // We suppose that there are no cycles between zero levels. + assert(target_level == 0 || current_level < target_level); + if (other_is_sync_level[current_state]) { + worklist.push({ target, result_state }); + } else if (current_level == last_useful_level) { + size_t idx_to_start = (insert_before ? inverse_sync[current_level] + 1 : inverse_sync[current_level]); + const size_t levels_to_add = std::accumulate(insert_cnt.begin() + idx_to_start, insert_cnt.end(), 0); + if (levels_to_add == 0) { + for (const State other_target: get_epsilon_succ(other_nft, target)) { + add_transition_with_target(result, result_state, EPSILON, create_composition_state(other_target, root_state, 0, is_lhs_waiting), jump_mode); } } else { - assert(result.levels[res_src] + level_diff == result_num_of_levels); - const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); - result.delta.add(res_src, symbol_post.symbol, res_tgt); - commited_states.insert(res_tgt); - } - } else { - // We are connecting to a non-zero-level state. - // Just copy the transition. - const Level level_diff = orig_tgt_level - orig_src_level; - assert(result.levels[res_src] + level_diff <= result_num_of_levels); - const State tgt_res = get_state(orig_tgt, (result.levels[res_src] + level_diff) % result_num_of_levels); - result.delta.add(res_src, symbol_post.symbol, tgt_res); - pred_map[tgt_res].insert(res_src); - if (!visited.contains(orig_tgt)) { - // Not visited, add to the worklist - worklist.push({tgt_res, orig_tgt}); - visited.insert(orig_tgt); - } - } - } - } - } - } - }; - - // Maps result of a composition starting from zero-level states lhs_root and rhs_root. - // This mapping ends in next zero-level states. - auto map_combination_path = [&](const State res_root, const State lhs_root, const State rhs_root) { - pred_map.clear(); - std::stack> worklist; - worklist.push({res_root, lhs_root, rhs_root}); - - bool perform_lhs_wait = false; - bool perform_rhs_wait = false; - - while (!worklist.empty()) { - auto [res_src, lhs_src, rhs_src] = worklist.top(); - worklist.pop(); - - const Level res_src_level = result.levels[res_src]; - const Level lhs_src_level = lhs.levels[lhs_src]; - const Level rhs_src_level = rhs.levels[rhs_src]; - const bool lhs_in_target_and_rhs_remains = lhs_src_level == 0 && rhs_src_level != 0; - - // Go in lhs all the way down to the sync level. - if (!lhs_is_sync_level[lhs_src_level] && !lhs_in_target_and_rhs_remains && !lhs.delta[lhs_src].empty()) { - for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { - for (const State lhs_tgt: symbol_post.targets) { - const Level lhs_tgt_level = lhs.levels[lhs_tgt]; - if (symbol_post.symbol == EPSILON && lhs_src_level == 0 && lhs_tgt_level == 0 && lhs_num_of_levels > 1) { - // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // It has been already handled in the main loop. - continue; - } - - if (lhs_tgt_level == 0) { - const size_t trans_len = lhs_num_of_levels - lhs_src_level; - // We are connecting to a zero-level state. - if (rhs_between[num_of_sync_levels] == 0) { - // There is no transition in rhs. - const State res_tgt = create_composition_state(lhs_tgt, rhs_src, 0); - result.delta.add(res_src, symbol_post.symbol, res_tgt); - // There are no unprocessed synchronization transitions, so we can commit the state. - // TODO: is it really necessary? - commited_states.insert(res_tgt); - } else { - // There is a transition in rhs. - const bool visited = get_state_from_product_storage(lhs_tgt, rhs_src) != Limits::max_state; - const State res_tgt = create_composition_state(lhs_tgt, rhs_src, trans_len + res_src_level); - result.delta.add(res_src, symbol_post.symbol, res_tgt); - pred_map[res_tgt].insert(res_src); - if (!visited) { - // Not visited, add to the worklist - worklist.push({res_tgt, lhs_tgt, rhs_src}); - } + result_state, } - } else { - // We are connecting to a non-zero-level state. - const Level level_diff = lhs_tgt_level - lhs_src_level; - assert(res_src_level + level_diff <= result_num_of_levels); - bool visited = get_state_from_product_storage(lhs_tgt, rhs_src) != Limits::max_state; - const State res_tgt = create_composition_state(lhs_tgt, rhs_src, (res_src_level + level_diff) % result_num_of_levels); - result.delta.add(res_src, symbol_post.symbol, res_tgt); - pred_map[res_tgt].insert(res_src); - if (!visited) { - // Not visited, add to the worklist - worklist.push({res_tgt, lhs_tgt, rhs_src}); - } - } - } - } - continue; - } - // Go in rhs all the way down to the sync level. - if (!rhs_is_sync_level[rhs_src_level]) { - for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { - for (const State rhs_tgt: symbol_post.targets) { - const Level rhs_tgt_level = rhs.levels[rhs_tgt]; - if (symbol_post.symbol == EPSILON && rhs_src_level == 0 && rhs_tgt_level == 0 && rhs_num_of_levels > 1) { - // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // It has been already handled in the main loop. - continue; } - if (rhs_tgt_level == 0) { - // We are connecting to a zero-level state. - const State res_tgt = create_composition_state(lhs_src, rhs_tgt, 0); - result.delta.add(res_src, symbol_post.symbol, res_tgt); - commited_states.insert(res_tgt); - } else { - // We are connecting to a non-zero-level state. - const Level level_diff = rhs_tgt_level - rhs_src_level; - assert(res_src_level + level_diff <= result_num_of_levels); - const bool visited = get_state_from_product_storage(lhs_src, rhs_tgt) != Limits::max_state; - const State res_tgt = create_composition_state(lhs_src, rhs_tgt, (res_src_level + level_diff) % result_num_of_levels); - result.delta.add(res_src, symbol_post.symbol, res_tgt); - pred_map[res_tgt].insert(res_src); - if (!visited) { - // Not visited, add to the worklist - worklist.push({res_tgt, lhs_src, rhs_tgt}); - } - } } } - continue; } - // Everythink is on sync level. - const bool epsilon_in_lhs = lhs.delta[lhs_src].find(EPSILON) != lhs.delta[lhs_src].end(); - const bool epsilon_in_rhs = rhs.delta[rhs_src].find(EPSILON) != rhs.delta[rhs_src].end(); - perform_lhs_wait = perform_lhs_wait || (epsilon_in_rhs && !epsilon_in_lhs); - perform_rhs_wait = perform_rhs_wait || (epsilon_in_lhs && !epsilon_in_rhs); - - // Processes the intersection of transitions in lhs and rhs. - auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets, const Symbol symbol) { - State local_res_src = res_src; - if (!project_out_sync_levels && !lhs_targets.empty() && !rhs_targets.empty()) { - local_res_src = add_transition(result, local_res_src, symbol, 1, jump_mode, pred_map); - } - for (const State lhs_tgt: lhs_targets) { - const Level lhs_tgt_level = lhs.levels[lhs_tgt]; - if (symbol == EPSILON && lhs_src_level == 0 && lhs_tgt_level == 0 && lhs_num_of_levels > 1) { - // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // It has been already handled in the main loop. - continue; - } - for (const State rhs_tgt: rhs_targets) { - const Level rhs_tgt_level = rhs.levels[rhs_tgt]; - if (symbol == EPSILON && rhs_src_level == 0 && rhs_tgt_level == 0 && rhs_num_of_levels > 1) { - // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // It has been already handled in the main loop. - continue; - } - - if (lhs_tgt_level == 0 && rhs_tgt_level == 0) { - // We are connecting to a zero-level state. - const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); - if (found_state != Limits::max_state) { - // The mapping exists, redirect transition goig to orig_src to the existing state. - duplicate_transitions(result, local_res_src, found_state, pred_map); - } else { - assert(found_state == Limits::max_state); - // The mapping does not exist. Update it. - create_composition_state(lhs_tgt, rhs_tgt, 0, true, local_res_src); - commited_states.insert(local_res_src); - } - } else { - const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); - if (found_state == Limits::max_state) { - // We have not visited this pair yet. - insert_to_product_storage(lhs_tgt, rhs_tgt, local_res_src); - worklist.push({local_res_src, lhs_tgt, rhs_tgt}); - } else { - duplicate_transitions(result, local_res_src, found_state, pred_map); - } - } - } - } - }; - - // Process symbol intersection - mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); - mata::utils::push_back(sync_iterator, lhs.delta[lhs_src]); - mata::utils::push_back(sync_iterator, rhs.delta[rhs_src]); - while (sync_iterator.advance()) { - const std::vector& same_symbol_posts{ sync_iterator.get_current() }; - assert(same_symbol_posts.size() == 2); // One move per state in the pair. - - process_intersection( - same_symbol_posts[0]->targets, - same_symbol_posts[1]->targets, - same_symbol_posts[0]->symbol - ); - } - - // Process DONT_CARE symbol in lhs. - if (lhs.delta[lhs_src].find(DONT_CARE) != lhs.delta[lhs_src].end()) { - for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { - process_intersection( - lhs.delta[lhs_src].find(DONT_CARE)->targets, - symbol_post.targets, - symbol_post.symbol - ); - } - } - - // Process DONT_CARE symbol in rhs. - if (rhs.delta[rhs_src].find(DONT_CARE) != rhs.delta[rhs_src].end()) { - for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { - process_intersection( - symbol_post.targets, - rhs.delta[rhs_src].find(DONT_CARE)->targets, - symbol_post.symbol - ); - } - } - } - - // Perform waiting if necessary. - if (perform_lhs_wait) { - map_epsilon_on_sync_path(res_root, rhs_root, lhs_root, false); - } - if (perform_rhs_wait) { - map_epsilon_on_sync_path(res_root, lhs_root, rhs_root, true); } }; @@ -721,55 +484,507 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& for (const State rhs_root: rhs.initial) { // Get the root state in the result NFT const State res_root = create_composition_state(lhs_root, rhs_root, 0); - commited_states.insert(res_root); result.initial.insert(res_root); } } - // MAIN LOOP + // Main Loop + bool lhs_waiting_cycle = false; + bool rhs_waiting_cycle = false; + State last_zero_level_composition_state = Limits::max_state; while (!worklist.empty()) { - auto [orig_lhs, orig_rhs] = worklist.top(); - worklist.pop(); - - // Get the state in the result NFT - const State res_root = get_state_from_product_storage(orig_lhs, orig_rhs); - // TODO - is commited_states really necessary? - if (!commited_states.contains(res_root)) { - result.final.erase(res_root); - continue; - } - - // Process "long" EPSILON transitions leading from zero-level state to zero-level state. - auto epsilon_post_a = lhs.delta[orig_lhs].find(EPSILON); - if (epsilon_post_a != lhs.delta[orig_lhs].end()) { - for (const State target: epsilon_post_a->targets) { - if (lhs.levels[target] == 0) { - const State res_tgt = create_composition_state(target, orig_rhs, 0); - add_transition_with_target(result, res_root, EPSILON, res_tgt, jump_mode, pred_map); - // result.delta.add(res_root, EPSILON, res_tgt); - commited_states.insert(res_tgt); - } + const State composition_state = worklist.front(); + const Level composition_state_level = result.levels[composition_state]; + worklist.pop_front(); + const State lhs_state = composition_to_lhs[composition_state]; + const State rhs_state = composition_to_rhs[composition_state]; + const Level lhs_source_level = lhs.levels[lhs_state]; + const Level rhs_source_level = rhs.levels[rhs_state]; + + // Because non-zero levels are processed first, we can reset wariables when we reach a zero level. + if (composition_state_level == 0) { + if (lhs_waiting_cycle) { + assert(last_zero_level_composition_state != Limits::max_state); + // TODO: Wait in lhs. } - } - auto epsilon_post_b = rhs.delta[orig_rhs].find(EPSILON); - if (epsilon_post_b != rhs.delta[orig_rhs].end()) { - for (const State target: epsilon_post_b->targets) { - if (rhs.levels[target] == 0) { - const State res_tgt = create_composition_state(orig_lhs, target, 0); - add_transition_with_target(result, res_root, EPSILON, res_tgt, jump_mode, pred_map); - // result.delta.add(res_root, EPSILON, res_tgt); - commited_states.insert(res_tgt); - } + if (rhs_waiting_cycle) { + assert(last_zero_level_composition_state != Limits::max_state); + // TODO: Wait in rhs. } + lhs_waiting_cycle = false; + rhs_waiting_cycle = false; + last_zero_level_composition_state = composition_state; } - // Map path created by the composition of lhs and rhs NFTs. - // Each iteration starts in zero-level states orig_lhs and orig_rhs - // and ends in next zero-level states. - map_combination_path(res_root, orig_lhs, orig_rhs); + // TODO: body } - return result.trim(); + + + + // // Initialize the result NFT, state map, and worklist + // Nft result; + // result.num_of_levels = result_num_of_levels; + // std::unordered_map pred_map; + // std::stack> worklist; + // std::unordered_set commited_states; + + // // Calculate number of lhs/rhs transitions before/between/after sync levels + // const std::vector lhs_between = get_interval_sizes(lhs_sync_levels, lhs_num_of_levels); + // const std::vector rhs_between = get_interval_sizes(rhs_sync_levels, rhs_num_of_levels); + + // BoolVector lhs_is_sync_level = create_mask(lhs_sync_levels, lhs_num_of_levels); + // BoolVector rhs_is_sync_level = create_mask(rhs_sync_levels, rhs_num_of_levels); + + // const std::vector lhs_sync_levels_inv = get_entry_indices_vec(lhs_sync_levels, lhs_num_of_levels); + // const std::vector rhs_sync_levels_inv = get_entry_indices_vec(rhs_sync_levels, rhs_num_of_levels); + + // auto create_composition_state = [&](const State state_a, const State state_b, const Level level, const bool is_a_lhs = true, const State composition_state_to_add = Limits::max_state) { + // assert(composition_state_to_add == Limits::max_state || result.levels[composition_state_to_add] == level); + + // // Try to find the entry in the state map. + // const auto key = is_a_lhs ? std::make_pair(state_a, state_b) : std::make_pair(state_b, state_a); + // const State lhs_state = key.first; + // const State rhs_state = key.second; + // const State found_state = get_state_from_product_storage(lhs_state, rhs_state); + // if (found_state != Limits::max_state) { + // assert(found_state == composition_state_to_add || composition_state_to_add == Limits::max_state); + // return found_state; + // } + + // // If not found, add a new state to the result NFT. + // State new_state = (composition_state_to_add != Limits::max_state) ? composition_state_to_add : result.add_state_with_level(level); + // insert_to_product_storage(lhs_state, rhs_state, new_state); + + // // If the level is zero, we need to check for final states and add to the worklist. + // if (level == 0) { + // // Because the key pair was not found in the map, we are sure that the state was not in the worklist yet either. + // worklist.push(key); + // if ((is_a_lhs && lhs.final.contains(state_a) && rhs.final.contains(state_b)) || + // (!is_a_lhs && rhs.final.contains(state_a) && lhs.final.contains(state_b))) { + // result.final.insert(new_state); + // } + // } + + // return new_state; + // }; + + // // Function: maps epsilon transitions on the sync path + // // TODO: Refactor - too many similarities in the code and with the code of map_combination_path. + // auto map_epsilon_on_sync_path = [&](const State res_root, const State orig_root, const State wait_root, const bool is_orig_lhs) { + // const Nft& orig_nft = is_orig_lhs ? lhs : rhs; + // const OrdVector& sync_levels = is_orig_lhs ? lhs_sync_levels : rhs_sync_levels; + // const std::vector& sync_levels_inv = is_orig_lhs ? lhs_sync_levels_inv : rhs_sync_levels_inv; + // const std::vector& between = is_orig_lhs ? rhs_between : lhs_between; // Use rhs_between if we are in lhs NFT, and vice versa. + // const BoolVector& is_sync_level = is_orig_lhs ? lhs_is_sync_level : rhs_is_sync_level; + // const bool add_before = !is_orig_lhs; // On each section within sync levels, lhs transitions go begore rhs transitions. + + // // Worklist contains pairs of (state in the result NFT, and state in the original lhs/rhs NFT). + // std::stack> worklist; + // worklist.emplace(res_root, orig_root); + // StateSet visited; // It is not necessary is the NFT has a valid structure (each cycle contains a zero-level state). + // visited.insert(orig_root); + // pred_map.clear(); + + // // TODO: Use more optimal date structure. + // // TODO: I can maybe use the main memory storage (matrix), + // // I'll just look up the pair (root, orig_state). Maybe. + // std::unordered_map local_state_map; + // auto get_state = [&](const State orig_state, const Level level) { + // // Get the state from the map or add a new one. + // auto it = local_state_map.find(orig_state); + // if (it != local_state_map.end()) { + // assert(result.levels[it->second] == level); + // return it->second; + // } + // const State new_state = result.add_state_with_level(level); + // local_state_map[orig_state] = new_state; + // return new_state; + // }; + + // while (!worklist.empty()) { + // auto [res_src, orig_src] = worklist.top(); + // worklist.pop(); + + // // Add EPSILON transition for those in lhs BEFORE the first transition (we are in rhs). + // if (res_src == res_root && add_before) { + // res_src = add_transition(result, res_src, EPSILON, between[0], jump_mode, pred_map); + // } + + // if (is_sync_level[orig_nft.levels[orig_src]]) { + // // We are at a sync level. We need to match only the epsilon transitions. + // const auto epsilon_post_it = orig_nft.delta[orig_src].find(EPSILON); + // if (epsilon_post_it == orig_nft.delta[orig_src].end()) { + // continue; + // } + // const size_t sync_level_idx = sync_levels_inv[orig_nft.levels[orig_src]]; + // // Add EPSILON transitions for those in rhs AFTER this last transition before the sync level (we are in lhs). + // if (!add_before) { + // res_src = add_transition(result, res_src, EPSILON, between[sync_level_idx], jump_mode, pred_map); + // } + // // Add EPSILON transitions for those in lhs BEFORE first transition after the sync level (we are in rhs). + // if (add_before) { + // res_src = add_transition(result, res_src, EPSILON, between[sync_level_idx + 1], jump_mode, pred_map); + // } + + // if (!project_out_sync_levels) { + // res_src = add_transition(result, res_src, EPSILON, 1, jump_mode, pred_map); + // } + + // // Process targets of the epsilon transitions (this transition will be projected out). + // for (const State orig_tgt: epsilon_post_it->targets) { + // if (orig_nft.levels[orig_tgt] == 0) { + // if (orig_src == orig_root && orig_nft.num_of_levels > 1) { + // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // // It has been already handled in the main loop. + // continue; + // } + // // We are connecting to a zero-level state. + // // Add EPSILON transitions for those in rhs AFTER this last transition (we are in lhs). + // if (!add_before && between[num_of_sync_levels] != 0) { + // const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); + // assert((result.levels[res_src] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); + // add_transition_with_target(result, res_src, EPSILON, res_tgt, jump_mode, pred_map); + // commited_states.insert(res_tgt); + // } else { + // // There is nothing to add. + // // Try to find if there is already a mapping for this state. + // const auto key = is_orig_lhs ? std::make_pair(orig_tgt, wait_root) : std::make_pair(wait_root, orig_tgt); + // const State found_state = get_state_from_product_storage(key.first, key.second); + // if (found_state != Limits::max_state) { + // // The mapping exists, redirect transition goig to orig_src to the existing state. + // duplicate_transitions(result, res_src, found_state, pred_map); + // } else { + // assert(found_state == Limits::max_state); + // // The mapping does not exist. Update it. + // create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs, res_src); + // commited_states.insert(res_src); + // } + // } + // } else { + // // We are connecting to a non-zero-level state. + // if (!visited.contains(orig_tgt)) { + // // Let's continue the traversal. + // // Project out the transition. Merge orig_scr and orig_tgt, while staying in res_src. + // worklist.push({res_src, orig_tgt}); + // visited.insert(orig_tgt); + // } + // } + // } + // } else { + // // We are not at a sync level. We need just to copy the transitions. + // const Level orig_src_level = orig_nft.levels[orig_src]; + // for (const SymbolPost& symbol_post: orig_nft.delta[orig_src]) { + // for (const State orig_tgt: symbol_post.targets) { + // const Level orig_tgt_level = orig_nft.levels[orig_tgt]; + // if (orig_tgt_level == 0) { + // if (orig_src == orig_root && symbol_post.symbol == EPSILON && orig_nft.num_of_levels > 1) { + // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // // It has been already handled in the main loop. + // continue; + // } + + // // We are connecting to a zero-level state. + // const Level level_diff = orig_nft.num_of_levels - orig_src_level; + // if (!add_before && between[num_of_sync_levels] != 0) { + // // We will add EPSILON transitions from rhs AFTER this last transition (we are in lhs). + // assert(result.levels[res_src] + level_diff < result_num_of_levels); + // const bool exist_res_tgt_inner = local_state_map.contains(orig_tgt); + // const State res_tgt_inner = get_state(orig_tgt, result.levels[res_src] + level_diff); + // // First process the transition with the symbol_post.symbol. + // result.delta.add(res_src, symbol_post.symbol, res_tgt_inner); + // if (!exist_res_tgt_inner) { + // // Then add the EPSILON transitions. + // const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); + // assert((result.levels[res_tgt_inner] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); + // add_transition_with_target(result, res_tgt_inner, EPSILON, res_tgt, jump_mode, pred_map); + // commited_states.insert(res_tgt); + // } + // } else { + // assert(result.levels[res_src] + level_diff == result_num_of_levels); + // const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); + // result.delta.add(res_src, symbol_post.symbol, res_tgt); + // commited_states.insert(res_tgt); + // } + // } else { + // // We are connecting to a non-zero-level state. + // // Just copy the transition. + // const Level level_diff = orig_tgt_level - orig_src_level; + // assert(result.levels[res_src] + level_diff <= result_num_of_levels); + // const State tgt_res = get_state(orig_tgt, (result.levels[res_src] + level_diff) % result_num_of_levels); + // result.delta.add(res_src, symbol_post.symbol, tgt_res); + // pred_map[tgt_res].insert(res_src); + // if (!visited.contains(orig_tgt)) { + // // Not visited, add to the worklist + // worklist.push({tgt_res, orig_tgt}); + // visited.insert(orig_tgt); + // } + // } + // } + // } + // } + // } + // }; + + // // Maps result of a composition starting from zero-level states lhs_root and rhs_root. + // // This mapping ends in next zero-level states. + // auto map_combination_path = [&](const State res_root, const State lhs_root, const State rhs_root) { + // pred_map.clear(); + // std::stack> worklist; + // worklist.push({res_root, lhs_root, rhs_root}); + + // bool perform_lhs_wait = false; + // bool perform_rhs_wait = false; + + // while (!worklist.empty()) { + // auto [res_src, lhs_src, rhs_src] = worklist.top(); + // worklist.pop(); + + // const Level res_src_level = result.levels[res_src]; + // const Level lhs_src_level = lhs.levels[lhs_src]; + // const Level rhs_src_level = rhs.levels[rhs_src]; + // const bool lhs_in_target_and_rhs_remains = lhs_src_level == 0 && rhs_src_level != 0; + + // // Go in lhs all the way down to the sync level. + // if (!lhs_is_sync_level[lhs_src_level] && !lhs_in_target_and_rhs_remains && !lhs.delta[lhs_src].empty()) { + // for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { + // for (const State lhs_tgt: symbol_post.targets) { + // const Level lhs_tgt_level = lhs.levels[lhs_tgt]; + // if (symbol_post.symbol == EPSILON && lhs_src_level == 0 && lhs_tgt_level == 0 && lhs_num_of_levels > 1) { + // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // // It has been already handled in the main loop. + // continue; + // } + + // if (lhs_tgt_level == 0) { + // const size_t trans_len = lhs_num_of_levels - lhs_src_level; + // // We are connecting to a zero-level state. + // if (rhs_between[num_of_sync_levels] == 0) { + // // There is no transition in rhs. + // const State res_tgt = create_composition_state(lhs_tgt, rhs_src, 0); + // result.delta.add(res_src, symbol_post.symbol, res_tgt); + // // There are no unprocessed synchronization transitions, so we can commit the state. + // // TODO: is it really necessary? + // commited_states.insert(res_tgt); + // } else { + // // There is a transition in rhs. + // const bool visited = get_state_from_product_storage(lhs_tgt, rhs_src) != Limits::max_state; + // const State res_tgt = create_composition_state(lhs_tgt, rhs_src, trans_len + res_src_level); + // result.delta.add(res_src, symbol_post.symbol, res_tgt); + // pred_map[res_tgt].insert(res_src); + // if (!visited) { + // // Not visited, add to the worklist + // worklist.push({res_tgt, lhs_tgt, rhs_src}); + // } + // } + // } else { + // // We are connecting to a non-zero-level state. + // const Level level_diff = lhs_tgt_level - lhs_src_level; + // assert(res_src_level + level_diff <= result_num_of_levels); + // bool visited = get_state_from_product_storage(lhs_tgt, rhs_src) != Limits::max_state; + // const State res_tgt = create_composition_state(lhs_tgt, rhs_src, (res_src_level + level_diff) % result_num_of_levels); + // result.delta.add(res_src, symbol_post.symbol, res_tgt); + // pred_map[res_tgt].insert(res_src); + // if (!visited) { + // // Not visited, add to the worklist + // worklist.push({res_tgt, lhs_tgt, rhs_src}); + // } + // } + // } + // } + // continue; + // } + + // // Go in rhs all the way down to the sync level. + // if (!rhs_is_sync_level[rhs_src_level]) { + // for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { + // for (const State rhs_tgt: symbol_post.targets) { + // const Level rhs_tgt_level = rhs.levels[rhs_tgt]; + // if (symbol_post.symbol == EPSILON && rhs_src_level == 0 && rhs_tgt_level == 0 && rhs_num_of_levels > 1) { + // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // // It has been already handled in the main loop. + // continue; + // } + + // if (rhs_tgt_level == 0) { + // // We are connecting to a zero-level state. + // const State res_tgt = create_composition_state(lhs_src, rhs_tgt, 0); + // result.delta.add(res_src, symbol_post.symbol, res_tgt); + // commited_states.insert(res_tgt); + // } else { + // // We are connecting to a non-zero-level state. + // const Level level_diff = rhs_tgt_level - rhs_src_level; + // assert(res_src_level + level_diff <= result_num_of_levels); + // const bool visited = get_state_from_product_storage(lhs_src, rhs_tgt) != Limits::max_state; + // const State res_tgt = create_composition_state(lhs_src, rhs_tgt, (res_src_level + level_diff) % result_num_of_levels); + // result.delta.add(res_src, symbol_post.symbol, res_tgt); + // pred_map[res_tgt].insert(res_src); + // if (!visited) { + // // Not visited, add to the worklist + // worklist.push({res_tgt, lhs_src, rhs_tgt}); + // } + // } + // } + // } + // continue; + // } + + // // Everythink is on sync level. + // const bool epsilon_in_lhs = lhs.delta[lhs_src].find(EPSILON) != lhs.delta[lhs_src].end(); + // const bool epsilon_in_rhs = rhs.delta[rhs_src].find(EPSILON) != rhs.delta[rhs_src].end(); + // perform_lhs_wait = perform_lhs_wait || (epsilon_in_rhs && !epsilon_in_lhs); + // perform_rhs_wait = perform_rhs_wait || (epsilon_in_lhs && !epsilon_in_rhs); + + // // Processes the intersection of transitions in lhs and rhs. + // auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets, const Symbol symbol) { + // State local_res_src = res_src; + // if (!project_out_sync_levels && !lhs_targets.empty() && !rhs_targets.empty()) { + // local_res_src = add_transition(result, local_res_src, symbol, 1, jump_mode, pred_map); + // } + // for (const State lhs_tgt: lhs_targets) { + // const Level lhs_tgt_level = lhs.levels[lhs_tgt]; + // if (symbol == EPSILON && lhs_src_level == 0 && lhs_tgt_level == 0 && lhs_num_of_levels > 1) { + // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // // It has been already handled in the main loop. + // continue; + // } + // for (const State rhs_tgt: rhs_targets) { + // const Level rhs_tgt_level = rhs.levels[rhs_tgt]; + // if (symbol == EPSILON && rhs_src_level == 0 && rhs_tgt_level == 0 && rhs_num_of_levels > 1) { + // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. + // // It has been already handled in the main loop. + // continue; + // } + + // if (lhs_tgt_level == 0 && rhs_tgt_level == 0) { + // // We are connecting to a zero-level state. + // const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); + // if (found_state != Limits::max_state) { + // // The mapping exists, redirect transition goig to orig_src to the existing state. + // duplicate_transitions(result, local_res_src, found_state, pred_map); + // } else { + // assert(found_state == Limits::max_state); + // // The mapping does not exist. Update it. + // create_composition_state(lhs_tgt, rhs_tgt, 0, true, local_res_src); + // commited_states.insert(local_res_src); + // } + // } else { + // const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); + // if (found_state == Limits::max_state) { + // // We have not visited this pair yet. + // insert_to_product_storage(lhs_tgt, rhs_tgt, local_res_src); + // worklist.push({local_res_src, lhs_tgt, rhs_tgt}); + // } else { + // duplicate_transitions(result, local_res_src, found_state, pred_map); + // } + // } + // } + // } + // }; + + // // Process symbol intersection + // mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); + // mata::utils::push_back(sync_iterator, lhs.delta[lhs_src]); + // mata::utils::push_back(sync_iterator, rhs.delta[rhs_src]); + // while (sync_iterator.advance()) { + // const std::vector& same_symbol_posts{ sync_iterator.get_current() }; + // assert(same_symbol_posts.size() == 2); // One move per state in the pair. + + // process_intersection( + // same_symbol_posts[0]->targets, + // same_symbol_posts[1]->targets, + // same_symbol_posts[0]->symbol + // ); + // } + + // // Process DONT_CARE symbol in lhs. + // if (lhs.delta[lhs_src].find(DONT_CARE) != lhs.delta[lhs_src].end()) { + // for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { + // process_intersection( + // lhs.delta[lhs_src].find(DONT_CARE)->targets, + // symbol_post.targets, + // symbol_post.symbol + // ); + // } + // } + + // // Process DONT_CARE symbol in rhs. + // if (rhs.delta[rhs_src].find(DONT_CARE) != rhs.delta[rhs_src].end()) { + // for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { + // process_intersection( + // symbol_post.targets, + // rhs.delta[rhs_src].find(DONT_CARE)->targets, + // symbol_post.symbol + // ); + // } + // } + // } + + // // Perform waiting if necessary. + // if (perform_lhs_wait) { + // map_epsilon_on_sync_path(res_root, rhs_root, lhs_root, false); + // } + // if (perform_rhs_wait) { + // map_epsilon_on_sync_path(res_root, lhs_root, rhs_root, true); + // } + // }; + + // // INITIALIZATION on a worklist (side effect of a create_composition_state) + // for (const State lhs_root: lhs.initial) { + // for (const State rhs_root: rhs.initial) { + // // Get the root state in the result NFT + // const State res_root = create_composition_state(lhs_root, rhs_root, 0); + // commited_states.insert(res_root); + // result.initial.insert(res_root); + // } + // } + + // // MAIN LOOP + // while (!worklist.empty()) { + // auto [orig_lhs, orig_rhs] = worklist.top(); + // worklist.pop(); + + // // Get the state in the result NFT + // const State res_root = get_state_from_product_storage(orig_lhs, orig_rhs); + // // TODO - is commited_states really necessary? + // if (!commited_states.contains(res_root)) { + // result.final.erase(res_root); + // continue; + // } + + // // Process "long" EPSILON transitions leading from zero-level state to zero-level state. + // auto epsilon_post_a = lhs.delta[orig_lhs].find(EPSILON); + // if (epsilon_post_a != lhs.delta[orig_lhs].end()) { + // for (const State target: epsilon_post_a->targets) { + // if (lhs.levels[target] == 0) { + // const State res_tgt = create_composition_state(target, orig_rhs, 0); + // add_transition_with_target(result, res_root, EPSILON, res_tgt, jump_mode, pred_map); + // // result.delta.add(res_root, EPSILON, res_tgt); + // commited_states.insert(res_tgt); + // } + // } + // } + // auto epsilon_post_b = rhs.delta[orig_rhs].find(EPSILON); + // if (epsilon_post_b != rhs.delta[orig_rhs].end()) { + // for (const State target: epsilon_post_b->targets) { + // if (rhs.levels[target] == 0) { + // const State res_tgt = create_composition_state(orig_lhs, target, 0); + // add_transition_with_target(result, res_root, EPSILON, res_tgt, jump_mode, pred_map); + // // result.delta.add(res_root, EPSILON, res_tgt); + // commited_states.insert(res_tgt); + // } + // } + // } + + // // Map path created by the composition of lhs and rhs NFTs. + // // Each iteration starts in zero-level states orig_lhs and orig_rhs + // // and ends in next zero-level states. + // map_combination_path(res_root, orig_lhs, orig_rhs); + // } + + // return result.trim(); + + } Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { From 186fb23bb957f61bdaf426b438262d075e654dbc Mon Sep 17 00:00:00 2001 From: koniksedy Date: Mon, 11 Aug 2025 17:47:19 +0200 Subject: [PATCH 19/65] new draft --- src/nft/composition.cc | 312 ++++++++++++++++++++++++++++++++--------- 1 file changed, 248 insertions(+), 64 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 248d3fb70..3a98a62f1 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -272,10 +272,15 @@ namespace { return succ; } - // void make_waiting_cycle(Nft& nft, const State state, const JumpMode jump_mode, PredMap& pred_map) { - // assert(nft.levels[state] == 0); - // add_transition_with_target(nft, state, EPSILON, state, jump_mode, pred_map); - // } + StateSet get_sync_succ(const Nft& lhs, const Nft& rhs, const State lhs_state, const State rhs_state, bool& is_lhs_waiting, bool& is_rhs_waiting) { + if (lhs.levels[lhs_state] == 0 && rhs.levels[rhs_state] == 0) { + return { lhs_state, rhs_state }; // If both states are zero-level, return them as successors. + } + assert(lhs.levels[lhs_state] != 0 && rhs.levels[rhs_state] != 0); + StateSet succ; + + } + } // TODO @@ -364,20 +369,20 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& Nft result; result.num_of_levels = result_num_of_levels; - std::deque worklist; + std::deque> worklist; // Calculate number of lhs/rhs transitions before/between/after sync levels - const std::vector lhs_between = get_interval_sizes(lhs_sync_levels, lhs_num_of_levels); - const std::vector rhs_between = get_interval_sizes(rhs_sync_levels, rhs_num_of_levels); + const std::vector lhs_interleave = get_interval_sizes(lhs_sync_levels, lhs_num_of_levels); + const std::vector rhs_interleave = get_interval_sizes(rhs_sync_levels, rhs_num_of_levels); - BoolVector lhs_is_sync_level = create_mask(lhs_sync_levels, lhs_num_of_levels); - BoolVector rhs_is_sync_level = create_mask(rhs_sync_levels, rhs_num_of_levels); + BoolVector lhs_is_sync_level_v = create_mask(lhs_sync_levels, lhs_num_of_levels); + BoolVector rhs_is_sync_level_v = create_mask(rhs_sync_levels, rhs_num_of_levels); const std::vector lhs_sync_levels_inv = get_entry_indices_vec(lhs_sync_levels, lhs_num_of_levels); const std::vector rhs_sync_levels_inv = get_entry_indices_vec(rhs_sync_levels, rhs_num_of_levels); - const Level lhs_last_useful_level = get_last_useful_level(lhs, lhs_is_sync_level); - const Level rhs_last_useful_level = get_last_useful_level(rhs, rhs_is_sync_level); + const Level lhs_last_nonsync_level = get_last_useful_level(lhs, lhs_is_sync_level_v); + const Level rhs_last_nonsync_level = get_last_useful_level(rhs, rhs_is_sync_level_v); /** * @brief Create a new composition state for the given pair of states, if it does not already exist. @@ -411,71 +416,106 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // If the level is zero, we need to check for final states and add to the worklist. if (level == 0) { // Because the key pair was not found in the map, we are sure that the state was not in the worklist yet either. - worklist.push_back(new_state); + worklist.push_back(key); if ((is_a_lhs && lhs.final.contains(state_a) && rhs.final.contains(state_b)) || (!is_a_lhs && rhs.final.contains(state_a) && lhs.final.contains(state_b))) { result.final.insert(new_state); } } else { - worklist.push_front(new_state); // Push to the front for non-zero levels. + worklist.push_front(key); // Push to the front for non-zero levels. } return new_state; }; - auto simulate_waiting_cycle = [&](const Nft& other_nft, const State root_state, const Level last_useful_level, const State waiting_state, const std::vector inverse_sync, const State other_state, const std::vector &insert_cnt, const BoolVector& other_is_sync_level, const bool is_lhs_waiting) { - const bool insert_before = is_lhs_waiting; + /** + * @brief Model the waiting in the waiting loop in one of transducers. + * + * @param run_nft The NFT that is being run (the one that is not waiting). + * @param wait_zero_state The zero state of the waiting NFT. + * @param run_zero_state The zero state of the running NFT. + * @param composition_zero_state The zero state of the composition NFT. + * @param run_is_sync_level The mask of sync levels in the running NFT. + * @param run_sync_levels_inv The inverted indices of sync levels in the running NFT. + * @param run_last_nonsync_level The last non-sync level in the running NFT. + * @param interleave The interleave vector that defines the order of levels in the composition NFT. + * @param is_wait_lhs If true, the waiting NFT is the left one (lhs), otherwise it is the right one (rhs). + */ + auto model_waiting = [&](const Nft& run_nft, + const State wait_zero_state, + const State run_zero_state, + const State composition_zero_state, + const BoolVector& run_is_sync_level, + const std::vector& run_sync_levels_inv, + const Level run_last_nonsync_level, + const std::vector& interleave, + const bool is_wait_lhs) { + + const bool interleave_before = is_wait_lhs; std::stack> worklist; - worklist.push({ other_state, root_state }); - State last_useful_state = Limits::max_state; - SparseSet symbols_from_last_useful_state_to_map; + worklist.push({ run_zero_state, composition_zero_state }); while (!worklist.empty()) { - auto [current_state, result_state] = worklist.top(); - const Level current_level = other_nft.levels[current_state]; - Level result_level = result.levels[result_state]; + auto [run_state, composition_state] = worklist.top(); + const Level run_state_level = run_nft.levels[run_state]; + const Level composition_state_level = result.levels[composition_state]; + const bool is_sync_level = run_is_sync_level[run_state_level]; worklist.pop(); - if (insert_before && current_level == 0) { - result_state = add_transition(result, result_state, EPSILON, insert_cnt[0], jump_mode); - } else if (other_is_sync_level[current_level]) { - if (insert_before) { - // Insert after this sync and before the next section. - result_state = add_transition(result, result_state, EPSILON, insert_cnt[inverse_sync[current_level] + 1], jump_mode); - } else { - // Insert after this section preceding the sync level. - result_state = add_transition(result, result_state, EPSILON, insert_cnt[inverse_sync[current_level]], jump_mode); - } + // Add epsilon transition for the transitions in the waiting NFT. + if (interleave_before && run_state_level == 0 && interleave.at(0) != 0) { + // Insert before the first section in the run NFT. + composition_state = add_transition(result, composition_state, EPSILON, interleave.at(0), jump_mode); + } else if (is_sync_level) { + // Insert before or right after the sync level in the run NFT. + const size_t idx_interleave = interleave_before ? run_sync_levels_inv[run_state_level] + 1 : run_sync_levels_inv[run_state_level]; + composition_state = add_transition(result, composition_state, EPSILON, interleave.at(idx_interleave), jump_mode); } - for (const SymbolPost& symbol_post: other_nft.delta[current_state]) { - if (other_is_sync_level[current_level]) { - if (symbol_post.symbol != EPSILON) { - continue; // Skip non-EPSILON transitions on sync levels. - } - for (const State target: symbol_post.targets) { - const Level target_level = other_nft.levels[target]; - // We suppose that there are no cycles between zero levels. - assert(target_level == 0 || current_level < target_level); - if (other_is_sync_level[current_state]) { - worklist.push({ target, result_state }); - } else if (current_level == last_useful_level) { - size_t idx_to_start = (insert_before ? inverse_sync[current_level] + 1 : inverse_sync[current_level]); - const size_t levels_to_add = std::accumulate(insert_cnt.begin() + idx_to_start, insert_cnt.end(), 0); - if (levels_to_add == 0) { - for (const State other_target: get_epsilon_succ(other_nft, target)) { - add_transition_with_target(result, result_state, EPSILON, create_composition_state(other_target, root_state, 0, is_lhs_waiting), jump_mode); - } - } else { - result_state, + // Process transitions from the run NFT. + for (const SymbolPost& run_symbol_post: run_nft.delta[run_state]) { + if (is_sync_level && run_symbol_post.symbol != EPSILON) { + continue; // Skip non-EPSILON transitions on sync levels. + } + for (const State run_target: run_symbol_post.targets) { + const Level run_target_level = run_nft.levels[run_target]; + const size_t transition_len = (run_target_level == 0 ? run_nft.num_of_levels : run_target_level) - run_state_level; + assert(run_target_level == 0 || run_state_level < run_target_level); + assert(transition_len > 0); + + const bool is_last_useful_transition = run_state_level <= run_last_nonsync_level && (run_target_level == 0 || run_target_level > run_last_nonsync_level); + if (is_last_useful_transition) { + // After this transiton, there can only be epsilon transitions from the wait NFT. + const size_t next_interleave_level_idx = (run_target_level == 0 ? (interleave.size() - 1) : run_sync_levels_inv[run_target_level]) + (interleave_before ? 1 : 0); + const size_t levels_to_add = std::accumulate(interleave.begin() + next_interleave_level_idx, interleave.end(), 0); + + if (levels_to_add == 0) { + // There are not epsilon transitions to add connect directly. + for (const State run_zero_target: get_epsilon_succ(run_nft, run_target)) { + const State composition_target_state = create_composition_state(wait_zero_state, run_zero_target, 0, is_wait_lhs); + add_transition_with_target(result, composition_state, run_symbol_post.symbol, composition_target_state, jump_mode); + } + } else { + // There are epsilon transitions to add, so we need to first create an inner state. + const State composition_inner_state = add_transition(result, composition_state, run_symbol_post.symbol, transition_len, jump_mode); + for (const State run_zero_target: get_epsilon_succ(run_nft, run_target)) { + const State composition_target_state = create_composition_state(wait_zero_state, run_zero_target, 0, is_wait_lhs); + add_transition_with_target(result, composition_inner_state, EPSILON, composition_target_state, jump_mode); } - } + } else if (is_sync_level) { + // This is a sync level transition, after which there will be at least one useful transition in the run NFT. + worklist.push({ run_target, composition_state }); + } else { + // This is a non-sync level transition after which there is more transitions in the run NFT. + assert(composition_state_level + transition_len < result.num_of_levels); + const State composition_target_state = result.add_state_with_level(composition_state_level + transition_len); + add_transition_with_target(result, composition_state, run_symbol_post.symbol, composition_target_state, jump_mode); + worklist.push({ run_target, composition_target_state }); } } } - } }; @@ -491,35 +531,179 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Main Loop bool lhs_waiting_cycle = false; bool rhs_waiting_cycle = false; + + auto get_sync_succ = [&](const State lhs_state, const State rhs_state) { + if (lhs.levels[lhs_state] == 0 && rhs.levels[rhs_state] == 0) { + return StateSet{ lhs_state, rhs_state }; // If both states are zero-level, return them as successors. + } + + StateSet succ; + std::queue> worklist; + worklist.push({ lhs_state, rhs_state }); + + while (!worklist.empty()) { + const auto [lhs_source, rhs_source] = worklist.front(); + worklist.pop(); + assert(lhs.levels[lhs_source] != 0 && rhs.levels[rhs_source] != 0); + + // Test if we need to wait later. + const bool lhs_state_post_contains_epsilon = lhs.delta[lhs_source].find(EPSILON) != lhs.delta[lhs_source].end(); + const bool rhs_state_post_contains_epsilon = rhs.delta[lhs_source].find(EPSILON) != rhs.delta[lhs_source].end(); + if (rhs_state_post_contains_epsilon && !lhs_state_post_contains_epsilon) { + lhs_waiting_cycle = true; + } + if (lhs_state_post_contains_epsilon && !rhs_state_post_contains_epsilon) { + rhs_waiting_cycle = true; + } + + mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); + mata::utils::push_back(sync_iterator, lhs.delta[lhs_source]); + mata::utils::push_back(sync_iterator, rhs.delta[rhs_source]); + while (sync_iterator.advance()) { + const std::vector& same_symbol_posts{ sync_iterator.get_current() }; + assert(same_symbol_posts.size() == 2); // One move per state in the pair. + for (const State lhs_target: same_symbol_posts[0]->targets) { + const Level lhs_target_level = lhs.levels[lhs_target]; + assert(lhs_target_level == 0 || lhs.levels[lhs_source] < lhs_target_level); + for (const State rhs_target: same_symbol_posts[1]->targets) { + const Level rhs_target_level = rhs.levels[rhs_target]; + assert(rhs_target_level == 0 || rhs.levels[rhs_source] < rhs_target_level); + assert((lhs_target_level != 0 && rhs_target_level != 0) || (lhs_target_level == 0 && rhs_target_level == 0)); + if (lhs_target_level == 0 && rhs_target_level == 0) { + succ.insert(create_composition_state(lhs_target, rhs_target, 0, true)); + } else { + worklist.push({ lhs_target, rhs_target }); + } + } + } + } + } + }; + + State last_zero_level_composition_state = Limits::max_state; while (!worklist.empty()) { - const State composition_state = worklist.front(); + const auto [lhs_state, rhs_state] = worklist.front(); + const State composition_state = get_state_from_product_storage(lhs_state, rhs_state); const Level composition_state_level = result.levels[composition_state]; + assert(composition_state != Limits::max_state); // The state should always be found in the product storage. worklist.pop_front(); - const State lhs_state = composition_to_lhs[composition_state]; - const State rhs_state = composition_to_rhs[composition_state]; - const Level lhs_source_level = lhs.levels[lhs_state]; - const Level rhs_source_level = rhs.levels[rhs_state]; - + + const StatePost& lhs_state_post = lhs.delta[lhs_state]; + const StatePost& rhs_state_post = rhs.delta[rhs_state]; + const Level lhs_state_level = lhs.levels[lhs_state]; + const Level rhs_state_level = rhs.levels[rhs_state]; + const bool lhs_is_sync_level = lhs_is_sync_level_v[lhs_state_level]; + const bool rhs_is_sync_level = rhs_is_sync_level_v[rhs_state_level]; + const bool lhs_after_last_nonsync_level = lhs_state_level > lhs_last_nonsync_level; + const bool rhs_after_last_nonsync_level = rhs_state_level > rhs_last_nonsync_level; + + // TODO: Move somewhere else. // Because non-zero levels are processed first, we can reset wariables when we reach a zero level. if (composition_state_level == 0) { if (lhs_waiting_cycle) { assert(last_zero_level_composition_state != Limits::max_state); - // TODO: Wait in lhs. + const State lhs_wait_state = composition_to_lhs[last_zero_level_composition_state]; + const State rhs_run_state = composition_to_rhs[last_zero_level_composition_state]; + model_waiting(rhs, lhs_wait_state, rhs_run_state, composition_state, rhs_is_sync_level_v, rhs_sync_levels_inv, rhs_last_nonsync_level, lhs_interleave, true); } if (rhs_waiting_cycle) { assert(last_zero_level_composition_state != Limits::max_state); - // TODO: Wait in rhs. + const State lhs_run_state = composition_to_lhs[last_zero_level_composition_state]; + const State rhs_wait_state = composition_to_rhs[last_zero_level_composition_state]; + model_waiting(lhs, rhs_wait_state, lhs_run_state, composition_state, lhs_is_sync_level_v, lhs_sync_levels_inv, lhs_last_nonsync_level, rhs_interleave, false); } lhs_waiting_cycle = false; rhs_waiting_cycle = false; last_zero_level_composition_state = composition_state; } - // TODO: body - } - + + if (!lhs_is_sync_level) { + assert(!lhs_after_last_nonsync_level); + // We need to go deeper in the lhs NFT. + for (const SymbolPost& lhs_symbol_post: lhs_state_post) { + for (const State lhs_target: lhs_symbol_post.targets) { + const Level lhs_target_level = lhs.levels[lhs_target]; + const size_t transition_len = (lhs_target_level == 0 ? lhs.num_of_levels : lhs_target_level) - lhs_state_level; + assert(lhs_target_level == 0 || lhs_state_level < lhs_target_level); + assert(transition_len > 0); + + const bool is_last_useful_transition = lhs_state_level <= lhs_last_nonsync_level && (lhs_target_level == 0 || lhs_target_level > lhs_last_nonsync_level); + if (rhs_after_last_nonsync_level && is_last_useful_transition) { + // We have to be careful because there are only sync level in the rhs NFT. + // This is last meaningful transition after which we only check for synchronization and connect to those targets. + for (const State composition_target: get_sync_succ(lhs_target, rhs_state)) { + // We can not use the create_composition_state here, because it will add the state to the worklist. + // Instead, we will just add a transition to the composition NFT. + assert(composition_state_level + transition_len == result.num_of_levels); + add_transition_with_target(result, composition_state, lhs_symbol_post.symbol, composition_target, jump_mode); + } + } else { + // This is a non-sync level and also not the last useful transition. + // We will just copy it to the composition NFT. + assert(composition_state_level + transition_len < result.num_of_levels); + const State composition_target_state = create_composition_state(lhs_target, rhs_state, composition_state_level + transition_len, true); + add_transition_with_target(result, composition_state, lhs_symbol_post.symbol, composition_target_state, jump_mode); + } + } + } + } else if (!rhs_is_sync_level) { + // We need to go deeper in the rhs NFT. + assert(!rhs_after_last_nonsync_level); + for (const SymbolPost& rhs_symbol_post: rhs_state_post) { + for (const State rhs_target: rhs_symbol_post.targets) { + const Level rhs_target_level = rhs.levels[rhs_target]; + const size_t transition_len = (rhs_target_level == 0 ? rhs.num_of_levels : rhs_target_level) - rhs_state_level; + assert(rhs_target_level == 0 || rhs_state_level < rhs_target_level); + assert(transition_len > 0); + + const bool is_last_useful_transition = rhs_state_level <= rhs_last_nonsync_level && (rhs_target_level == 0 || rhs_target_level > rhs_last_nonsync_level); + if (lhs_after_last_nonsync_level && is_last_useful_transition) { + // We have to be careful because there are only sync level in the lhs NFT. + // This is last meaningful transition after which we only check for synchronization and connect to those targets + for (const State composition_target: get_sync_succ(lhs_state, rhs_target)) { + // We can not use the create_composition_state here, because it will add the state to the worklist. + // Instead, we will just add a transition to the composition NFT. + assert(composition_state_level + transition_len == result.num_of_levels); + add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, composition_target, jump_mode); + } + } else { + // This is a non-sync level and also not the last useful transition. + // We will just copy it to the composition NFT. + assert(composition_state_level + transition_len < result.num_of_levels); + const State composition_target_state = create_composition_state(lhs_state, rhs_target, composition_state_level + transition_len, false); + add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, composition_target_state, jump_mode); + } + } + } + } else { + // Both NFTs are on sync levels, we need to check for synchronization. + const auto lhs_state_post_end = lhs_state_post.end(); + const auto rhs_state_post_end = rhs_state_post.end(); + const bool lhs_state_post_contains_epsilon = lhs_state_post.find(EPSILON) != lhs_state_post_end; + const bool rhs_state_post_contains_epsilon = rhs_state_post.find(EPSILON) != rhs_state_post_end; + if (rhs_state_post_contains_epsilon && !lhs_state_post_contains_epsilon) { + lhs_waiting_cycle = true; + } + if (lhs_state_post_contains_epsilon && !rhs_state_post_contains_epsilon) { + rhs_waiting_cycle = true; + } + mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); + mata::utils::push_back(sync_iterator, lhs.delta[lhs_state]); + mata::utils::push_back(sync_iterator, rhs.delta[rhs_state]); + while (sync_iterator.advance()) { + const std::vector& same_symbol_posts{ sync_iterator.get_current() }; + assert(same_symbol_posts.size() == 2); // One move per state in the pair. + for (const State lhs_target: same_symbol_posts[0]->targets) { + for (const State rhs_target: same_symbol_posts[1]->targets) { + create_composition_state(lhs_target, rhs_target, composition_state_level, true, composition_state); + } + } + } + } + } // // Initialize the result NFT, state map, and worklist From 851cba2706071dc3ae03bd543432681e56fa6ae5 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Mon, 11 Aug 2025 17:59:39 +0200 Subject: [PATCH 20/65] polished draft --- src/nft/composition.cc | 582 ++--------------------------------------- 1 file changed, 28 insertions(+), 554 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 3a98a62f1..89df67cf1 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -120,7 +120,6 @@ namespace { if (len == 1 || jump_mode == JumpMode::RepeatSymbol) { nft.delta.add(source, symbol, target); - // pred_map[target].insert(source); return target; } @@ -130,12 +129,10 @@ namespace { assert(inner_level < nft.num_of_levels); const State inner_target = nft.add_state_with_level(inner_level); nft.delta.add(inner_src, symbol, inner_target); - // pred_map[inner_target].insert(inner_src); // Optimization: we do not need to track inner states in pred_map. inner_src = inner_target; } assert(inner_level == nft.levels[source] + len); nft.delta.add(inner_src, symbol, target); - // pred_map[target].insert(inner_src); return target; } @@ -159,7 +156,6 @@ namespace { if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { nft.delta.add(source, symbol, target); - // pred_map[target].insert(source); return; } @@ -169,46 +165,10 @@ namespace { assert(inner_level < nft.num_of_levels); const State inner_target = nft.add_state_with_level(inner_level); nft.delta.add(inner_src, symbol, inner_target); - // pred_map[inner_target].insert(inner_src); // Optimization: we do not need to track inner states in pred_map. inner_src = inner_target; } assert(inner_level == nft.levels[source] + trans_len); nft.delta.add(inner_src, symbol, target); - // pred_map[target].insert(inner_src); - } - - /** Duplicate transitions that lead to the old target state and map them to the new target state. - * - * @param nft The NFT in which the transitions will be duplicated. - * @param old_target The original target state of the transitions. - * @param new_target The new target state to which the transitions will be duplicated. - * @param pred_map A map that keeps track of predecessors for each target state. - */ - void duplicate_transitions(Nft& nft, const State old_target, const State new_target, const PredMap& pred_map) { - assert(nft.levels[old_target] == nft.levels[new_target]); - if (old_target == new_target) { return; } - - // NODE: The state new_target is a "product-like" state, so if old_target is a final state, - // than new_target should already be a final state. - // if (nft.final.contains(old_target)) { - // nft.final.insert(new_target); - // } - - // NODE: Do we need this? Isn't it the same as the previous check? - // Also, how does the state become initial? Does both states have to be initial? - // if (nft.initial.contains(old_target)) { - // nft.initial.insert(new_target); - // } - - auto it = pred_map.find(old_target); - assert(it != pred_map.end()); - for (const State pred: it->second) { - for (SymbolPost& symbol_post: nft.delta.mutable_state_post(pred)) { - if (symbol_post.targets.contains(old_target)) { - symbol_post.targets.insert(new_target); - } - } - } } StateSet get_epsilon_on_sync_levels_succ(const Nft& nft, const State source, const mata::BoolVector& is_sync_level) { @@ -270,39 +230,13 @@ namespace { } return succ; - } - - StateSet get_sync_succ(const Nft& lhs, const Nft& rhs, const State lhs_state, const State rhs_state, bool& is_lhs_waiting, bool& is_rhs_waiting) { - if (lhs.levels[lhs_state] == 0 && rhs.levels[rhs_state] == 0) { - return { lhs_state, rhs_state }; // If both states are zero-level, return them as successors. - } - assert(lhs.levels[lhs_state] != 0 && rhs.levels[rhs_state] != 0); - StateSet succ; - - } - + } } -// TODO -// Use dequeue with nonzero state pairs on the left and pairs on zero levels on the right. -// At the end ack if need to add a waiting cycle in lhs or rhs. -// Using get_epsilon_on_sync_levels_succ() to get successors that are going to be paired with the other. -// Potentialy there is no need to manualy do the nonwaiting path, maybe we dont even need the get_epsilonb_on_sync_level_succ function. -// Just create waiting loop and then try to do it again. Because pairs that does not lead anywere weare already visided, we will be foreced to go -// through the waiting loop. Although, there can be a problem with the memory storage. We can not reshape it during the computation. -// No, we can not use those two functions. We have to do the wait in place. Meaning that the function get_epsilon_on_sync_levels_succ() is goig to need -// an access to the memory storage and create the path. -// We need to calculate the last level to avoid usage of pred_map. - - - - - namespace mata::nft { -// TODO: Refactor this function to be more readable and maintainable. -// There is a shitton of code duplication in this function. + Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool project_out_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { assert(lhs_sync_levels.size() == rhs_sync_levels.size()); assert(lhs_sync_levels.size() < lhs.num_of_levels && rhs_sync_levels.size() < rhs.num_of_levels); @@ -531,6 +465,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // Main Loop bool lhs_waiting_cycle = false; bool rhs_waiting_cycle = false; + State last_zero_level_composition_state = Limits::max_state; auto get_sync_succ = [&](const State lhs_state, const State rhs_state) { if (lhs.levels[lhs_state] == 0 && rhs.levels[rhs_state] == 0) { @@ -580,8 +515,26 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } }; - - State last_zero_level_composition_state = Limits::max_state; + auto handle_waiting = [&] (const State new_composition_state) { + if (last_zero_level_composition_state != Limits::max_state) { + if (lhs_waiting_cycle) { + assert(last_zero_level_composition_state != Limits::max_state); + const State lhs_wait_state = composition_to_lhs[last_zero_level_composition_state]; + const State rhs_run_state = composition_to_rhs[last_zero_level_composition_state]; + model_waiting(rhs, lhs_wait_state, rhs_run_state, last_zero_level_composition_state, rhs_is_sync_level_v, rhs_sync_levels_inv, rhs_last_nonsync_level, lhs_interleave, true); + } + if (rhs_waiting_cycle) { + assert(last_zero_level_composition_state != Limits::max_state); + const State lhs_run_state = composition_to_lhs[last_zero_level_composition_state]; + const State rhs_wait_state = composition_to_rhs[last_zero_level_composition_state]; + model_waiting(lhs, rhs_wait_state, lhs_run_state, last_zero_level_composition_state, lhs_is_sync_level_v, lhs_sync_levels_inv, lhs_last_nonsync_level, rhs_interleave, false); + } + } + lhs_waiting_cycle = false; + rhs_waiting_cycle = false; + last_zero_level_composition_state = new_composition_state; + }; + while (!worklist.empty()) { const auto [lhs_state, rhs_state] = worklist.front(); const State composition_state = get_state_from_product_storage(lhs_state, rhs_state); @@ -597,28 +550,11 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& const bool rhs_is_sync_level = rhs_is_sync_level_v[rhs_state_level]; const bool lhs_after_last_nonsync_level = lhs_state_level > lhs_last_nonsync_level; const bool rhs_after_last_nonsync_level = rhs_state_level > rhs_last_nonsync_level; - - // TODO: Move somewhere else. - // Because non-zero levels are processed first, we can reset wariables when we reach a zero level. + if (composition_state_level == 0) { - if (lhs_waiting_cycle) { - assert(last_zero_level_composition_state != Limits::max_state); - const State lhs_wait_state = composition_to_lhs[last_zero_level_composition_state]; - const State rhs_run_state = composition_to_rhs[last_zero_level_composition_state]; - model_waiting(rhs, lhs_wait_state, rhs_run_state, composition_state, rhs_is_sync_level_v, rhs_sync_levels_inv, rhs_last_nonsync_level, lhs_interleave, true); - } - if (rhs_waiting_cycle) { - assert(last_zero_level_composition_state != Limits::max_state); - const State lhs_run_state = composition_to_lhs[last_zero_level_composition_state]; - const State rhs_wait_state = composition_to_rhs[last_zero_level_composition_state]; - model_waiting(lhs, rhs_wait_state, lhs_run_state, composition_state, lhs_is_sync_level_v, lhs_sync_levels_inv, lhs_last_nonsync_level, rhs_interleave, false); - } - lhs_waiting_cycle = false; - rhs_waiting_cycle = false; - last_zero_level_composition_state = composition_state; + handle_waiting(composition_state); } - if (!lhs_is_sync_level) { assert(!lhs_after_last_nonsync_level); // We need to go deeper in the lhs NFT. @@ -702,473 +638,11 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } } } - } - - // // Initialize the result NFT, state map, and worklist - // Nft result; - // result.num_of_levels = result_num_of_levels; - // std::unordered_map pred_map; - // std::stack> worklist; - // std::unordered_set commited_states; - - // // Calculate number of lhs/rhs transitions before/between/after sync levels - // const std::vector lhs_between = get_interval_sizes(lhs_sync_levels, lhs_num_of_levels); - // const std::vector rhs_between = get_interval_sizes(rhs_sync_levels, rhs_num_of_levels); - - // BoolVector lhs_is_sync_level = create_mask(lhs_sync_levels, lhs_num_of_levels); - // BoolVector rhs_is_sync_level = create_mask(rhs_sync_levels, rhs_num_of_levels); - - // const std::vector lhs_sync_levels_inv = get_entry_indices_vec(lhs_sync_levels, lhs_num_of_levels); - // const std::vector rhs_sync_levels_inv = get_entry_indices_vec(rhs_sync_levels, rhs_num_of_levels); - - // auto create_composition_state = [&](const State state_a, const State state_b, const Level level, const bool is_a_lhs = true, const State composition_state_to_add = Limits::max_state) { - // assert(composition_state_to_add == Limits::max_state || result.levels[composition_state_to_add] == level); - - // // Try to find the entry in the state map. - // const auto key = is_a_lhs ? std::make_pair(state_a, state_b) : std::make_pair(state_b, state_a); - // const State lhs_state = key.first; - // const State rhs_state = key.second; - // const State found_state = get_state_from_product_storage(lhs_state, rhs_state); - // if (found_state != Limits::max_state) { - // assert(found_state == composition_state_to_add || composition_state_to_add == Limits::max_state); - // return found_state; - // } - - // // If not found, add a new state to the result NFT. - // State new_state = (composition_state_to_add != Limits::max_state) ? composition_state_to_add : result.add_state_with_level(level); - // insert_to_product_storage(lhs_state, rhs_state, new_state); - - // // If the level is zero, we need to check for final states and add to the worklist. - // if (level == 0) { - // // Because the key pair was not found in the map, we are sure that the state was not in the worklist yet either. - // worklist.push(key); - // if ((is_a_lhs && lhs.final.contains(state_a) && rhs.final.contains(state_b)) || - // (!is_a_lhs && rhs.final.contains(state_a) && lhs.final.contains(state_b))) { - // result.final.insert(new_state); - // } - // } - - // return new_state; - // }; - - // // Function: maps epsilon transitions on the sync path - // // TODO: Refactor - too many similarities in the code and with the code of map_combination_path. - // auto map_epsilon_on_sync_path = [&](const State res_root, const State orig_root, const State wait_root, const bool is_orig_lhs) { - // const Nft& orig_nft = is_orig_lhs ? lhs : rhs; - // const OrdVector& sync_levels = is_orig_lhs ? lhs_sync_levels : rhs_sync_levels; - // const std::vector& sync_levels_inv = is_orig_lhs ? lhs_sync_levels_inv : rhs_sync_levels_inv; - // const std::vector& between = is_orig_lhs ? rhs_between : lhs_between; // Use rhs_between if we are in lhs NFT, and vice versa. - // const BoolVector& is_sync_level = is_orig_lhs ? lhs_is_sync_level : rhs_is_sync_level; - // const bool add_before = !is_orig_lhs; // On each section within sync levels, lhs transitions go begore rhs transitions. - - // // Worklist contains pairs of (state in the result NFT, and state in the original lhs/rhs NFT). - // std::stack> worklist; - // worklist.emplace(res_root, orig_root); - // StateSet visited; // It is not necessary is the NFT has a valid structure (each cycle contains a zero-level state). - // visited.insert(orig_root); - // pred_map.clear(); - - // // TODO: Use more optimal date structure. - // // TODO: I can maybe use the main memory storage (matrix), - // // I'll just look up the pair (root, orig_state). Maybe. - // std::unordered_map local_state_map; - // auto get_state = [&](const State orig_state, const Level level) { - // // Get the state from the map or add a new one. - // auto it = local_state_map.find(orig_state); - // if (it != local_state_map.end()) { - // assert(result.levels[it->second] == level); - // return it->second; - // } - // const State new_state = result.add_state_with_level(level); - // local_state_map[orig_state] = new_state; - // return new_state; - // }; - - // while (!worklist.empty()) { - // auto [res_src, orig_src] = worklist.top(); - // worklist.pop(); - - // // Add EPSILON transition for those in lhs BEFORE the first transition (we are in rhs). - // if (res_src == res_root && add_before) { - // res_src = add_transition(result, res_src, EPSILON, between[0], jump_mode, pred_map); - // } - - // if (is_sync_level[orig_nft.levels[orig_src]]) { - // // We are at a sync level. We need to match only the epsilon transitions. - // const auto epsilon_post_it = orig_nft.delta[orig_src].find(EPSILON); - // if (epsilon_post_it == orig_nft.delta[orig_src].end()) { - // continue; - // } - // const size_t sync_level_idx = sync_levels_inv[orig_nft.levels[orig_src]]; - // // Add EPSILON transitions for those in rhs AFTER this last transition before the sync level (we are in lhs). - // if (!add_before) { - // res_src = add_transition(result, res_src, EPSILON, between[sync_level_idx], jump_mode, pred_map); - // } - // // Add EPSILON transitions for those in lhs BEFORE first transition after the sync level (we are in rhs). - // if (add_before) { - // res_src = add_transition(result, res_src, EPSILON, between[sync_level_idx + 1], jump_mode, pred_map); - // } - - // if (!project_out_sync_levels) { - // res_src = add_transition(result, res_src, EPSILON, 1, jump_mode, pred_map); - // } - - // // Process targets of the epsilon transitions (this transition will be projected out). - // for (const State orig_tgt: epsilon_post_it->targets) { - // if (orig_nft.levels[orig_tgt] == 0) { - // if (orig_src == orig_root && orig_nft.num_of_levels > 1) { - // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // // It has been already handled in the main loop. - // continue; - // } - // // We are connecting to a zero-level state. - // // Add EPSILON transitions for those in rhs AFTER this last transition (we are in lhs). - // if (!add_before && between[num_of_sync_levels] != 0) { - // const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); - // assert((result.levels[res_src] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); - // add_transition_with_target(result, res_src, EPSILON, res_tgt, jump_mode, pred_map); - // commited_states.insert(res_tgt); - // } else { - // // There is nothing to add. - // // Try to find if there is already a mapping for this state. - // const auto key = is_orig_lhs ? std::make_pair(orig_tgt, wait_root) : std::make_pair(wait_root, orig_tgt); - // const State found_state = get_state_from_product_storage(key.first, key.second); - // if (found_state != Limits::max_state) { - // // The mapping exists, redirect transition goig to orig_src to the existing state. - // duplicate_transitions(result, res_src, found_state, pred_map); - // } else { - // assert(found_state == Limits::max_state); - // // The mapping does not exist. Update it. - // create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs, res_src); - // commited_states.insert(res_src); - // } - // } - // } else { - // // We are connecting to a non-zero-level state. - // if (!visited.contains(orig_tgt)) { - // // Let's continue the traversal. - // // Project out the transition. Merge orig_scr and orig_tgt, while staying in res_src. - // worklist.push({res_src, orig_tgt}); - // visited.insert(orig_tgt); - // } - // } - // } - // } else { - // // We are not at a sync level. We need just to copy the transitions. - // const Level orig_src_level = orig_nft.levels[orig_src]; - // for (const SymbolPost& symbol_post: orig_nft.delta[orig_src]) { - // for (const State orig_tgt: symbol_post.targets) { - // const Level orig_tgt_level = orig_nft.levels[orig_tgt]; - // if (orig_tgt_level == 0) { - // if (orig_src == orig_root && symbol_post.symbol == EPSILON && orig_nft.num_of_levels > 1) { - // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // // It has been already handled in the main loop. - // continue; - // } - - // // We are connecting to a zero-level state. - // const Level level_diff = orig_nft.num_of_levels - orig_src_level; - // if (!add_before && between[num_of_sync_levels] != 0) { - // // We will add EPSILON transitions from rhs AFTER this last transition (we are in lhs). - // assert(result.levels[res_src] + level_diff < result_num_of_levels); - // const bool exist_res_tgt_inner = local_state_map.contains(orig_tgt); - // const State res_tgt_inner = get_state(orig_tgt, result.levels[res_src] + level_diff); - // // First process the transition with the symbol_post.symbol. - // result.delta.add(res_src, symbol_post.symbol, res_tgt_inner); - // if (!exist_res_tgt_inner) { - // // Then add the EPSILON transitions. - // const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); - // assert((result.levels[res_tgt_inner] + between[num_of_sync_levels]) % result_num_of_levels == result.levels[res_tgt]); - // add_transition_with_target(result, res_tgt_inner, EPSILON, res_tgt, jump_mode, pred_map); - // commited_states.insert(res_tgt); - // } - // } else { - // assert(result.levels[res_src] + level_diff == result_num_of_levels); - // const State res_tgt = create_composition_state(orig_tgt, wait_root, 0, is_orig_lhs); - // result.delta.add(res_src, symbol_post.symbol, res_tgt); - // commited_states.insert(res_tgt); - // } - // } else { - // // We are connecting to a non-zero-level state. - // // Just copy the transition. - // const Level level_diff = orig_tgt_level - orig_src_level; - // assert(result.levels[res_src] + level_diff <= result_num_of_levels); - // const State tgt_res = get_state(orig_tgt, (result.levels[res_src] + level_diff) % result_num_of_levels); - // result.delta.add(res_src, symbol_post.symbol, tgt_res); - // pred_map[tgt_res].insert(res_src); - // if (!visited.contains(orig_tgt)) { - // // Not visited, add to the worklist - // worklist.push({tgt_res, orig_tgt}); - // visited.insert(orig_tgt); - // } - // } - // } - // } - // } - // } - // }; - - // // Maps result of a composition starting from zero-level states lhs_root and rhs_root. - // // This mapping ends in next zero-level states. - // auto map_combination_path = [&](const State res_root, const State lhs_root, const State rhs_root) { - // pred_map.clear(); - // std::stack> worklist; - // worklist.push({res_root, lhs_root, rhs_root}); - - // bool perform_lhs_wait = false; - // bool perform_rhs_wait = false; - - // while (!worklist.empty()) { - // auto [res_src, lhs_src, rhs_src] = worklist.top(); - // worklist.pop(); - - // const Level res_src_level = result.levels[res_src]; - // const Level lhs_src_level = lhs.levels[lhs_src]; - // const Level rhs_src_level = rhs.levels[rhs_src]; - // const bool lhs_in_target_and_rhs_remains = lhs_src_level == 0 && rhs_src_level != 0; - - // // Go in lhs all the way down to the sync level. - // if (!lhs_is_sync_level[lhs_src_level] && !lhs_in_target_and_rhs_remains && !lhs.delta[lhs_src].empty()) { - // for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { - // for (const State lhs_tgt: symbol_post.targets) { - // const Level lhs_tgt_level = lhs.levels[lhs_tgt]; - // if (symbol_post.symbol == EPSILON && lhs_src_level == 0 && lhs_tgt_level == 0 && lhs_num_of_levels > 1) { - // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // // It has been already handled in the main loop. - // continue; - // } - - // if (lhs_tgt_level == 0) { - // const size_t trans_len = lhs_num_of_levels - lhs_src_level; - // // We are connecting to a zero-level state. - // if (rhs_between[num_of_sync_levels] == 0) { - // // There is no transition in rhs. - // const State res_tgt = create_composition_state(lhs_tgt, rhs_src, 0); - // result.delta.add(res_src, symbol_post.symbol, res_tgt); - // // There are no unprocessed synchronization transitions, so we can commit the state. - // // TODO: is it really necessary? - // commited_states.insert(res_tgt); - // } else { - // // There is a transition in rhs. - // const bool visited = get_state_from_product_storage(lhs_tgt, rhs_src) != Limits::max_state; - // const State res_tgt = create_composition_state(lhs_tgt, rhs_src, trans_len + res_src_level); - // result.delta.add(res_src, symbol_post.symbol, res_tgt); - // pred_map[res_tgt].insert(res_src); - // if (!visited) { - // // Not visited, add to the worklist - // worklist.push({res_tgt, lhs_tgt, rhs_src}); - // } - // } - // } else { - // // We are connecting to a non-zero-level state. - // const Level level_diff = lhs_tgt_level - lhs_src_level; - // assert(res_src_level + level_diff <= result_num_of_levels); - // bool visited = get_state_from_product_storage(lhs_tgt, rhs_src) != Limits::max_state; - // const State res_tgt = create_composition_state(lhs_tgt, rhs_src, (res_src_level + level_diff) % result_num_of_levels); - // result.delta.add(res_src, symbol_post.symbol, res_tgt); - // pred_map[res_tgt].insert(res_src); - // if (!visited) { - // // Not visited, add to the worklist - // worklist.push({res_tgt, lhs_tgt, rhs_src}); - // } - // } - // } - // } - // continue; - // } - - // // Go in rhs all the way down to the sync level. - // if (!rhs_is_sync_level[rhs_src_level]) { - // for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { - // for (const State rhs_tgt: symbol_post.targets) { - // const Level rhs_tgt_level = rhs.levels[rhs_tgt]; - // if (symbol_post.symbol == EPSILON && rhs_src_level == 0 && rhs_tgt_level == 0 && rhs_num_of_levels > 1) { - // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // // It has been already handled in the main loop. - // continue; - // } - - // if (rhs_tgt_level == 0) { - // // We are connecting to a zero-level state. - // const State res_tgt = create_composition_state(lhs_src, rhs_tgt, 0); - // result.delta.add(res_src, symbol_post.symbol, res_tgt); - // commited_states.insert(res_tgt); - // } else { - // // We are connecting to a non-zero-level state. - // const Level level_diff = rhs_tgt_level - rhs_src_level; - // assert(res_src_level + level_diff <= result_num_of_levels); - // const bool visited = get_state_from_product_storage(lhs_src, rhs_tgt) != Limits::max_state; - // const State res_tgt = create_composition_state(lhs_src, rhs_tgt, (res_src_level + level_diff) % result_num_of_levels); - // result.delta.add(res_src, symbol_post.symbol, res_tgt); - // pred_map[res_tgt].insert(res_src); - // if (!visited) { - // // Not visited, add to the worklist - // worklist.push({res_tgt, lhs_src, rhs_tgt}); - // } - // } - // } - // } - // continue; - // } - - // // Everythink is on sync level. - // const bool epsilon_in_lhs = lhs.delta[lhs_src].find(EPSILON) != lhs.delta[lhs_src].end(); - // const bool epsilon_in_rhs = rhs.delta[rhs_src].find(EPSILON) != rhs.delta[rhs_src].end(); - // perform_lhs_wait = perform_lhs_wait || (epsilon_in_rhs && !epsilon_in_lhs); - // perform_rhs_wait = perform_rhs_wait || (epsilon_in_lhs && !epsilon_in_rhs); - - // // Processes the intersection of transitions in lhs and rhs. - // auto process_intersection = [&](const StateSet& lhs_targets, const StateSet& rhs_targets, const Symbol symbol) { - // State local_res_src = res_src; - // if (!project_out_sync_levels && !lhs_targets.empty() && !rhs_targets.empty()) { - // local_res_src = add_transition(result, local_res_src, symbol, 1, jump_mode, pred_map); - // } - // for (const State lhs_tgt: lhs_targets) { - // const Level lhs_tgt_level = lhs.levels[lhs_tgt]; - // if (symbol == EPSILON && lhs_src_level == 0 && lhs_tgt_level == 0 && lhs_num_of_levels > 1) { - // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // // It has been already handled in the main loop. - // continue; - // } - // for (const State rhs_tgt: rhs_targets) { - // const Level rhs_tgt_level = rhs.levels[rhs_tgt]; - // if (symbol == EPSILON && rhs_src_level == 0 && rhs_tgt_level == 0 && rhs_num_of_levels > 1) { - // // This is an NFA-like epsilon transition going from zero-level state to another zero-level state. - // // It has been already handled in the main loop. - // continue; - // } - - // if (lhs_tgt_level == 0 && rhs_tgt_level == 0) { - // // We are connecting to a zero-level state. - // const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); - // if (found_state != Limits::max_state) { - // // The mapping exists, redirect transition goig to orig_src to the existing state. - // duplicate_transitions(result, local_res_src, found_state, pred_map); - // } else { - // assert(found_state == Limits::max_state); - // // The mapping does not exist. Update it. - // create_composition_state(lhs_tgt, rhs_tgt, 0, true, local_res_src); - // commited_states.insert(local_res_src); - // } - // } else { - // const State found_state = get_state_from_product_storage(lhs_tgt, rhs_tgt); - // if (found_state == Limits::max_state) { - // // We have not visited this pair yet. - // insert_to_product_storage(lhs_tgt, rhs_tgt, local_res_src); - // worklist.push({local_res_src, lhs_tgt, rhs_tgt}); - // } else { - // duplicate_transitions(result, local_res_src, found_state, pred_map); - // } - // } - // } - // } - // }; - - // // Process symbol intersection - // mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); - // mata::utils::push_back(sync_iterator, lhs.delta[lhs_src]); - // mata::utils::push_back(sync_iterator, rhs.delta[rhs_src]); - // while (sync_iterator.advance()) { - // const std::vector& same_symbol_posts{ sync_iterator.get_current() }; - // assert(same_symbol_posts.size() == 2); // One move per state in the pair. - - // process_intersection( - // same_symbol_posts[0]->targets, - // same_symbol_posts[1]->targets, - // same_symbol_posts[0]->symbol - // ); - // } - - // // Process DONT_CARE symbol in lhs. - // if (lhs.delta[lhs_src].find(DONT_CARE) != lhs.delta[lhs_src].end()) { - // for (const SymbolPost& symbol_post: rhs.delta[rhs_src]) { - // process_intersection( - // lhs.delta[lhs_src].find(DONT_CARE)->targets, - // symbol_post.targets, - // symbol_post.symbol - // ); - // } - // } - - // // Process DONT_CARE symbol in rhs. - // if (rhs.delta[rhs_src].find(DONT_CARE) != rhs.delta[rhs_src].end()) { - // for (const SymbolPost& symbol_post: lhs.delta[lhs_src]) { - // process_intersection( - // symbol_post.targets, - // rhs.delta[rhs_src].find(DONT_CARE)->targets, - // symbol_post.symbol - // ); - // } - // } - // } - - // // Perform waiting if necessary. - // if (perform_lhs_wait) { - // map_epsilon_on_sync_path(res_root, rhs_root, lhs_root, false); - // } - // if (perform_rhs_wait) { - // map_epsilon_on_sync_path(res_root, lhs_root, rhs_root, true); - // } - // }; - - // // INITIALIZATION on a worklist (side effect of a create_composition_state) - // for (const State lhs_root: lhs.initial) { - // for (const State rhs_root: rhs.initial) { - // // Get the root state in the result NFT - // const State res_root = create_composition_state(lhs_root, rhs_root, 0); - // commited_states.insert(res_root); - // result.initial.insert(res_root); - // } - // } - - // // MAIN LOOP - // while (!worklist.empty()) { - // auto [orig_lhs, orig_rhs] = worklist.top(); - // worklist.pop(); - - // // Get the state in the result NFT - // const State res_root = get_state_from_product_storage(orig_lhs, orig_rhs); - // // TODO - is commited_states really necessary? - // if (!commited_states.contains(res_root)) { - // result.final.erase(res_root); - // continue; - // } - - // // Process "long" EPSILON transitions leading from zero-level state to zero-level state. - // auto epsilon_post_a = lhs.delta[orig_lhs].find(EPSILON); - // if (epsilon_post_a != lhs.delta[orig_lhs].end()) { - // for (const State target: epsilon_post_a->targets) { - // if (lhs.levels[target] == 0) { - // const State res_tgt = create_composition_state(target, orig_rhs, 0); - // add_transition_with_target(result, res_root, EPSILON, res_tgt, jump_mode, pred_map); - // // result.delta.add(res_root, EPSILON, res_tgt); - // commited_states.insert(res_tgt); - // } - // } - // } - // auto epsilon_post_b = rhs.delta[orig_rhs].find(EPSILON); - // if (epsilon_post_b != rhs.delta[orig_rhs].end()) { - // for (const State target: epsilon_post_b->targets) { - // if (rhs.levels[target] == 0) { - // const State res_tgt = create_composition_state(orig_lhs, target, 0); - // add_transition_with_target(result, res_root, EPSILON, res_tgt, jump_mode, pred_map); - // // result.delta.add(res_root, EPSILON, res_tgt); - // commited_states.insert(res_tgt); - // } - // } - // } - - // // Map path created by the composition of lhs and rhs NFTs. - // // Each iteration starts in zero-level states orig_lhs and orig_rhs - // // and ends in next zero-level states. - // map_combination_path(res_root, orig_lhs, orig_rhs); - // } - - // return result.trim(); - - + handle_waiting(Limits::max_state); // Handle any remaining waiting cycles after the main loop. + + return result.trim(); // Trim the result NFT to remove dead-end paths. } Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { From 5248bcb6ad79f923a566b75ff4996a5e4fa7f849 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Tue, 12 Aug 2025 17:49:12 +0200 Subject: [PATCH 21/65] put old composition back --- include/mata/nft/nft.hh | 7 +- src/nft/composition.cc | 619 +++++++++++++--------------------------- 2 files changed, 200 insertions(+), 426 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index a9f2cf95d..b4bddf494 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -774,12 +774,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, * is interpreted as a sequence repeating the same symbol or as a single instance of the symbol followed by a sequence of @c DONT_CARE. * @return A new NFT after the composition. */ -inline Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::NoJump) { - return compose(lhs, rhs, utils::OrdVector{ lhs_sync_level }, utils::OrdVector{ rhs_sync_level }, project_out_sync_levels, jump_mode); -} - -Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, - bool project_out_sync_levels = true, bool are_sync_levels_unwinded = false, JumpMode jump_mode = JumpMode::NoJump); +Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::NoJump); /** * @brief Concatenate two NFTs. diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 89df67cf1..eb90aaf1a 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -15,91 +15,105 @@ namespace { using namespace mata::nft; using PredMap = std::unordered_map; - using ProductMap = std::unordered_map,State>; - using MatrixProductStorage = std::vector>; - using VecMapProductStorage = std::vector>; - using InvertedProductStorage = std::vector; + enum class SynchronizationType : uint8_t { + NaN = 0, ///< Default initialization value + ONLY_ON_EPSILON = 1, ///< Epsilon symbol for synchronization + ONLY_ON_SYMBOL = 2, ///< Non-epsilon symbol for synchronization + ON_EPSILON_AND_SYMBOL = 3, ///< Both epsilon and non-epsilon + UNDER_COMPUTATION = 4 ///< Unknown synchronization type + }; - /** - * Create a mask from the given entries and universum size. - * The mask will have `true` for each entry in @p entries and `false` for all other indices. - * - * @param entries The entries to be set in the mask. - * @param universum_size The size of the universum. - * - * @return A BoolVector representing the mask. - */ - template - mata::BoolVector create_mask(const OrdVector& entries, const size_t universum_size) { - assert(entries.empty() || entries.back() < universum_size); - mata::BoolVector mask(universum_size, false); - for (const auto& entry : entries) { - mask[entry] = true; - } - return mask; + inline SynchronizationType operator|(SynchronizationType lhs, SynchronizationType rhs) { + return static_cast( + static_cast(lhs) | static_cast(rhs) + ); + } + + inline SynchronizationType& operator|=(SynchronizationType& lhs, SynchronizationType rhs) { + lhs = lhs | rhs; + return lhs; } /** - * Get a vector of indices for the entries in the given OrdVector. - * This is useful for quickly finding the position of an entry in the OrdVector. + * @brief For each state sets a type of synchronization that follows. * - * @param entries The OrdVector containing the entries. - * @param universum_size The size of the universum. - * - * @return A vector of indices where each index corresponds to the position of the entry in @p entries. - * If an entry is not present, its index will be set to `std::numeric_limits::max()`. + * @param nft The NFT to analyze. + * @param sync_level The level for which to compute the synchronization types. + * @return A vector of synchronization types for each state in the NFT. */ - template - std::vector get_entry_indices_vec(const OrdVector& entries, const size_t universum_size) { - assert(entries.empty() || entries.back() < universum_size); - std::vector entry_indices_vec(universum_size, std::numeric_limits::max()); - size_t idx = 0; - for (const auto& entry : entries) { - entry_indices_vec[entry] = idx++; - } - return entry_indices_vec; - } + std::vector get_synchronization_types(const Nft& nft, const size_t sync_level) { + const size_t num_of_states = nft.num_of_states(); + std::vector sync_types(num_of_states, SynchronizationType::NaN); - Level get_last_useful_level(const Nft& nft, const mata::BoolVector& is_sync_level) { - assert(std::any_of(is_sync_level.begin(), is_sync_level.end(), [](bool val) { return val; })); - Level last_useful_level = 0; - const size_t num_of_levels = is_sync_level.size(); - for (Level level = 0; level < num_of_levels; ++level) { - if (!is_sync_level[level]) { - last_useful_level = level; + for (State root = 0; root < num_of_states; ++root) { + if (nft.levels[root] != sync_level) { + continue; // Skip states not on the sync level. } - } - return last_useful_level; - } - /** - * Get the sizes of intervals between borders in the given OrdVector. The border on index 0 is before - * the first element, the border on index 1 is after the first element, and so on. - * - * @param border_indices The OrdVector containing the indices of the borders. - * @param universum_size The size of the universum. - * - * @return A vector containing the sizes of intervals between the entries in @p border_indices. - */ - template - std::vector get_interval_sizes(const OrdVector& border_indices, const size_t universum_size) { - assert(!border_indices.empty()); - const size_t num_of_intervals = border_indices.size() + 1; - std::vector sizes(num_of_intervals, 0); - auto border_indices_it = border_indices.cbegin(); - - sizes[0] = *border_indices_it; - const size_t last_interval_idx = num_of_intervals - 1; - for (size_t i = 1; i < last_interval_idx; ++i) { - const size_t prev_border = *border_indices_it; - ++border_indices_it; - sizes[i] = *border_indices_it - prev_border - 1; + std::stack stack; + stack.push(root); + while (!stack.empty()) { + const State state = stack.top(); + const Level state_level = nft.levels[state]; + const StatePost& state_post = nft.delta[state]; + SynchronizationType current_sync_type = sync_types[state]; + assert(current_sync_type == SynchronizationType::NaN || current_sync_type == SynchronizationType::UNDER_COMPUTATION); + stack.pop(); + + // If it has been visited, than by now we know that it has been already computed for its children. + if (current_sync_type == SynchronizationType::UNDER_COMPUTATION) { + current_sync_type = SynchronizationType::NaN; + for (const SymbolPost& symbol_post : state_post) { + for (const State target : symbol_post.targets) { + current_sync_type |= sync_types[target]; + } + } + sync_types[state] = current_sync_type; + continue; // We already visited its children. + } + + // If we are on the sync level, we can compute the synchronization type. + if (state_level == sync_level) { + if (state_post.find(EPSILON) != state_post.end()) { + current_sync_type = SynchronizationType::ONLY_ON_EPSILON; + if (state_post.size() > 1) { + current_sync_type |= SynchronizationType::ONLY_ON_SYMBOL; + } + } else if (!state_post.empty()) { + current_sync_type = SynchronizationType::ONLY_ON_SYMBOL; + } + sync_types[state] = current_sync_type; + continue; // No need to visit its children. + } + + // We need to visit its children. + sync_types[state] = SynchronizationType::UNDER_COMPUTATION; // Mark this state as under computation. + stack.push(state); // Push it back to the stack to compute it later. + for (const SymbolPost& symbol_post : state_post) { + for (const State target : symbol_post.targets) { + assert(nft.levels[target] > nft.levels[state]); + assert(sync_types[target] != SynchronizationType::UNDER_COMPUTATION); + if (sync_types[target] != SynchronizationType::NaN) { + current_sync_type |= sync_types[target]; + } else { + stack.push(target); // Push the target state to visit it later. + } + } + } + } } - sizes[num_of_intervals - 1] = universum_size - *border_indices_it - 1; - return sizes; + assert(std::all_of(sync_types.begin(), sync_types.end(), + [](SynchronizationType type) { return type != SynchronizationType::UNDER_COMPUTATION; })); + + return sync_types; } + using ProductMap = std::unordered_map,State>; + using MatrixProductStorage = std::vector>; + using VecMapProductStorage = std::vector>; + using InvertedProductStorage = std::vector; + /** Add a transition to the NFT from source state over symbol and length len to a new target state. * * @param nft The NFT to which the transition will be added. @@ -170,84 +184,21 @@ namespace { assert(inner_level == nft.levels[source] + trans_len); nft.delta.add(inner_src, symbol, target); } - - StateSet get_epsilon_on_sync_levels_succ(const Nft& nft, const State source, const mata::BoolVector& is_sync_level) { - StateSet succ; - std::queue worklist; - worklist.push(source); - while (!worklist.empty()) { - const State current = worklist.front(); - const Level current_level = nft.levels[current]; - worklist.pop(); - for (const SymbolPost& symbol_post: nft.delta[current]) { - if (is_sync_level[current_level] && symbol_post.symbol != EPSILON) { - continue; // Skip non-EPSILON transitions on sync levels. - } - for (const State target: symbol_post.targets) { - const Level target_level = nft.levels[target]; - // We suppose that there are no cycles between zero levels. - assert(target_level == 0 || current_level < target_level); - if (target_level == 0) { - succ.insert(target); - } else { - worklist.push(target); - } - } - } - } - - return succ; - } - - StateSet get_epsilon_succ(const Nft& nft, const State source) { - if (nft.levels[source] == 0) { - return { source }; // If the source is a zero-level state, return it as the only successor. - } - - StateSet succ; - std::queue worklist; - worklist.push(source); - while (!worklist.empty()) { - const State current = worklist.front(); - const Level current_level = nft.levels[current]; - worklist.pop(); - - for (const SymbolPost& symbol_post: nft.delta[current]) { - if (symbol_post.symbol != EPSILON) { - continue; // Skip non-EPSILON transitions. - } - for (const State target: symbol_post.targets) { - const Level target_level = nft.levels[target]; - // We suppose that there are no cycles between zero levels. - assert(target_level == 0 || current_level < target_level); - if (target_level == 0) { - succ.insert(target); - } else { - worklist.push(target); - } - } - } - } - - return succ; - } } namespace mata::nft { -Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, const bool project_out_sync_levels, const bool are_sync_levels_unwinded, const JumpMode jump_mode) { - assert(lhs_sync_levels.size() == rhs_sync_levels.size()); - assert(lhs_sync_levels.size() < lhs.num_of_levels && rhs_sync_levels.size() < rhs.num_of_levels); +Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, const bool project_out_sync_levels, const JumpMode jump_mode) { + assert(lhs_sync_level < lhs.num_of_levels && rhs_sync_level < rhs.num_of_levels); // Number of Levels and States - const size_t num_of_sync_levels = lhs_sync_levels.size(); const size_t lhs_num_of_levels = lhs.num_of_levels; const size_t rhs_num_of_levels = rhs.num_of_levels; const size_t lhs_num_of_states = lhs.num_of_states(); const size_t rhs_num_of_states = rhs.num_of_states(); - const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (project_out_sync_levels ? (2 * num_of_sync_levels) : num_of_sync_levels); + const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (project_out_sync_levels ? (2) : 1); // FAST STORAGE OF COMPOSITION STATES // The largest matrix of pairs of states we are brave enough to allocate. @@ -303,20 +254,7 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& Nft result; result.num_of_levels = result_num_of_levels; - std::deque> worklist; - - // Calculate number of lhs/rhs transitions before/between/after sync levels - const std::vector lhs_interleave = get_interval_sizes(lhs_sync_levels, lhs_num_of_levels); - const std::vector rhs_interleave = get_interval_sizes(rhs_sync_levels, rhs_num_of_levels); - - BoolVector lhs_is_sync_level_v = create_mask(lhs_sync_levels, lhs_num_of_levels); - BoolVector rhs_is_sync_level_v = create_mask(rhs_sync_levels, rhs_num_of_levels); - - const std::vector lhs_sync_levels_inv = get_entry_indices_vec(lhs_sync_levels, lhs_num_of_levels); - const std::vector rhs_sync_levels_inv = get_entry_indices_vec(rhs_sync_levels, rhs_num_of_levels); - - const Level lhs_last_nonsync_level = get_last_useful_level(lhs, lhs_is_sync_level_v); - const Level rhs_last_nonsync_level = get_last_useful_level(rhs, rhs_is_sync_level_v); + std::deque worklist; /** * @brief Create a new composition state for the given pair of states, if it does not already exist. @@ -350,108 +288,19 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& // If the level is zero, we need to check for final states and add to the worklist. if (level == 0) { // Because the key pair was not found in the map, we are sure that the state was not in the worklist yet either. - worklist.push_back(key); + worklist.push_back(new_state); if ((is_a_lhs && lhs.final.contains(state_a) && rhs.final.contains(state_b)) || (!is_a_lhs && rhs.final.contains(state_a) && lhs.final.contains(state_b))) { result.final.insert(new_state); } } else { - worklist.push_front(key); // Push to the front for non-zero levels. + worklist.push_front(new_state); // Push to the front for non-zero levels. } return new_state; }; - /** - * @brief Model the waiting in the waiting loop in one of transducers. - * - * @param run_nft The NFT that is being run (the one that is not waiting). - * @param wait_zero_state The zero state of the waiting NFT. - * @param run_zero_state The zero state of the running NFT. - * @param composition_zero_state The zero state of the composition NFT. - * @param run_is_sync_level The mask of sync levels in the running NFT. - * @param run_sync_levels_inv The inverted indices of sync levels in the running NFT. - * @param run_last_nonsync_level The last non-sync level in the running NFT. - * @param interleave The interleave vector that defines the order of levels in the composition NFT. - * @param is_wait_lhs If true, the waiting NFT is the left one (lhs), otherwise it is the right one (rhs). - */ - auto model_waiting = [&](const Nft& run_nft, - const State wait_zero_state, - const State run_zero_state, - const State composition_zero_state, - const BoolVector& run_is_sync_level, - const std::vector& run_sync_levels_inv, - const Level run_last_nonsync_level, - const std::vector& interleave, - const bool is_wait_lhs) { - - const bool interleave_before = is_wait_lhs; - - std::stack> worklist; - worklist.push({ run_zero_state, composition_zero_state }); - while (!worklist.empty()) { - auto [run_state, composition_state] = worklist.top(); - const Level run_state_level = run_nft.levels[run_state]; - const Level composition_state_level = result.levels[composition_state]; - const bool is_sync_level = run_is_sync_level[run_state_level]; - worklist.pop(); - - // Add epsilon transition for the transitions in the waiting NFT. - if (interleave_before && run_state_level == 0 && interleave.at(0) != 0) { - // Insert before the first section in the run NFT. - composition_state = add_transition(result, composition_state, EPSILON, interleave.at(0), jump_mode); - } else if (is_sync_level) { - // Insert before or right after the sync level in the run NFT. - const size_t idx_interleave = interleave_before ? run_sync_levels_inv[run_state_level] + 1 : run_sync_levels_inv[run_state_level]; - composition_state = add_transition(result, composition_state, EPSILON, interleave.at(idx_interleave), jump_mode); - } - // Process transitions from the run NFT. - for (const SymbolPost& run_symbol_post: run_nft.delta[run_state]) { - if (is_sync_level && run_symbol_post.symbol != EPSILON) { - continue; // Skip non-EPSILON transitions on sync levels. - } - for (const State run_target: run_symbol_post.targets) { - const Level run_target_level = run_nft.levels[run_target]; - const size_t transition_len = (run_target_level == 0 ? run_nft.num_of_levels : run_target_level) - run_state_level; - assert(run_target_level == 0 || run_state_level < run_target_level); - assert(transition_len > 0); - - const bool is_last_useful_transition = run_state_level <= run_last_nonsync_level && (run_target_level == 0 || run_target_level > run_last_nonsync_level); - if (is_last_useful_transition) { - // After this transiton, there can only be epsilon transitions from the wait NFT. - const size_t next_interleave_level_idx = (run_target_level == 0 ? (interleave.size() - 1) : run_sync_levels_inv[run_target_level]) + (interleave_before ? 1 : 0); - const size_t levels_to_add = std::accumulate(interleave.begin() + next_interleave_level_idx, interleave.end(), 0); - - if (levels_to_add == 0) { - // There are not epsilon transitions to add connect directly. - for (const State run_zero_target: get_epsilon_succ(run_nft, run_target)) { - const State composition_target_state = create_composition_state(wait_zero_state, run_zero_target, 0, is_wait_lhs); - add_transition_with_target(result, composition_state, run_symbol_post.symbol, composition_target_state, jump_mode); - } - } else { - // There are epsilon transitions to add, so we need to first create an inner state. - const State composition_inner_state = add_transition(result, composition_state, run_symbol_post.symbol, transition_len, jump_mode); - for (const State run_zero_target: get_epsilon_succ(run_nft, run_target)) { - const State composition_target_state = create_composition_state(wait_zero_state, run_zero_target, 0, is_wait_lhs); - add_transition_with_target(result, composition_inner_state, EPSILON, composition_target_state, jump_mode); - } - } - } else if (is_sync_level) { - // This is a sync level transition, after which there will be at least one useful transition in the run NFT. - worklist.push({ run_target, composition_state }); - - } else { - // This is a non-sync level transition after which there is more transitions in the run NFT. - assert(composition_state_level + transition_len < result.num_of_levels); - const State composition_target_state = result.add_state_with_level(composition_state_level + transition_len); - add_transition_with_target(result, composition_state, run_symbol_post.symbol, composition_target_state, jump_mode); - worklist.push({ run_target, composition_target_state }); - } - } - } - } - }; // INITIALIZATION on a worklist (side effect of a create_composition_state) for (const State lhs_root: lhs.initial) { @@ -462,201 +311,131 @@ Nft compose_fast(const Nft& lhs, const Nft& rhs, const utils::OrdVector& } } - // Main Loop - bool lhs_waiting_cycle = false; - bool rhs_waiting_cycle = false; - State last_zero_level_composition_state = Limits::max_state; - auto get_sync_succ = [&](const State lhs_state, const State rhs_state) { - if (lhs.levels[lhs_state] == 0 && rhs.levels[rhs_state] == 0) { - return StateSet{ lhs_state, rhs_state }; // If both states are zero-level, return them as successors. - } - StateSet succ; - std::queue> worklist; - worklist.push({ lhs_state, rhs_state }); + while (!worklist.empty()) { + } - while (!worklist.empty()) { - const auto [lhs_source, rhs_source] = worklist.front(); - worklist.pop(); - assert(lhs.levels[lhs_source] != 0 && rhs.levels[rhs_source] != 0); + return result.trim(); // Trim the result NFT to remove dead-end paths. +} - // Test if we need to wait later. - const bool lhs_state_post_contains_epsilon = lhs.delta[lhs_source].find(EPSILON) != lhs.delta[lhs_source].end(); - const bool rhs_state_post_contains_epsilon = rhs.delta[lhs_source].find(EPSILON) != rhs.delta[lhs_source].end(); - if (rhs_state_post_contains_epsilon && !lhs_state_post_contains_epsilon) { - lhs_waiting_cycle = true; - } - if (lhs_state_post_contains_epsilon && !rhs_state_post_contains_epsilon) { - rhs_waiting_cycle = true; - } +Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { + assert(!lhs_sync_levels.empty()); + assert(lhs_sync_levels.size() == rhs_sync_levels.size()); - mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); - mata::utils::push_back(sync_iterator, lhs.delta[lhs_source]); - mata::utils::push_back(sync_iterator, rhs.delta[rhs_source]); - while (sync_iterator.advance()) { - const std::vector& same_symbol_posts{ sync_iterator.get_current() }; - assert(same_symbol_posts.size() == 2); // One move per state in the pair. - for (const State lhs_target: same_symbol_posts[0]->targets) { - const Level lhs_target_level = lhs.levels[lhs_target]; - assert(lhs_target_level == 0 || lhs.levels[lhs_source] < lhs_target_level); - for (const State rhs_target: same_symbol_posts[1]->targets) { - const Level rhs_target_level = rhs.levels[rhs_target]; - assert(rhs_target_level == 0 || rhs.levels[rhs_source] < rhs_target_level); - assert((lhs_target_level != 0 && rhs_target_level != 0) || (lhs_target_level == 0 && rhs_target_level == 0)); - if (lhs_target_level == 0 && rhs_target_level == 0) { - succ.insert(create_composition_state(lhs_target, rhs_target, 0, true)); - } else { - worklist.push({ lhs_target, rhs_target }); - } - } - } + if (lhs_sync_levels.size() == 1 && rhs_sync_levels.size() == 1) { + // If we have only one synchronization level we can do it faster. + return compose(lhs, rhs, lhs_sync_levels.front(), lhs_sync_levels.front(), project_out_sync_levels, jump_mode); + } + + throw std::runtime_error("Noodler should not go here. Noodler should always use JumpMode::NoJump."); + + // Inserts loop into the given Nft for each state with level 0. + // The loop word is constructed using the EPSILON symbol for all levels, except for the levels + // where is_dcare_on_transition is true, in which case the DONT_CARE symbol is used. + auto insert_self_loops = [&](Nft &nft, const BoolVector &is_dcare_on_transition) { + Word loop_word(nft.num_of_levels, EPSILON); + for (size_t i{ 0 }; i < nft.num_of_levels; i++) { + if (is_dcare_on_transition[i]) { + loop_word[i] = DONT_CARE; } } - }; - auto handle_waiting = [&] (const State new_composition_state) { - if (last_zero_level_composition_state != Limits::max_state) { - if (lhs_waiting_cycle) { - assert(last_zero_level_composition_state != Limits::max_state); - const State lhs_wait_state = composition_to_lhs[last_zero_level_composition_state]; - const State rhs_run_state = composition_to_rhs[last_zero_level_composition_state]; - model_waiting(rhs, lhs_wait_state, rhs_run_state, last_zero_level_composition_state, rhs_is_sync_level_v, rhs_sync_levels_inv, rhs_last_nonsync_level, lhs_interleave, true); - } - if (rhs_waiting_cycle) { - assert(last_zero_level_composition_state != Limits::max_state); - const State lhs_run_state = composition_to_lhs[last_zero_level_composition_state]; - const State rhs_wait_state = composition_to_rhs[last_zero_level_composition_state]; - model_waiting(lhs, rhs_wait_state, lhs_run_state, last_zero_level_composition_state, lhs_is_sync_level_v, lhs_sync_levels_inv, lhs_last_nonsync_level, rhs_interleave, false); + size_t original_num_of_states = nft.num_of_states(); + for (State s{ 0 }; s < original_num_of_states; s++) { + if (nft.levels[s] == 0) { + nft.insert_word(s, loop_word, s); } } - lhs_waiting_cycle = false; - rhs_waiting_cycle = false; - last_zero_level_composition_state = new_composition_state; }; - - while (!worklist.empty()) { - const auto [lhs_state, rhs_state] = worklist.front(); - const State composition_state = get_state_from_product_storage(lhs_state, rhs_state); - const Level composition_state_level = result.levels[composition_state]; - assert(composition_state != Limits::max_state); // The state should always be found in the product storage. - worklist.pop_front(); - - const StatePost& lhs_state_post = lhs.delta[lhs_state]; - const StatePost& rhs_state_post = rhs.delta[rhs_state]; - const Level lhs_state_level = lhs.levels[lhs_state]; - const Level rhs_state_level = rhs.levels[rhs_state]; - const bool lhs_is_sync_level = lhs_is_sync_level_v[lhs_state_level]; - const bool rhs_is_sync_level = rhs_is_sync_level_v[rhs_state_level]; - const bool lhs_after_last_nonsync_level = lhs_state_level > lhs_last_nonsync_level; - const bool rhs_after_last_nonsync_level = rhs_state_level > rhs_last_nonsync_level; - - if (composition_state_level == 0) { - handle_waiting(composition_state); - } - if (!lhs_is_sync_level) { - assert(!lhs_after_last_nonsync_level); - // We need to go deeper in the lhs NFT. - for (const SymbolPost& lhs_symbol_post: lhs_state_post) { - for (const State lhs_target: lhs_symbol_post.targets) { - const Level lhs_target_level = lhs.levels[lhs_target]; - const size_t transition_len = (lhs_target_level == 0 ? lhs.num_of_levels : lhs_target_level) - lhs_state_level; - assert(lhs_target_level == 0 || lhs_state_level < lhs_target_level); - assert(transition_len > 0); - - const bool is_last_useful_transition = lhs_state_level <= lhs_last_nonsync_level && (lhs_target_level == 0 || lhs_target_level > lhs_last_nonsync_level); - if (rhs_after_last_nonsync_level && is_last_useful_transition) { - // We have to be careful because there are only sync level in the rhs NFT. - // This is last meaningful transition after which we only check for synchronization and connect to those targets. - for (const State composition_target: get_sync_succ(lhs_target, rhs_state)) { - // We can not use the create_composition_state here, because it will add the state to the worklist. - // Instead, we will just add a transition to the composition NFT. - assert(composition_state_level + transition_len == result.num_of_levels); - add_transition_with_target(result, composition_state, lhs_symbol_post.symbol, composition_target, jump_mode); - } - } else { - // This is a non-sync level and also not the last useful transition. - // We will just copy it to the composition NFT. - assert(composition_state_level + transition_len < result.num_of_levels); - const State composition_target_state = create_composition_state(lhs_target, rhs_state, composition_state_level + transition_len, true); - add_transition_with_target(result, composition_state, lhs_symbol_post.symbol, composition_target_state, jump_mode); - } - } - } - } else if (!rhs_is_sync_level) { - // We need to go deeper in the rhs NFT. - assert(!rhs_after_last_nonsync_level); - for (const SymbolPost& rhs_symbol_post: rhs_state_post) { - for (const State rhs_target: rhs_symbol_post.targets) { - const Level rhs_target_level = rhs.levels[rhs_target]; - const size_t transition_len = (rhs_target_level == 0 ? rhs.num_of_levels : rhs_target_level) - rhs_state_level; - assert(rhs_target_level == 0 || rhs_state_level < rhs_target_level); - assert(transition_len > 0); - - const bool is_last_useful_transition = rhs_state_level <= rhs_last_nonsync_level && (rhs_target_level == 0 || rhs_target_level > rhs_last_nonsync_level); - if (lhs_after_last_nonsync_level && is_last_useful_transition) { - // We have to be careful because there are only sync level in the lhs NFT. - // This is last meaningful transition after which we only check for synchronization and connect to those targets - for (const State composition_target: get_sync_succ(lhs_state, rhs_target)) { - // We can not use the create_composition_state here, because it will add the state to the worklist. - // Instead, we will just add a transition to the composition NFT. - assert(composition_state_level + transition_len == result.num_of_levels); - add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, composition_target, jump_mode); - } - } else { - // This is a non-sync level and also not the last useful transition. - // We will just copy it to the composition NFT. - assert(composition_state_level + transition_len < result.num_of_levels); - const State composition_target_state = create_composition_state(lhs_state, rhs_target, composition_state_level + transition_len, false); - add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, composition_target_state, jump_mode); - } - } - } - } else { - // Both NFTs are on sync levels, we need to check for synchronization. - const auto lhs_state_post_end = lhs_state_post.end(); - const auto rhs_state_post_end = rhs_state_post.end(); - const bool lhs_state_post_contains_epsilon = lhs_state_post.find(EPSILON) != lhs_state_post_end; - const bool rhs_state_post_contains_epsilon = rhs_state_post.find(EPSILON) != rhs_state_post_end; - if (rhs_state_post_contains_epsilon && !lhs_state_post_contains_epsilon) { - lhs_waiting_cycle = true; - } - if (lhs_state_post_contains_epsilon && !rhs_state_post_contains_epsilon) { - rhs_waiting_cycle = true; - } - mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); - mata::utils::push_back(sync_iterator, lhs.delta[lhs_state]); - mata::utils::push_back(sync_iterator, rhs.delta[rhs_state]); - while (sync_iterator.advance()) { - const std::vector& same_symbol_posts{ sync_iterator.get_current() }; - assert(same_symbol_posts.size() == 2); // One move per state in the pair. - for (const State lhs_target: same_symbol_posts[0]->targets) { - for (const State rhs_target: same_symbol_posts[1]->targets) { - create_composition_state(lhs_target, rhs_target, composition_state_level, true, composition_state); - } - } - } + // Calculate the number of non-synchronization levels in lhs and rhs before/after each/last synchronization level. + // Example: + // Lets suppose we have 2 synchnonization levels. + // The vector lhs_nonsync_levels_cnt = { 2, 0, 1 } indicates that + // - before the first synchronization level (i == 0) there are 2 non-synchronization levels + // - before the second synchronization level (i == 1) there are 0 non-synchronization levels + // - after the last synchronization level (i == |sync|) there is 1 non-synchronization level + std::vector lhs_nonsync_levels_cnt; + std::vector rhs_nonsync_levels_cnt; + const size_t num_of_sync_levels = lhs_sync_levels.size(); + lhs_nonsync_levels_cnt.reserve(num_of_sync_levels + 1); + rhs_nonsync_levels_cnt.reserve(num_of_sync_levels + 1); + auto lhs_sync_levels_it = lhs_sync_levels.cbegin(); + auto rhs_sync_levels_it = rhs_sync_levels.cbegin(); + // Before the first synchronization level (i == 0) + lhs_nonsync_levels_cnt.push_back(*lhs_sync_levels_it++); + rhs_nonsync_levels_cnt.push_back(*rhs_sync_levels_it++); + // Before each synchronization level (i < |sync|) + for (size_t i = 1; i < num_of_sync_levels; ++i) { + lhs_nonsync_levels_cnt.push_back(*lhs_sync_levels_it - *(lhs_sync_levels_it - 1) - 1); + rhs_nonsync_levels_cnt.push_back(*rhs_sync_levels_it - *(rhs_sync_levels_it - 1) - 1); + ++lhs_sync_levels_it; + ++rhs_sync_levels_it; + } + // After the last synchronization level (i == |sync|) + lhs_nonsync_levels_cnt.push_back(static_cast(lhs.num_of_levels) - *(lhs_sync_levels_it - 1) - 1); + rhs_nonsync_levels_cnt.push_back(static_cast(rhs.num_of_levels) - *(rhs_sync_levels_it - 1) - 1); + + // Construct a mask for new levels in lhs and rhs. + // For each synchronization block (non-synchronization levels up to a synchronization transition), + // first insert the non-synchronization levels from lhs, then from rhs, and finally the synchronization level itself. + // For the last block of non-synchronization levels after the last synchronization level, + // insert the non-synchronization levels from lhs first, followed by the levels from rhs. + // Example: + // LHS | RHS + // --------------------------------------------+--------------------------------------------- + // num_of_levels = 5 | num_of_levels = 4 + // sync_levels = { 2, 4 } | sync_levels = { 1, 2 } + // --------------------------------------------|--------------------------------------------- + // nonsync_levels_cnt = { 2, 1, 0 } | nonsync_levels_cnt = { 1, 0, 1 } + // new_levels_mask = { 0, 0, 1, 0, 0, 0, 1 } | new_levels_mask = { 1, 1, 0, 0, 1, 0, 0 } + auto lhs_nonsync_levels_cnt_it = lhs_nonsync_levels_cnt.cbegin(); + auto rhs_nonsync_levels_cnt_it = rhs_nonsync_levels_cnt.cbegin(); + BoolVector lhs_new_levels_mask; + BoolVector rhs_new_levels_mask; + OrdVector sync_levels_to_project_out; + Level level = 0; + const size_t nonsync_levels_cnt_size = lhs_nonsync_levels_cnt.size(); + for (size_t i = 0; i < nonsync_levels_cnt_size; ++i) { + // LHS goes first -> make space (insert new levels) in RHS + for (unsigned j = 0; j < *lhs_nonsync_levels_cnt_it; ++j, ++level) { + lhs_new_levels_mask.push_back(false); + rhs_new_levels_mask.push_back(true); + } + // RHS goes first -> make space (insert new levels) in LHS + for (unsigned j = 0; j < *rhs_nonsync_levels_cnt_it; ++j, ++level) { + lhs_new_levels_mask.push_back(true); + rhs_new_levels_mask.push_back(false); } + // The synchronization level goes last + if (i < num_of_sync_levels) { + sync_levels_to_project_out.push_back(level); + lhs_new_levels_mask.push_back(false); + rhs_new_levels_mask.push_back(false); + ++level; + } + ++lhs_nonsync_levels_cnt_it; + ++rhs_nonsync_levels_cnt_it; } - handle_waiting(Limits::max_state); // Handle any remaining waiting cycles after the main loop. - - return result.trim(); // Trim the result NFT to remove dead-end paths. -} + // Insert new levels into lhs and rhs. + Nft lhs_synced = insert_levels(lhs, lhs_new_levels_mask, jump_mode); + Nft rhs_synced = insert_levels(rhs, rhs_new_levels_mask, jump_mode); -Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { - assert(!lhs_sync_levels.empty()); - assert(lhs_sync_levels.size() == rhs_sync_levels.size()); + // Two auxiliary states (states from inserted loops) can not create a product state. + const State lhs_first_aux_state = lhs_synced.num_of_states(); + const State rhs_first_aux_state = rhs_synced.num_of_states(); - if (jump_mode == JumpMode::NoJump) { - // If we are not jumping, we can use the fast composition. - return compose_fast(lhs, rhs, lhs_sync_levels, rhs_sync_levels, project_out_sync_levels, true, jump_mode); - } + // Insert self-loops into lhs and rhs to ensure synchronization on epsilon transitions. + insert_self_loops(lhs_synced, lhs_new_levels_mask); + insert_self_loops(rhs_synced, rhs_new_levels_mask); - // TODO - add better unwinding - throw std::runtime_error("Noodler should not go here. Noodler should always use JumpMode::NoJump."); - return compose_fast(lhs.unwind_jumps({ DONT_CARE }, jump_mode), rhs.unwind_jumps({ DONT_CARE }, jump_mode), lhs_sync_levels, rhs_sync_levels, project_out_sync_levels, true, JumpMode::NoJump); + Nft result{ intersection(lhs_synced, rhs_synced, nullptr, jump_mode, lhs_first_aux_state, rhs_first_aux_state) }; + if (project_out_sync_levels) { + result = project_out(result, sync_levels_to_project_out, jump_mode); + } + return result; } } // mata::nft From 2f6a37349ffcd175f6e82a8af75cb0df30a13256 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Tue, 12 Aug 2025 18:42:45 +0200 Subject: [PATCH 22/65] new idea --- src/nft/composition.cc | 100 ++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 17 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index eb90aaf1a..95a51bb43 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -16,11 +16,11 @@ namespace { using PredMap = std::unordered_map; enum class SynchronizationType : uint8_t { - NaN = 0, ///< Default initialization value - ONLY_ON_EPSILON = 1, ///< Epsilon symbol for synchronization - ONLY_ON_SYMBOL = 2, ///< Non-epsilon symbol for synchronization + UNINITIALIZED = 0, ///< Default initialization value + ONLY_ON_SYMBOL = 1, ///< Epsilon symbol for synchronization + ONLY_ON_EPSILON = 2, ///< Non-epsilon symbol for synchronization ON_EPSILON_AND_SYMBOL = 3, ///< Both epsilon and non-epsilon - UNDER_COMPUTATION = 4 ///< Unknown synchronization type + UNDER_COMPUTATION = 4 ///< Unknown synchronization type }; inline SynchronizationType operator|(SynchronizationType lhs, SynchronizationType rhs) { @@ -34,6 +34,36 @@ namespace { return lhs; } + static const bool perform_synchronization[4][4] = { + /* * rhs_sync_type */ + /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ + /* =======================*========================================================================= */ + /* UNINITIALIZED */ { false, false, false, false }, + /* ONLY_ON_SYMBOL */ { false, true, false, true }, + /* ONLY_ON_EPSILON */ { false, false, true, true }, + /* ON_EPSILON_AND_SYMBOL */ { false, true, true, true } + }; + + static const bool perform_wait_on_lhs[4][4] = { + /* * rhs_sync_type */ + /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ + /* =======================*========================================================================= */ + /* UNINITIALIZED */ { false, false, false, false }, + /* ONLY_ON_SYMBOL */ { false, false, true, true }, + /* ONLY_ON_EPSILON */ { false, false, false, false }, + /* ON_EPSILON_AND_SYMBOL */ { false, false, true, true } + }; + + static const bool perform_wait_on_rhs[4][4] = { + /* * rhs_sync_type */ + /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ + /* =======================*========================================================================= */ + /* UNINITIALIZED */ { false, false, false, false }, + /* ONLY_ON_SYMBOL */ { false, false, false, false }, + /* ONLY_ON_EPSILON */ { false, true, false, true }, + /* ON_EPSILON_AND_SYMBOL */ { false, true, false, true } + }; + /** * @brief For each state sets a type of synchronization that follows. * @@ -43,7 +73,7 @@ namespace { */ std::vector get_synchronization_types(const Nft& nft, const size_t sync_level) { const size_t num_of_states = nft.num_of_states(); - std::vector sync_types(num_of_states, SynchronizationType::NaN); + std::vector sync_types(num_of_states, SynchronizationType::UNINITIALIZED); for (State root = 0; root < num_of_states; ++root) { if (nft.levels[root] != sync_level) { @@ -57,12 +87,12 @@ namespace { const Level state_level = nft.levels[state]; const StatePost& state_post = nft.delta[state]; SynchronizationType current_sync_type = sync_types[state]; - assert(current_sync_type == SynchronizationType::NaN || current_sync_type == SynchronizationType::UNDER_COMPUTATION); + assert(current_sync_type == SynchronizationType::UNINITIALIZED || current_sync_type == SynchronizationType::UNDER_COMPUTATION); stack.pop(); // If it has been visited, than by now we know that it has been already computed for its children. if (current_sync_type == SynchronizationType::UNDER_COMPUTATION) { - current_sync_type = SynchronizationType::NaN; + current_sync_type = SynchronizationType::UNINITIALIZED; for (const SymbolPost& symbol_post : state_post) { for (const State target : symbol_post.targets) { current_sync_type |= sync_types[target]; @@ -93,7 +123,7 @@ namespace { for (const State target : symbol_post.targets) { assert(nft.levels[target] > nft.levels[state]); assert(sync_types[target] != SynchronizationType::UNDER_COMPUTATION); - if (sync_types[target] != SynchronizationType::NaN) { + if (sync_types[target] != SynchronizationType::UNINITIALIZED) { current_sync_type |= sync_types[target]; } else { stack.push(target); // Push the target state to visit it later. @@ -199,6 +229,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const size_t lhs_num_of_states = lhs.num_of_states(); const size_t rhs_num_of_states = rhs.num_of_states(); const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (project_out_sync_levels ? (2) : 1); + assert(result_num_of_levels > 0); + + Nft result; + result.num_of_levels = result_num_of_levels; + std::deque worklist; // FAST STORAGE OF COMPOSITION STATES // The largest matrix of pairs of states we are brave enough to allocate. @@ -251,11 +286,6 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le composition_to_rhs[composition_state] = rhs_state; }; - - Nft result; - result.num_of_levels = result_num_of_levels; - std::deque worklist; - /** * @brief Create a new composition state for the given pair of states, if it does not already exist. * @@ -300,9 +330,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le return new_state; }; - - - // INITIALIZATION on a worklist (side effect of a create_composition_state) + // INITIALIZATION of a worklist (side effect of a create_composition_state) for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { // Get the root state in the result NFT @@ -311,9 +339,47 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } } - + const std::vector lhs_sync_types = get_synchronization_types(lhs, lhs_sync_level); + const std::vector rhs_sync_types = get_synchronization_types(rhs, rhs_sync_level); while (!worklist.empty()) { + const State composition_state = worklist.front(); + const State lhs_state = composition_to_lhs[composition_state]; + const State rhs_state = composition_to_rhs[composition_state]; + const Level composition_state_level = result.levels[composition_state]; + assert((composition_state_level == 0) == (lhs.levels[lhs_state] == 0 && rhs.levels[rhs_state] == 0)); + const Level lhs_level = lhs.levels[lhs_state]; + const Level rhs_level = rhs.levels[rhs_state]; + worklist.pop_front(); + + if (composition_state_level == 0) { + // We are at the zero level. + // It is now time to decide is we want to continue the synchronization and/or + // to wait in the lhs_state and/or rhs_state, because we can not synchronize + // on epsilon and on a symbol at the same time. + // Note: Transduce that contains symbol that can not synchronize with epsilon will wait. + const size_t lhs_sync_type_id = static_cast(lhs_sync_types[lhs_state]); + const size_t rhs_sync_type_id = static_cast(rhs_sync_types[rhs_state]); + + if (perform_wait_on_lhs[lhs_sync_type_id][rhs_sync_type_id]) { + // TODO: wait on lhs + } + + if (perform_wait_on_rhs[lhs_sync_type_id][rhs_sync_type_id]) { + // TODO: wait on rhs + } + + if (!perform_synchronization[lhs_sync_type_id][rhs_sync_type_id]) { + // No synchronization is needed. + // There are no symbols that would synchronize. + // There is total sismatch between lhs and rhs synchronization symbols. + continue; + } + } + + assert(perform_synchronization[static_cast(lhs_sync_types[lhs_state])][static_cast(rhs_sync_types[rhs_state])]); + + // TODO: Synchronization body. } return result.trim(); // Trim the result NFT to remove dead-end paths. From 933f07d2c89b7c106cf43280590a598051ec1e3b Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 13 Aug 2025 18:25:17 +0200 Subject: [PATCH 23/65] wip --- src/nft/composition.cc | 330 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 308 insertions(+), 22 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 95a51bb43..9d5675902 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -14,6 +14,10 @@ namespace { using mata::Symbol; using namespace mata::nft; using PredMap = std::unordered_map; + using ProductMap = std::unordered_map,State>; + using MatrixProductStorage = std::vector>; + using VecMapProductStorage = std::vector>; + using InvertedProductStorage = std::vector; enum class SynchronizationType : uint8_t { UNINITIALIZED = 0, ///< Default initialization value @@ -34,11 +38,29 @@ namespace { return lhs; } + struct SynchronizationProperties { + const Nft& nft; + const std::vector reachable_sync_types; ///< Types of synchronization for each state. + InvertedProductStorage& composition_to_this_map; ///< Mapping from composition state to lhs state. + InvertedProductStorage& composition_to_other_map; ///< Mapping from composition state to rhs state. + const Level sync_level; ///< The synchronization level. + const size_t num_of_trans_to_replace_sync; ///< Number of transitions to replace synchronization. + + SynchronizationProperties(const Nft& nft, const Level sync_level, const size_t num_of_trans_to_replace_sync, + InvertedProductStorage& composition_to_this_map, InvertedProductStorage& composition_to_other_map) + : nft(nft), + reachable_sync_types(get_synchronization_types(nft, sync_level)), + composition_to_this_map(composition_to_this_map), + composition_to_other_map(composition_to_other_map), + sync_level(sync_level), + num_of_trans_to_replace_sync(num_of_trans_to_replace_sync) {} + }; + static const bool perform_synchronization[4][4] = { /* * rhs_sync_type */ /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ /* =======================*========================================================================= */ - /* UNINITIALIZED */ { false, false, false, false }, + /* UNINITIALIZED */ { true, false, false, false }, /* ONLY_ON_SYMBOL */ { false, true, false, true }, /* ONLY_ON_EPSILON */ { false, false, true, true }, /* ON_EPSILON_AND_SYMBOL */ { false, true, true, true } @@ -139,11 +161,6 @@ namespace { return sync_types; } - using ProductMap = std::unordered_map,State>; - using MatrixProductStorage = std::vector>; - using VecMapProductStorage = std::vector>; - using InvertedProductStorage = std::vector; - /** Add a transition to the NFT from source state over symbol and length len to a new target state. * * @param nft The NFT to which the transition will be added. @@ -224,13 +241,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(lhs_sync_level < lhs.num_of_levels && rhs_sync_level < rhs.num_of_levels); // Number of Levels and States - const size_t lhs_num_of_levels = lhs.num_of_levels; - const size_t rhs_num_of_levels = rhs.num_of_levels; const size_t lhs_num_of_states = lhs.num_of_states(); const size_t rhs_num_of_states = rhs.num_of_states(); - const size_t result_num_of_levels = lhs_num_of_levels + rhs_num_of_levels - (project_out_sync_levels ? (2) : 1); + const size_t result_num_of_levels = lhs.num_of_levels + rhs.num_of_levels - (project_out_sync_levels ? (2) : 1); assert(result_num_of_levels > 0); - + Nft result; result.num_of_levels = result_num_of_levels; std::deque worklist; @@ -330,6 +345,212 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le return new_state; }; + // Calculate the number of epsilon transitions that will replace one epsilon synchronization transition during waiting. + // This depends on whether we are projecting out the synchronization level and if there are any transitions in the waiting + // transducer, that we need to place right before or right after the synchronization level. + const size_t lhs_levels_before_sync = lhs_sync_level; + const size_t rhs_levels_before_sync = rhs_sync_level; + const size_t lhs_levels_after_sync = lhs.num_of_levels - lhs_sync_level - 1; + const size_t rhs_levels_after_sync = rhs.num_of_levels - rhs_sync_level - 1; + // If we are waiting on the RHS, we need to know how many epsilon transitions + // will replace the synchronization transition in the LHS. + // 1) Transitions from RHS will always go as last before the snychronization level. + // 2) If the synchronization level is the last level in the LHS, we need to add + // all remaining transitions from the RHS that goes after the synchronization level. + // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. + const size_t lhs_num_of_trans_to_replace_sync = (rhs_levels_before_sync) + + (lhs_sync_level == lhs.num_of_levels - 1 ? rhs_levels_after_sync : 0) + + (project_out_sync_levels ? 0 : 1); + // If we are waiting on the LHS, we need to know how many epsilon transitions + // will replace the synchronization transition in the RHS. + // 1) Because transtions from LHS always go as first, the only time when they go immediately before the synchronization level + // is when the synchronization level is the first level in the RHS. + // 2) LHS transitions go always before RHS transitions, so after the synchronization level, + // we know that there will be transitions from the LHS. + // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. + const size_t rhs_num_of_trans_to_replace_sync = (rhs_sync_level == 0 ? lhs_levels_before_sync : 0) + + (lhs_levels_after_sync) + + (project_out_sync_levels ? 0 : 1); + + const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level, lhs_num_of_trans_to_replace_sync, composition_to_lhs, composition_to_rhs); + const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level, rhs_num_of_trans_to_replace_sync, composition_to_rhs, composition_to_lhs); + + auto model_waiting = [&](const State composition_root_state, const bool is_lhs_waiting) { + const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props : lhs_sync_props; + const Level sync_level = running_sync_props.sync_level; + const State running_root_state = running_sync_props.composition_to_this_map[composition_root_state]; + const State waiting_root_state = running_sync_props.composition_to_other_map[composition_root_state]; + const Levels& running_levels = running_sync_props.nft.levels; + const Delta& running_delta = running_sync_props.nft.delta; + const size_t num_of_trans_to_replace_sync = running_sync_props.num_of_trans_to_replace_sync; + const std::vector& reachable_sync_types = running_sync_props.reachable_sync_types; + // If the synchronization level is the last level, and it cannot be replaced by any sequence of epsilon transitions + // (i.e., it wanishes), we need to "in place" do the synchronization step and connect previous state to the next + // zero-level state. + const bool handle_sync_in_place = running_sync_props.sync_level == running_sync_props.nft.num_of_levels - 1 && + num_of_trans_to_replace_sync == 0; + + std::stack> stack; + stack.push({ composition_root_state, running_root_state}); + while (!stack.empty()) { + auto [composition_state, running_state] = stack.top(); + const Level running_state_level = running_levels[running_state]; + stack.pop(); + + // If we are in RHS and LHS is waiting and also the synchronization level is not the first level, + // then we need to add the epsilon transition from the waiting LHS, because transitions from the LHS + // goes always before the transitions from the RHS. + if (is_lhs_waiting && sync_level != 0 && running_state_level == 0 && lhs_levels_before_sync > 0) { + composition_state = add_transition(result, composition_state, EPSILON, lhs_levels_before_sync, jump_mode); + } + + if (running_state_level < sync_level) { + // We are before the synchronization level. + for (const SymbolPost& running_symbol_post : running_delta[running_state]) { + for (const State running_target : running_symbol_post.targets) { + if (reachable_sync_types[running_target] == SynchronizationType::ONLY_ON_SYMBOL) { + // The target state leads to only a synchronization on a symbol. + // We don't need this. We need to synchronize on epsilon. + continue; + } + + const Level running_target_level = running_levels[running_target]; + const size_t trans_len = running_target_level - running_state_level; + assert(running_target_level > running_state_level); + assert(trans_len > 0); + + if (running_target_level == sync_level && handle_sync_in_place) { + // We are at the synchronization level after which there will be nothing added. + // We need to connect running_state to epsilon successors of the running_target. + auto epsilon_sync_symbol_post = running_delta[running_target].find(EPSILON); + assert(epsilon_sync_symbol_post != running_delta[running_target].end()); + for (const State epsilon_target : epsilon_sync_symbol_post->targets) { + assert(running_levels[epsilon_target] == 0); + add_transition_with_target(result, composition_state, running_symbol_post.symbol, create_composition_state(waiting_root_state, epsilon_target, 0, is_lhs_waiting), jump_mode); + } + } else { + // It does not matter if the target is a synchronization level or not + // (we can handle any synchronization later, because it will be replaced + // by epsilon transitions). + const State new_composition_state = add_transition(result, composition_state, running_symbol_post.symbol, trans_len, jump_mode); + stack.push({ std::move(new_composition_state), running_target }); + } + } + } + + } else if (running_state_level == sync_level) { + // We are exactly at the synchronization level. + assert(!handle_sync_in_place); + auto epsilon_sync_symbol_post = running_delta[running_state].find(EPSILON); + assert(epsilon_sync_symbol_post != running_delta[running_state].end()); + for (const State epsilon_target : epsilon_sync_symbol_post->targets) { + if (num_of_trans_to_replace_sync > 0) { + // We will add some transitions on the place of the synchronization transition. + if (running_levels[epsilon_target] == 0) { + // We are connecting directly to the zero-level state. + add_transition_with_target(result, composition_state, EPSILON, create_composition_state(waiting_root_state, epsilon_target, 0, is_lhs_waiting), jump_mode); + } else { + // We are connecting to an internal state. Create new auxiliar state for it. + const State new_composition_state = add_transition(result, composition_state, EPSILON, num_of_trans_to_replace_sync, jump_mode); + stack.push({ std::move(new_composition_state), epsilon_target }); + } + } else { + // Just ignore this transition. + // (It is being projected out and there are no transitions in the waiting NFT + // that would be place directly before or after the synchronization level.) + stack.push({ composition_state, epsilon_target }); + } + } + } else { + // We are past the synchronization level. + // We just need to get to the next zero-level state. + for (const SymbolPost& symbol_post : running_delta[running_state]) { + for (const State target : symbol_post.targets) { + const Level target_level = running_levels[target]; + if (target_level == 0) { + // We are connecting to a zero-level state. + if (!is_lhs_waiting && rhs_levels_after_sync > 0) { + // We are in the LHS and RHS waits. Because transitions from RHS goes as last, + // we need to add epsilon transitions from the waiting RHS after this transition. + const size_t trans_len = running_sync_props.nft.num_of_levels - running_levels[running_state]; + const State new_composition_state = add_transition(result, composition_state, symbol_post.symbol, trans_len, jump_mode); + add_transition_with_target(result, new_composition_state, EPSILON, create_composition_state(waiting_root_state, target, 0, is_lhs_waiting), jump_mode); + } else { + // We don't need to add any other transitions, just this one. + add_transition_with_target(result, composition_state, symbol_post.symbol, create_composition_state(waiting_root_state, target, 0, is_lhs_waiting), jump_mode); + } + } else { + // Otherwise just copy the transition. + const State new_composition_state = add_transition(result, composition_state, symbol_post.symbol, target_level - running_state_level, jump_mode); + stack.push({new_composition_state, target}); + } + } + } + } + } + }; + + auto synchronize = [&](const State lhs_state, const State rhs_state, const State composition_source_state, const Symbol symbol_when_projected_out = Limits::max_symbol) { + const Level lhs_level = lhs.levels[lhs_state]; + const Level rhs_level = rhs.levels[rhs_state]; + const Level composition_target_level = (result.levels[composition_source_state] + 1) % result.num_of_levels; + + // Do the normal synchronization on symbol. + mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); + mata::utils::push_back(sync_iterator, lhs.delta[lhs_state]); + mata::utils::push_back(sync_iterator, rhs.delta[rhs_state]); + while (sync_iterator.advance()) { + const std::vector& same_symbol_posts{ sync_iterator.get_current() }; + assert(same_symbol_posts.size() == 2); // One move per state in the pair. + const Symbol symbol = project_out_sync_levels ? symbol_when_projected_out : same_symbol_posts[0]->symbol; + for (const State lhs_sync_target : same_symbol_posts[0]->targets) { + for (const State rhs_sync_target : same_symbol_posts[1]->targets) { + + add_transition_with_target(result, composition_source_state, symbol, + create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + } + } + } + + // Do the synchronization on DONT_CARE in the LHS. + auto lhs_dont_care_sync_it = lhs.delta[lhs_state].find(DONT_CARE); + if (lhs_dont_care_sync_it != lhs.delta[lhs_state].end()) { + for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { + if (rhs_symbol_post.symbol == EPSILON) { + // We don't want to synchronize on DONT_CARE with EPSILON. + continue; + } + const Symbol symbol = project_out_sync_levels ? symbol_when_projected_out : rhs_symbol_post.symbol; + for (const State rhs_sync_target : rhs_symbol_post.targets) { + for (const State lhs_sync_target : lhs_dont_care_sync_it->targets) { + // Add the transition from the LHS DONT_CARE to the RHS target. + add_transition_with_target(result, composition_source_state, symbol, + create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + } + } + } + } + + // Do the synchronization on DONT_CARE in the RHS. + auto rhs_dont_care_sync_it = rhs.delta[rhs_state].find(DONT_CARE); + if (rhs_dont_care_sync_it != rhs.delta[rhs_state].end()) { + for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { + if (lhs_symbol_post.symbol == EPSILON) { + // We don't want to synchronize on DONT_CARE with EPSILON. + continue; + } + const Symbol symbol = project_out_sync_levels ? symbol_when_projected_out : lhs_symbol_post.symbol; + for (const State lhs_sync_target : lhs_symbol_post.targets) { + for (const State rhs_sync_target : rhs_dont_care_sync_it->targets) { + // Add the transition from the RHS DONT_CARE to the LHS target. + add_transition_with_target(result, composition_source_state, symbol, + create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + } + } + } + } + }; + // INITIALIZATION of a worklist (side effect of a create_composition_state) for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { @@ -339,34 +560,37 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } } - const std::vector lhs_sync_types = get_synchronization_types(lhs, lhs_sync_level); - const std::vector rhs_sync_types = get_synchronization_types(rhs, rhs_sync_level); + const std::vector& lhs_reachable_sync_types = lhs_sync_props.reachable_sync_types; + const std::vector& rhs_reachable_sync_types = rhs_sync_props.reachable_sync_types; while (!worklist.empty()) { const State composition_state = worklist.front(); const State lhs_state = composition_to_lhs[composition_state]; const State rhs_state = composition_to_rhs[composition_state]; const Level composition_state_level = result.levels[composition_state]; - assert((composition_state_level == 0) == (lhs.levels[lhs_state] == 0 && rhs.levels[rhs_state] == 0)); const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; + const size_t lhs_sync_type_id = static_cast(lhs_reachable_sync_types[lhs_state]); + const size_t rhs_sync_type_id = static_cast(rhs_reachable_sync_types[rhs_state]); + // LHS can be before the synchronization level only if RHS is before the synchronization level as well. + // Otherwise, there are both past the synchronization level. + const bool is_lhs_before_sync = (lhs_level != 0 && rhs_level == 0) && lhs_level < lhs_sync_level; + const bool is_rhs_before_sync = (lhs_level <= lhs_sync_level) && rhs_level < rhs_sync_level; worklist.pop_front(); - if (composition_state_level == 0) { - // We are at the zero level. + if (lhs_level == 0 and rhs_level == 0) { + // We are at the zero level states. // It is now time to decide is we want to continue the synchronization and/or // to wait in the lhs_state and/or rhs_state, because we can not synchronize // on epsilon and on a symbol at the same time. // Note: Transduce that contains symbol that can not synchronize with epsilon will wait. - const size_t lhs_sync_type_id = static_cast(lhs_sync_types[lhs_state]); - const size_t rhs_sync_type_id = static_cast(rhs_sync_types[rhs_state]); if (perform_wait_on_lhs[lhs_sync_type_id][rhs_sync_type_id]) { - // TODO: wait on lhs + model_waiting(composition_state, true); // LHS is waiting. } - + if (perform_wait_on_rhs[lhs_sync_type_id][rhs_sync_type_id]) { - // TODO: wait on rhs + model_waiting(composition_state, false); // RHS is waiting. } if (!perform_synchronization[lhs_sync_type_id][rhs_sync_type_id]) { @@ -377,9 +601,71 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } } - assert(perform_synchronization[static_cast(lhs_sync_types[lhs_state])][static_cast(rhs_sync_types[rhs_state])]); + assert(perform_synchronization[lhs_sync_type_id][rhs_sync_type_id]); + + if (is_lhs_before_sync) { + // LHS is before the synchronization level, so we need to first get there. + for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { + for (const State lhs_target : lhs_symbol_post.targets) { + const size_t lhs_target_sync_type_id = static_cast(lhs_reachable_sync_types[lhs_target]); + if (!perform_synchronization[lhs_sync_type_id][lhs_target_sync_type_id]) { + // The target does not lead to a synchronization. + // We don't need to explore this. + continue; + } + const Level lhs_target_level = lhs.levels[lhs_target]; + const size_t trans_len = lhs_target_level - lhs_level; + assert(lhs_target_level > lhs_level); + + // We don't need to worry, if it's a last useful transition, and + // doint the synchronization in place, because we are sure that there is/will be + // at leas one unprocessed useful transition in the RHS. + add_transition_with_target(result, composition_state, lhs_symbol_post.symbol, + create_composition_state(lhs_target, rhs_state, composition_state_level + trans_len, true), jump_mode); + } + } + } else if (is_rhs_before_sync) { + // LHS is at synchronization level, but RHS is before the synchronization level. + // RHS needs to continue. + for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { + for (const State rhs_target : rhs_symbol_post.targets) { + const size_t rhs_target_sync_type_id = static_cast(rhs_reachable_sync_types[rhs_target]); + if (!perform_synchronization[rhs_sync_type_id][rhs_target_sync_type_id]) { + // The target does not lead to a synchronization. + // We don't need to explore this. + continue; + } + const Level rhs_target_level = rhs.levels[rhs_target]; + const size_t trans_len = rhs_target_level - rhs_level; + assert(rhs_target_level > rhs_level); + + const bool lhs_level_is_last = lhs_level == lhs.num_of_levels - 1; + const bool rhs_target_level_is_last = rhs_target_level == rhs.num_of_levels - 1; + if (lhs_level_is_last && rhs_target_level_is_last && project_out_sync_levels) { + // LHS is at the synchronization level. There is no transition after the synchronization in LHS. + // The target in RHS is at synchronization level, at there is also no transition after the synchronization in RHS. + // Also we are projecting out the synchronization levels. + // This means, that we need now to synchronize and make a transition to the zero-level state in the result NFT. + synchronize(lhs_state, rhs_target, composition_state, rhs_symbol_post.symbol); + } else { + // Just copy the transition. + add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, + create_composition_state(lhs_state, rhs_target, composition_state_level + trans_len, false), jump_mode); + } + } + } - // TODO: Synchronization body. + } else if (lhs_level == lhs_sync_level && rhs_level == rhs_sync_level) { + assert(lhs_level != lhs.num_of_levels - 1 && rhs_level != rhs.num_of_levels - 1); + synchronize(lhs_state, rhs_state, composition_state); + } else if (lhs_level != 0) { + assert(!is_lhs_before_sync && !is_rhs_before_sync); + } else { + assert(!is_lhs_before_sync && !is_rhs_before_sync); + assert(lhs_level == 0 && rhs_level != 0); + } + + } return result.trim(); // Trim the result NFT to remove dead-end paths. From d5ea5aa0777e04133761b7fc4997a70481cb9ee9 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 14 Aug 2025 13:53:46 +0200 Subject: [PATCH 24/65] draft done --- src/nft/composition.cc | 79 ++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 9d5675902..e07dcaf99 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -60,7 +60,7 @@ namespace { /* * rhs_sync_type */ /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ /* =======================*========================================================================= */ - /* UNINITIALIZED */ { true, false, false, false }, + /* UNINITIALIZED */ { false, false, false, false }, /* ONLY_ON_SYMBOL */ { false, true, false, true }, /* ONLY_ON_EPSILON */ { false, false, true, true }, /* ON_EPSILON_AND_SYMBOL */ { false, true, true, true } @@ -248,7 +248,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le Nft result; result.num_of_levels = result_num_of_levels; - std::deque worklist; + std::deque> worklist; // FAST STORAGE OF COMPOSITION STATES // The largest matrix of pairs of states we are brave enough to allocate. @@ -333,13 +333,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // If the level is zero, we need to check for final states and add to the worklist. if (level == 0) { // Because the key pair was not found in the map, we are sure that the state was not in the worklist yet either. - worklist.push_back(new_state); + worklist.push_back(key); if ((is_a_lhs && lhs.final.contains(state_a) && rhs.final.contains(state_b)) || (!is_a_lhs && rhs.final.contains(state_a) && lhs.final.contains(state_b))) { result.final.insert(new_state); } } else { - worklist.push_front(new_state); // Push to the front for non-zero levels. + worklist.push_front(key); // Push to the front for non-zero levels. } return new_state; @@ -490,10 +490,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } }; - auto synchronize = [&](const State lhs_state, const State rhs_state, const State composition_source_state, const Symbol symbol_when_projected_out = Limits::max_symbol) { + auto synchronize = [&](const State lhs_state, const State rhs_state, const State composition_source_state, const bool reconnect = false, const Symbol symbol_when_reconnect = Limits::max_symbol) { const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; const Level composition_target_level = (result.levels[composition_source_state] + 1) % result.num_of_levels; + const bool vanish_sync_level = !reconnect && project_out_sync_levels; // Do the normal synchronization on symbol. mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); @@ -502,12 +503,15 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le while (sync_iterator.advance()) { const std::vector& same_symbol_posts{ sync_iterator.get_current() }; assert(same_symbol_posts.size() == 2); // One move per state in the pair. - const Symbol symbol = project_out_sync_levels ? symbol_when_projected_out : same_symbol_posts[0]->symbol; + const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : same_symbol_posts[0]->symbol; for (const State lhs_sync_target : same_symbol_posts[0]->targets) { for (const State rhs_sync_target : same_symbol_posts[1]->targets) { - - add_transition_with_target(result, composition_source_state, symbol, - create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + if (vanish_sync_level) { + worklist.push_back({ lhs_sync_target, rhs_sync_target }); + } else { + add_transition_with_target(result, composition_source_state, symbol, + create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + } } } } @@ -520,12 +524,16 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We don't want to synchronize on DONT_CARE with EPSILON. continue; } - const Symbol symbol = project_out_sync_levels ? symbol_when_projected_out : rhs_symbol_post.symbol; + const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : rhs_symbol_post.symbol; for (const State rhs_sync_target : rhs_symbol_post.targets) { for (const State lhs_sync_target : lhs_dont_care_sync_it->targets) { // Add the transition from the LHS DONT_CARE to the RHS target. - add_transition_with_target(result, composition_source_state, symbol, - create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + if (vanish_sync_level) { + worklist.push_back({ lhs_sync_target, rhs_sync_target }); + } else { + add_transition_with_target(result, composition_source_state, symbol, + create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + } } } } @@ -539,12 +547,16 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We don't want to synchronize on DONT_CARE with EPSILON. continue; } - const Symbol symbol = project_out_sync_levels ? symbol_when_projected_out : lhs_symbol_post.symbol; + const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : lhs_symbol_post.symbol; for (const State lhs_sync_target : lhs_symbol_post.targets) { for (const State rhs_sync_target : rhs_dont_care_sync_it->targets) { // Add the transition from the RHS DONT_CARE to the LHS target. - add_transition_with_target(result, composition_source_state, symbol, - create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + if (vanish_sync_level) { + worklist.push_back({ lhs_sync_target, rhs_sync_target }); + } else { + add_transition_with_target(result, composition_source_state, symbol, + create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + } } } } @@ -564,19 +576,19 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const std::vector& rhs_reachable_sync_types = rhs_sync_props.reachable_sync_types; while (!worklist.empty()) { - const State composition_state = worklist.front(); - const State lhs_state = composition_to_lhs[composition_state]; - const State rhs_state = composition_to_rhs[composition_state]; - const Level composition_state_level = result.levels[composition_state]; + const auto [lhs_state, rhs_state] = worklist.front(); + worklist.pop_front(); const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; + const State composition_state = get_state_from_product_storage(lhs_state, rhs_state); + assert(composition_state != Limits::max_state); + const Level composition_state_level = result.levels[composition_state]; const size_t lhs_sync_type_id = static_cast(lhs_reachable_sync_types[lhs_state]); const size_t rhs_sync_type_id = static_cast(rhs_reachable_sync_types[rhs_state]); // LHS can be before the synchronization level only if RHS is before the synchronization level as well. // Otherwise, there are both past the synchronization level. const bool is_lhs_before_sync = (lhs_level != 0 && rhs_level == 0) && lhs_level < lhs_sync_level; const bool is_rhs_before_sync = (lhs_level <= lhs_sync_level) && rhs_level < rhs_sync_level; - worklist.pop_front(); if (lhs_level == 0 and rhs_level == 0) { // We are at the zero level states. @@ -646,7 +658,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // The target in RHS is at synchronization level, at there is also no transition after the synchronization in RHS. // Also we are projecting out the synchronization levels. // This means, that we need now to synchronize and make a transition to the zero-level state in the result NFT. - synchronize(lhs_state, rhs_target, composition_state, rhs_symbol_post.symbol); + synchronize(lhs_state, rhs_target, composition_state, true, rhs_symbol_post.symbol); } else { // Just copy the transition. add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, @@ -656,15 +668,36 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } } else if (lhs_level == lhs_sync_level && rhs_level == rhs_sync_level) { + // We are at the synchronization level in both NFTs. assert(lhs_level != lhs.num_of_levels - 1 && rhs_level != rhs.num_of_levels - 1); - synchronize(lhs_state, rhs_state, composition_state); + synchronize(lhs_state, rhs_state, composition_state); } else if (lhs_level != 0) { + // LHS is past the synchronization level and there are some transitions remaining to remap. assert(!is_lhs_before_sync && !is_rhs_before_sync); + for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { + for (const State lhs_target : lhs_symbol_post.targets) { + const Level lhs_target_level = lhs.levels[lhs_target]; + const size_t trans_len = (lhs_target_level == 0 ? lhs.num_of_levels : lhs_target_level) - lhs_level; + assert(lhs_target_level > lhs_level || lhs_target_level == 0); + add_transition_with_target(result, composition_state, lhs_symbol_post.symbol, + create_composition_state(lhs_target, rhs_state, composition_state_level + trans_len, true), jump_mode); + } + } } else { assert(!is_lhs_before_sync && !is_rhs_before_sync); assert(lhs_level == 0 && rhs_level != 0); - } + // RHS is past the synchronization level and there are some transitions remaining to remap. + for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { + for (const State rhs_target : rhs_symbol_post.targets) { + const Level rhs_target_level = rhs.levels[rhs_target]; + const size_t trans_len = (rhs_target_level == 0 ? rhs.num_of_levels : rhs_target_level) - rhs_level; + assert(rhs_target_level > rhs_level || rhs_target_level == 0); + add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, + create_composition_state(lhs_state, rhs_target, composition_state_level + trans_len, false), jump_mode); + } + } + } } From 6d408565e3451dbc9889a84632be04940022455d Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 14 Aug 2025 15:58:16 +0200 Subject: [PATCH 25/65] add comments --- src/nft/composition.cc | 70 +++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index e07dcaf99..959e015ac 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -19,43 +19,45 @@ namespace { using VecMapProductStorage = std::vector>; using InvertedProductStorage = std::vector; + // Enum class for a state flag to indicate which synchronization + //types (synchronization on epsilon or symbol) can be reached from the state. enum class SynchronizationType : uint8_t { - UNINITIALIZED = 0, ///< Default initialization value - ONLY_ON_SYMBOL = 1, ///< Epsilon symbol for synchronization - ONLY_ON_EPSILON = 2, ///< Non-epsilon symbol for synchronization - ON_EPSILON_AND_SYMBOL = 3, ///< Both epsilon and non-epsilon - UNDER_COMPUTATION = 4 ///< Unknown synchronization type + UNINITIALIZED = 0b0000'0000, ///< Default value. + ONLY_ON_SYMBOL = 0b0000'0001, ///< Synchronization on EPSILON. + ONLY_ON_EPSILON = 0b0000'0010, ///< Synchronization on symbol. + ON_EPSILON_AND_SYMBOL = 0b0000'0011, ///< Synchronization on EPSILON and symbol. + UNDER_COMPUTATION = 0b0000'0100 ///< The synchronization is being computed (only helper value). }; - inline SynchronizationType operator|(SynchronizationType lhs, SynchronizationType rhs) { - return static_cast( - static_cast(lhs) | static_cast(rhs) - ); + return static_cast(static_cast(lhs) | static_cast(rhs)); } - inline SynchronizationType& operator|=(SynchronizationType& lhs, SynchronizationType rhs) { lhs = lhs | rhs; return lhs; } + /** + * @brief A struct to hold properties related to synchronization during the composition of NFTs. + * This simplifies the passing of multiple synchronization-related parameters. + */ struct SynchronizationProperties { const Nft& nft; - const std::vector reachable_sync_types; ///< Types of synchronization for each state. - InvertedProductStorage& composition_to_this_map; ///< Mapping from composition state to lhs state. - InvertedProductStorage& composition_to_other_map; ///< Mapping from composition state to rhs state. - const Level sync_level; ///< The synchronization level. + const std::vector reachable_sync_types; + const Level sync_level; const size_t num_of_trans_to_replace_sync; ///< Number of transitions to replace synchronization. SynchronizationProperties(const Nft& nft, const Level sync_level, const size_t num_of_trans_to_replace_sync, InvertedProductStorage& composition_to_this_map, InvertedProductStorage& composition_to_other_map) : nft(nft), reachable_sync_types(get_synchronization_types(nft, sync_level)), - composition_to_this_map(composition_to_this_map), - composition_to_other_map(composition_to_other_map), sync_level(sync_level), num_of_trans_to_replace_sync(num_of_trans_to_replace_sync) {} }; + // We can perform synchronization only if from both lhs and rhs can be reached a synchronization + // level, that synchronizes on EPSILON and/or symbol. + // For example, there is not way that we can synchronize when only synchronization on EPSILON in lhs + // and only on symbol in rhs is possible. static const bool perform_synchronization[4][4] = { /* * rhs_sync_type */ /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ @@ -66,6 +68,8 @@ namespace { /* ON_EPSILON_AND_SYMBOL */ { false, true, true, true } }; + // We need to wait in the LHS state if there is a synchronization on RHS state and a possibility + // that the LHS can not synchronize on it (e.g. LHS monewhere synchronizes on a symbol). static const bool perform_wait_on_lhs[4][4] = { /* * rhs_sync_type */ /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ @@ -76,6 +80,8 @@ namespace { /* ON_EPSILON_AND_SYMBOL */ { false, false, true, true } }; + // We need to wait in the RHS state if there is a synchronization on LHS state and a possibility + // that the RHS can not synchronize on it (e.g. RHS monewhere synchronizes on a symbol). static const bool perform_wait_on_rhs[4][4] = { /* * rhs_sync_type */ /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ @@ -375,21 +381,39 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level, lhs_num_of_trans_to_replace_sync, composition_to_lhs, composition_to_rhs); const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level, rhs_num_of_trans_to_replace_sync, composition_to_rhs, composition_to_lhs); - auto model_waiting = [&](const State composition_root_state, const bool is_lhs_waiting) { + /** + * @brief Model the waiting in one of the NFTs if they could not synchronize due to the EPSILON or symbol transition. + * + * For example, if there is EPSILON on the synchronization level in the RHS and LHS can not synchronize on it, + * then the LHS needs to wait in the root (zero-level) state that precedes the synchronization and RHS will + * continue in the corresponding RHS root ower the problematic EPSILON synchronization to the next zero-level state. + * The RHS is going to act al if it is interleaving with the LHS transitions (LHS transitions always go before RHS transitions), + * however it will place the EPSILON on each such a transition from the LHS. + * + * The problem is if we want to project out the synchronization levels, but there are not transitions following them + * in the LHS not RHS (the synchronization level is the last level in both of them). Then one level before the + * synchronization level, we need to connect successors of such states to the zero-level state that lies after the synchronization level. + * + * @param composition_root_state The root state of the composition NFT. + * @param waiting_root_state The root state of the NFT that is waiting (LHS or RHS). + * @param running_root_state The root state of the NFT that is running (LHS or RHS). + * @param is_lhs_waiting If true, the LHS is waiting, otherwise the RHS is waiting. + */ + auto model_waiting = [&](const State composition_root_state, const State waiting_root_state, const State running_root_state, const bool is_lhs_waiting) { const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props : lhs_sync_props; const Level sync_level = running_sync_props.sync_level; - const State running_root_state = running_sync_props.composition_to_this_map[composition_root_state]; - const State waiting_root_state = running_sync_props.composition_to_other_map[composition_root_state]; const Levels& running_levels = running_sync_props.nft.levels; const Delta& running_delta = running_sync_props.nft.delta; const size_t num_of_trans_to_replace_sync = running_sync_props.num_of_trans_to_replace_sync; const std::vector& reachable_sync_types = running_sync_props.reachable_sync_types; // If the synchronization level is the last level, and it cannot be replaced by any sequence of epsilon transitions - // (i.e., it wanishes), we need to "in place" do the synchronization step and connect previous state to the next + // (i.e., it wanishes), we need to do "in place" synchronization step and connect previous state to the next // zero-level state. const bool handle_sync_in_place = running_sync_props.sync_level == running_sync_props.nft.num_of_levels - 1 && num_of_trans_to_replace_sync == 0; + // We cannot use inverted storage here, because there can be one composition state for multiple original pairs of states. + // Therefore we need to keep track of it in the stack instead. std::stack> stack; stack.push({ composition_root_state, running_root_state}); while (!stack.empty()) { @@ -598,11 +622,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Note: Transduce that contains symbol that can not synchronize with epsilon will wait. if (perform_wait_on_lhs[lhs_sync_type_id][rhs_sync_type_id]) { - model_waiting(composition_state, true); // LHS is waiting. + model_waiting(composition_state, lhs_state, rhs_state, true); // LHS is waiting. } if (perform_wait_on_rhs[lhs_sync_type_id][rhs_sync_type_id]) { - model_waiting(composition_state, false); // RHS is waiting. + model_waiting(composition_state, rhs_state, lhs_state, false); // RHS is waiting. } if (!perform_synchronization[lhs_sync_type_id][rhs_sync_type_id]) { @@ -713,8 +737,6 @@ Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_lev return compose(lhs, rhs, lhs_sync_levels.front(), lhs_sync_levels.front(), project_out_sync_levels, jump_mode); } - throw std::runtime_error("Noodler should not go here. Noodler should always use JumpMode::NoJump."); - // Inserts loop into the given Nft for each state with level 0. // The loop word is constructed using the EPSILON symbol for all levels, except for the levels // where is_dcare_on_transition is true, in which case the DONT_CARE symbol is used. From 79edec681bca6499370eea3d7eefe0d455c15413 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 14 Aug 2025 16:07:16 +0200 Subject: [PATCH 26/65] 2D storage --- src/nft/composition.cc | 129 ++++++++++++----------------------------- 1 file changed, 37 insertions(+), 92 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 959e015ac..2a94c731b 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -3,6 +3,7 @@ // MATA headers #include "mata/nft/nft.hh" +#include "mata/utils/two-dimensional-map.hh" #include #include @@ -13,11 +14,6 @@ using namespace mata::utils; namespace { using mata::Symbol; using namespace mata::nft; - using PredMap = std::unordered_map; - using ProductMap = std::unordered_map,State>; - using MatrixProductStorage = std::vector>; - using VecMapProductStorage = std::vector>; - using InvertedProductStorage = std::vector; // Enum class for a state flag to indicate which synchronization //types (synchronization on epsilon or symbol) can be reached from the state. @@ -46,8 +42,7 @@ namespace { const Level sync_level; const size_t num_of_trans_to_replace_sync; ///< Number of transitions to replace synchronization. - SynchronizationProperties(const Nft& nft, const Level sync_level, const size_t num_of_trans_to_replace_sync, - InvertedProductStorage& composition_to_this_map, InvertedProductStorage& composition_to_other_map) + SynchronizationProperties(const Nft& nft, const Level sync_level, const size_t num_of_trans_to_replace_sync) : nft(nft), reachable_sync_types(get_synchronization_types(nft, sync_level)), sync_level(sync_level), @@ -251,62 +246,42 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const size_t rhs_num_of_states = rhs.num_of_states(); const size_t result_num_of_levels = lhs.num_of_levels + rhs.num_of_levels - (project_out_sync_levels ? (2) : 1); assert(result_num_of_levels > 0); - + + // Calculate the number of epsilon transitions that will replace one epsilon synchronization transition during waiting. + // This depends on whether we are projecting out the synchronization level and if there are any transitions in the waiting + // transducer, that we need to place right before or right after the synchronization level. + const size_t lhs_levels_before_sync = lhs_sync_level; + const size_t rhs_levels_before_sync = rhs_sync_level; + const size_t lhs_levels_after_sync = lhs.num_of_levels - lhs_sync_level - 1; + const size_t rhs_levels_after_sync = rhs.num_of_levels - rhs_sync_level - 1; + // If we are waiting on the RHS, we need to know how many epsilon transitions + // will replace the synchronization transition in the LHS. + // 1) Transitions from RHS will always go as last before the snychronization level. + // 2) If the synchronization level is the last level in the LHS, we need to add + // all remaining transitions from the RHS that goes after the synchronization level. + // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. + const size_t lhs_num_of_trans_to_replace_sync = (rhs_levels_before_sync) + + (lhs_sync_level == lhs.num_of_levels - 1 ? rhs_levels_after_sync : 0) + + (project_out_sync_levels ? 0 : 1); + // If we are waiting on the LHS, we need to know how many epsilon transitions + // will replace the synchronization transition in the RHS. + // 1) Because transtions from LHS always go as first, the only time when they go immediately before the synchronization level + // is when the synchronization level is the first level in the RHS. + // 2) LHS transitions go always before RHS transitions, so after the synchronization level, + // we know that there will be transitions from the LHS. + // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. + const size_t rhs_num_of_trans_to_replace_sync = (rhs_sync_level == 0 ? lhs_levels_before_sync : 0) + + (lhs_levels_after_sync) + + (project_out_sync_levels ? 0 : 1); + + const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level, lhs_num_of_trans_to_replace_sync); + const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level, rhs_num_of_trans_to_replace_sync); + Nft result; result.num_of_levels = result_num_of_levels; + TwoDimensionalMap composition_storage(lhs_num_of_states, rhs_num_of_states); std::deque> worklist; - // FAST STORAGE OF COMPOSITION STATES - // The largest matrix of pairs of states we are brave enough to allocate. - // Let's say we are fine with allocating larget_composition * (about 8 Bytes) space. - // So ten million cells is close to 100 MB. - // If the number is larger, then we do not allocate a matrix, but use a vector of unordered maps. - // The unordered_map seems to be about twice slower. - constexpr size_t MAX_COMPOSITION_MATRIX_SIZE = 50'000'000; - // constexpr size_t MAX_COMPOSITION_MATRIX_SIZE = 0; - const bool larget_composition = lhs_num_of_states * rhs_num_of_states > MAX_COMPOSITION_MATRIX_SIZE; - assert(lhs_num_of_states < Limits::max_state); - assert(rhs_num_of_states < Limits::max_state); - - // Two variants of storage for the mapping from pairs of lhs and rhs states to composition state, for large and non-large products. - MatrixProductStorage matrix_composition_storage; - VecMapProductStorage vec_map_composition_storage; - InvertedProductStorage composition_to_lhs(lhs_num_of_states + rhs_num_of_states); - InvertedProductStorage composition_to_rhs(lhs_num_of_states + rhs_num_of_states); - - // Initialize the storage, according to the number of possible state pairs. - if (!larget_composition) - matrix_composition_storage = MatrixProductStorage(lhs_num_of_states, std::vector(rhs_num_of_states, Limits::max_state)); - else - vec_map_composition_storage = VecMapProductStorage(lhs_num_of_states); - - /// Give me the composition state for the pair of lhs and rhs states. - /// Returns Limits::max_state if not found. - auto get_state_from_product_storage = [&](State lhs_state, State rhs_state) { - if (!larget_composition) - return matrix_composition_storage[lhs_state][rhs_state]; - else { - auto it = vec_map_composition_storage[lhs_state].find(rhs_state); - if (it == vec_map_composition_storage[lhs_state].end()) - return Limits::max_state; - else - return it->second; - } - }; - - /// Insert new mapping lhs rhs state pair to composition state. - auto insert_to_product_storage = [&](State lhs_state, State rhs_state, State composition_state) { - if (!larget_composition) - matrix_composition_storage[lhs_state][rhs_state] = composition_state; - else - vec_map_composition_storage[lhs_state][rhs_state] = composition_state; - - composition_to_lhs.resize(composition_state+1); - composition_to_rhs.resize(composition_state+1); - composition_to_lhs[composition_state] = lhs_state; - composition_to_rhs[composition_state] = rhs_state; - }; - /** * @brief Create a new composition state for the given pair of states, if it does not already exist. * @@ -326,7 +301,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const auto key = is_a_lhs ? std::make_pair(state_a, state_b) : std::make_pair(state_b, state_a); const State lhs_state = key.first; const State rhs_state = key.second; - const State found_state = get_state_from_product_storage(lhs_state, rhs_state); + const State found_state = composition_storage.get(lhs_state, rhs_state); if (found_state != Limits::max_state) { assert(found_state == composition_state_to_add || composition_state_to_add == Limits::max_state); return found_state; @@ -334,7 +309,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // If not found, add a new state to the result NFT. State new_state = (composition_state_to_add != Limits::max_state) ? composition_state_to_add : result.add_state_with_level(level); - insert_to_product_storage(lhs_state, rhs_state, new_state); + composition_storage.insert(lhs_state, rhs_state, new_state); // If the level is zero, we need to check for final states and add to the worklist. if (level == 0) { @@ -351,36 +326,6 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le return new_state; }; - // Calculate the number of epsilon transitions that will replace one epsilon synchronization transition during waiting. - // This depends on whether we are projecting out the synchronization level and if there are any transitions in the waiting - // transducer, that we need to place right before or right after the synchronization level. - const size_t lhs_levels_before_sync = lhs_sync_level; - const size_t rhs_levels_before_sync = rhs_sync_level; - const size_t lhs_levels_after_sync = lhs.num_of_levels - lhs_sync_level - 1; - const size_t rhs_levels_after_sync = rhs.num_of_levels - rhs_sync_level - 1; - // If we are waiting on the RHS, we need to know how many epsilon transitions - // will replace the synchronization transition in the LHS. - // 1) Transitions from RHS will always go as last before the snychronization level. - // 2) If the synchronization level is the last level in the LHS, we need to add - // all remaining transitions from the RHS that goes after the synchronization level. - // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. - const size_t lhs_num_of_trans_to_replace_sync = (rhs_levels_before_sync) + - (lhs_sync_level == lhs.num_of_levels - 1 ? rhs_levels_after_sync : 0) + - (project_out_sync_levels ? 0 : 1); - // If we are waiting on the LHS, we need to know how many epsilon transitions - // will replace the synchronization transition in the RHS. - // 1) Because transtions from LHS always go as first, the only time when they go immediately before the synchronization level - // is when the synchronization level is the first level in the RHS. - // 2) LHS transitions go always before RHS transitions, so after the synchronization level, - // we know that there will be transitions from the LHS. - // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. - const size_t rhs_num_of_trans_to_replace_sync = (rhs_sync_level == 0 ? lhs_levels_before_sync : 0) + - (lhs_levels_after_sync) + - (project_out_sync_levels ? 0 : 1); - - const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level, lhs_num_of_trans_to_replace_sync, composition_to_lhs, composition_to_rhs); - const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level, rhs_num_of_trans_to_replace_sync, composition_to_rhs, composition_to_lhs); - /** * @brief Model the waiting in one of the NFTs if they could not synchronize due to the EPSILON or symbol transition. * @@ -604,7 +549,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le worklist.pop_front(); const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; - const State composition_state = get_state_from_product_storage(lhs_state, rhs_state); + const State composition_state = composition_storage.get(lhs_state, rhs_state); assert(composition_state != Limits::max_state); const Level composition_state_level = result.levels[composition_state]; const size_t lhs_sync_type_id = static_cast(lhs_reachable_sync_types[lhs_state]); From 7cf387ee669dea2065126ced525ff00c8517c2e3 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 14 Aug 2025 17:26:42 +0200 Subject: [PATCH 27/65] nft new add_transition methods --- include/mata/nft/nft.hh | 39 ++++++ src/nft/composition.cc | 301 +++++++++++++++------------------------- src/nft/nft.cc | 88 ++++++++++++ 3 files changed, 240 insertions(+), 188 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index b4bddf494..668e5f6ad 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -262,6 +262,45 @@ public: */ State add_transition(State source, const std::vector& symbols); + /** + * @brief Add a single NFT transition with a length @p length from a source state @p source + * to a newly created target state, creating as many inner states as needed for the transition + * @p length and the jump mode. + * + * @param source The source state where the transition begins. @p source must already exist. + * @param symbol The symbol used for the transition. + * @param length The length of the transition. + * @param jump_mode Specifies the semantic of jump transitions (transitions with a length greater than 1). + */ + State add_transition_with_lenght(State source, Symbol symbol, size_t length, JumpMode jump_mode = JumpMode::RepeatSymbol); + + /** + * @brief Add a single NFT transition from a source state @p source to a target state @p target + * creating as many inner states as needed for the transition length and the jump mode. + * + * @param source The source state where the transition begins. @p source must already exist. + * @param symbol The symbol used for the transition. + * @param target The target state where the transition ends. @p target must already exist. + * @param jump_mode Specifies the semantic of jump transitions (transitions with a length greater than 1). + */ + void add_transition_with_target(const State source, const Symbol symbol, const State target, const JumpMode jump_mode = JumpMode::RepeatSymbol) { + add_transition_with_same_level_targets(source, symbol, { target }, jump_mode); + } + + /** + * @brief Add a NFT transition from a source state @p source to a set of target states @p targets + * creating as many inner states as needed for the transition length and the jump mode. + * + * For transitions with a length `l` greater than 1, the common part (before the `l`-th symbol) of the + * is shared for all targets and only at the `l`-th symbol the transition branches to each target. + * + * @param source The source state where the transition begins. @p source must already exist. + * @param symbol The symbol used for the transition. + * @param targets The set of target states where the transition ends. Each target must already exist. + * @param jump_mode Specifies the semantic of jump transitions (transitions with a length greater than 1). + */ + void add_transition_with_same_level_targets(State source, Symbol symbol, const StateSet& targets, JumpMode jump_mode = JumpMode::RepeatSymbol); + /** * @brief Inserts a word, which is created by interleaving parts from @p word_parts_on_levels, into the NFT * from a source state @p source to a target state @p target, creating new states along the path of @p word. diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 2a94c731b..9fd5c2778 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -15,7 +15,7 @@ namespace { using mata::Symbol; using namespace mata::nft; - // Enum class for a state flag to indicate which synchronization + // Enum class for a state flag to indicate which synchronization //types (synchronization on epsilon or symbol) can be reached from the state. enum class SynchronizationType : uint8_t { UNINITIALIZED = 0b0000'0000, ///< Default value. @@ -33,20 +33,91 @@ namespace { } /** - * @brief A struct to hold properties related to synchronization during the composition of NFTs. + * @brief A class to hold properties related to synchronization during the composition of NFTs. * This simplifies the passing of multiple synchronization-related parameters. */ - struct SynchronizationProperties { + class SynchronizationProperties { + public: const Nft& nft; - const std::vector reachable_sync_types; const Level sync_level; const size_t num_of_trans_to_replace_sync; ///< Number of transitions to replace synchronization. + const std::vector reachable_sync_types{}; SynchronizationProperties(const Nft& nft, const Level sync_level, const size_t num_of_trans_to_replace_sync) - : nft(nft), - reachable_sync_types(get_synchronization_types(nft, sync_level)), - sync_level(sync_level), - num_of_trans_to_replace_sync(num_of_trans_to_replace_sync) {} + : nft(nft), sync_level(sync_level), num_of_trans_to_replace_sync(num_of_trans_to_replace_sync) + { + get_synchronization_types(); + } + + private: + /** + * @brief For each state sets a type of synchronization that follows. + */ + void get_synchronization_types() { + const size_t num_of_states = nft.num_of_states(); + std::vector sync_types(num_of_states, SynchronizationType::UNINITIALIZED); + + for (State root = 0; root < num_of_states; ++root) { + if (nft.levels[root] != sync_level) { + continue; // Skip states not on the sync level. + } + + std::stack stack; + stack.push(root); + while (!stack.empty()) { + const State state = stack.top(); + const Level state_level = nft.levels[state]; + const StatePost& state_post = nft.delta[state]; + SynchronizationType current_sync_type = sync_types[state]; + assert(current_sync_type == SynchronizationType::UNINITIALIZED || current_sync_type == SynchronizationType::UNDER_COMPUTATION); + stack.pop(); + + // If it has been visited, than by now we know that it has been already computed for its children. + if (current_sync_type == SynchronizationType::UNDER_COMPUTATION) { + current_sync_type = SynchronizationType::UNINITIALIZED; + for (const SymbolPost& symbol_post : state_post) { + for (const State target : symbol_post.targets) { + current_sync_type |= sync_types[target]; + } + } + sync_types[state] = current_sync_type; + continue; // We already visited its children. + } + + // If we are on the sync level, we can compute the synchronization type. + if (state_level == sync_level) { + if (state_post.find(EPSILON) != state_post.end()) { + current_sync_type = SynchronizationType::ONLY_ON_EPSILON; + if (state_post.size() > 1) { + current_sync_type |= SynchronizationType::ONLY_ON_SYMBOL; + } + } else if (!state_post.empty()) { + current_sync_type = SynchronizationType::ONLY_ON_SYMBOL; + } + sync_types[state] = current_sync_type; + continue; // No need to visit its children. + } + + // We need to visit its children. + sync_types[state] = SynchronizationType::UNDER_COMPUTATION; // Mark this state as under computation. + stack.push(state); // Push it back to the stack to compute it later. + for (const SymbolPost& symbol_post : state_post) { + for (const State target : symbol_post.targets) { + assert(nft.levels[target] > nft.levels[state]); + assert(sync_types[target] != SynchronizationType::UNDER_COMPUTATION); + if (sync_types[target] != SynchronizationType::UNINITIALIZED) { + current_sync_type |= sync_types[target]; + } else { + stack.push(target); // Push the target state to visit it later. + } + } + } + } + } + + assert(std::all_of(sync_types.begin(), sync_types.end(), + [](SynchronizationType type) { return type != SynchronizationType::UNDER_COMPUTATION; })); + } }; // We can perform synchronization only if from both lhs and rhs can be reached a synchronization @@ -63,7 +134,7 @@ namespace { /* ON_EPSILON_AND_SYMBOL */ { false, true, true, true } }; - // We need to wait in the LHS state if there is a synchronization on RHS state and a possibility + // We need to wait in the LHS state if there is a synchronization on RHS state and a possibility // that the LHS can not synchronize on it (e.g. LHS monewhere synchronizes on a symbol). static const bool perform_wait_on_lhs[4][4] = { /* * rhs_sync_type */ @@ -86,152 +157,6 @@ namespace { /* ONLY_ON_EPSILON */ { false, true, false, true }, /* ON_EPSILON_AND_SYMBOL */ { false, true, false, true } }; - - /** - * @brief For each state sets a type of synchronization that follows. - * - * @param nft The NFT to analyze. - * @param sync_level The level for which to compute the synchronization types. - * @return A vector of synchronization types for each state in the NFT. - */ - std::vector get_synchronization_types(const Nft& nft, const size_t sync_level) { - const size_t num_of_states = nft.num_of_states(); - std::vector sync_types(num_of_states, SynchronizationType::UNINITIALIZED); - - for (State root = 0; root < num_of_states; ++root) { - if (nft.levels[root] != sync_level) { - continue; // Skip states not on the sync level. - } - - std::stack stack; - stack.push(root); - while (!stack.empty()) { - const State state = stack.top(); - const Level state_level = nft.levels[state]; - const StatePost& state_post = nft.delta[state]; - SynchronizationType current_sync_type = sync_types[state]; - assert(current_sync_type == SynchronizationType::UNINITIALIZED || current_sync_type == SynchronizationType::UNDER_COMPUTATION); - stack.pop(); - - // If it has been visited, than by now we know that it has been already computed for its children. - if (current_sync_type == SynchronizationType::UNDER_COMPUTATION) { - current_sync_type = SynchronizationType::UNINITIALIZED; - for (const SymbolPost& symbol_post : state_post) { - for (const State target : symbol_post.targets) { - current_sync_type |= sync_types[target]; - } - } - sync_types[state] = current_sync_type; - continue; // We already visited its children. - } - - // If we are on the sync level, we can compute the synchronization type. - if (state_level == sync_level) { - if (state_post.find(EPSILON) != state_post.end()) { - current_sync_type = SynchronizationType::ONLY_ON_EPSILON; - if (state_post.size() > 1) { - current_sync_type |= SynchronizationType::ONLY_ON_SYMBOL; - } - } else if (!state_post.empty()) { - current_sync_type = SynchronizationType::ONLY_ON_SYMBOL; - } - sync_types[state] = current_sync_type; - continue; // No need to visit its children. - } - - // We need to visit its children. - sync_types[state] = SynchronizationType::UNDER_COMPUTATION; // Mark this state as under computation. - stack.push(state); // Push it back to the stack to compute it later. - for (const SymbolPost& symbol_post : state_post) { - for (const State target : symbol_post.targets) { - assert(nft.levels[target] > nft.levels[state]); - assert(sync_types[target] != SynchronizationType::UNDER_COMPUTATION); - if (sync_types[target] != SynchronizationType::UNINITIALIZED) { - current_sync_type |= sync_types[target]; - } else { - stack.push(target); // Push the target state to visit it later. - } - } - } - } - } - - assert(std::all_of(sync_types.begin(), sync_types.end(), - [](SynchronizationType type) { return type != SynchronizationType::UNDER_COMPUTATION; })); - - return sync_types; - } - - /** Add a transition to the NFT from source state over symbol and length len to a new target state. - * - * @param nft The NFT to which the transition will be added. - * @param source The source state of the transition. - * @param symbol The symbol of the transition. - * @param len The length of the transition. - * @param jump_mode The mode of the jump. - * @param pred_map A map to keep track of predecessors for each target state. - * - * @return The target state of the transition. - */ - State add_transition(Nft& nft, const State source, const Symbol symbol, const size_t len, const JumpMode jump_mode){//, PredMap& pred_map) { - if (len == 0) { return source; } - - assert(nft.levels[source] + len <= nft.num_of_levels); - const Level target_level = static_cast((nft.levels[source] + len) % nft.num_of_levels); - const State target = nft.add_state_with_level(target_level); - - if (len == 1 || jump_mode == JumpMode::RepeatSymbol) { - nft.delta.add(source, symbol, target); - return target; - } - - State inner_src = source; - Level inner_level = nft.levels[inner_src] + 1; - for (size_t i = 0; i < len - 1; ++i, ++inner_level) { - assert(inner_level < nft.num_of_levels); - const State inner_target = nft.add_state_with_level(inner_level); - nft.delta.add(inner_src, symbol, inner_target); - inner_src = inner_target; - } - assert(inner_level == nft.levels[source] + len); - nft.delta.add(inner_src, symbol, target); - - return target; - } - - /** - * Add a transition from source state to target state with the given symbol. - * - * @param nft The NFT to which the transition will be added. - * @param source The source state of the transition. - * @param symbol The symbol of the transition. - * @param target The target state of the transition. - * @param jump_mode The mode of the jump transitions. - * @param pred_map A map to keep track of predecessors for each target state. - */ - void add_transition_with_target(Nft& nft, const State source, const Symbol symbol, const State target, const JumpMode jump_mode){//, PredMap& pred_map) { - if (source == target) { return; } - - assert(nft.levels[source] < nft.levels[target] || nft.levels[target] == 0); - const size_t trans_len = (nft.levels[target] == 0 ? nft.num_of_levels : nft.levels[target]) - nft.levels[source]; - assert(trans_len > 0); - - if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { - nft.delta.add(source, symbol, target); - return; - } - - State inner_src = source; - Level inner_level = nft.levels[inner_src] + 1; - for (size_t i = 0; i < trans_len - 1; ++i, ++inner_level) { - assert(inner_level < nft.num_of_levels); - const State inner_target = nft.add_state_with_level(inner_level); - nft.delta.add(inner_src, symbol, inner_target); - inner_src = inner_target; - } - assert(inner_level == nft.levels[source] + trans_len); - nft.delta.add(inner_src, symbol, target); - } } namespace mata::nft @@ -257,23 +182,23 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // If we are waiting on the RHS, we need to know how many epsilon transitions // will replace the synchronization transition in the LHS. // 1) Transitions from RHS will always go as last before the snychronization level. - // 2) If the synchronization level is the last level in the LHS, we need to add + // 2) If the synchronization level is the last level in the LHS, we need to add // all remaining transitions from the RHS that goes after the synchronization level. // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. - const size_t lhs_num_of_trans_to_replace_sync = (rhs_levels_before_sync) + + const size_t lhs_num_of_trans_to_replace_sync = (rhs_levels_before_sync) + (lhs_sync_level == lhs.num_of_levels - 1 ? rhs_levels_after_sync : 0) + - (project_out_sync_levels ? 0 : 1); + (project_out_sync_levels ? 0 : 1); // If we are waiting on the LHS, we need to know how many epsilon transitions // will replace the synchronization transition in the RHS. // 1) Because transtions from LHS always go as first, the only time when they go immediately before the synchronization level // is when the synchronization level is the first level in the RHS. - // 2) LHS transitions go always before RHS transitions, so after the synchronization level, + // 2) LHS transitions go always before RHS transitions, so after the synchronization level, // we know that there will be transitions from the LHS. // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. const size_t rhs_num_of_trans_to_replace_sync = (rhs_sync_level == 0 ? lhs_levels_before_sync : 0) + (lhs_levels_after_sync) + (project_out_sync_levels ? 0 : 1); - + const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level, lhs_num_of_trans_to_replace_sync); const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level, rhs_num_of_trans_to_replace_sync); @@ -328,17 +253,17 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le /** * @brief Model the waiting in one of the NFTs if they could not synchronize due to the EPSILON or symbol transition. - * + * * For example, if there is EPSILON on the synchronization level in the RHS and LHS can not synchronize on it, * then the LHS needs to wait in the root (zero-level) state that precedes the synchronization and RHS will * continue in the corresponding RHS root ower the problematic EPSILON synchronization to the next zero-level state. * The RHS is going to act al if it is interleaving with the LHS transitions (LHS transitions always go before RHS transitions), * however it will place the EPSILON on each such a transition from the LHS. - * + * * The problem is if we want to project out the synchronization levels, but there are not transitions following them * in the LHS not RHS (the synchronization level is the last level in both of them). Then one level before the * synchronization level, we need to connect successors of such states to the zero-level state that lies after the synchronization level. - * + * * @param composition_root_state The root state of the composition NFT. * @param waiting_root_state The root state of the NFT that is waiting (LHS or RHS). * @param running_root_state The root state of the NFT that is running (LHS or RHS). @@ -370,7 +295,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // then we need to add the epsilon transition from the waiting LHS, because transitions from the LHS // goes always before the transitions from the RHS. if (is_lhs_waiting && sync_level != 0 && running_state_level == 0 && lhs_levels_before_sync > 0) { - composition_state = add_transition(result, composition_state, EPSILON, lhs_levels_before_sync, jump_mode); + composition_state = result.add_transition_with_lenght(composition_state, EPSILON, lhs_levels_before_sync, jump_mode); } if (running_state_level < sync_level) { @@ -387,7 +312,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const size_t trans_len = running_target_level - running_state_level; assert(running_target_level > running_state_level); assert(trans_len > 0); - + if (running_target_level == sync_level && handle_sync_in_place) { // We are at the synchronization level after which there will be nothing added. // We need to connect running_state to epsilon successors of the running_target. @@ -395,13 +320,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(epsilon_sync_symbol_post != running_delta[running_target].end()); for (const State epsilon_target : epsilon_sync_symbol_post->targets) { assert(running_levels[epsilon_target] == 0); - add_transition_with_target(result, composition_state, running_symbol_post.symbol, create_composition_state(waiting_root_state, epsilon_target, 0, is_lhs_waiting), jump_mode); - } + result.add_transition_with_target(composition_state, running_symbol_post.symbol, create_composition_state(waiting_root_state, epsilon_target, 0, is_lhs_waiting), jump_mode); + } } else { - // It does not matter if the target is a synchronization level or not + // It does not matter if the target is a synchronization level or not // (we can handle any synchronization later, because it will be replaced // by epsilon transitions). - const State new_composition_state = add_transition(result, composition_state, running_symbol_post.symbol, trans_len, jump_mode); + const State new_composition_state = result.add_transition_with_lenght(composition_state, running_symbol_post.symbol, trans_len, jump_mode); stack.push({ std::move(new_composition_state), running_target }); } } @@ -417,10 +342,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We will add some transitions on the place of the synchronization transition. if (running_levels[epsilon_target] == 0) { // We are connecting directly to the zero-level state. - add_transition_with_target(result, composition_state, EPSILON, create_composition_state(waiting_root_state, epsilon_target, 0, is_lhs_waiting), jump_mode); + result.add_transition_with_target(composition_state, EPSILON, create_composition_state(waiting_root_state, epsilon_target, 0, is_lhs_waiting), jump_mode); } else { // We are connecting to an internal state. Create new auxiliar state for it. - const State new_composition_state = add_transition(result, composition_state, EPSILON, num_of_trans_to_replace_sync, jump_mode); + const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, num_of_trans_to_replace_sync, jump_mode); stack.push({ std::move(new_composition_state), epsilon_target }); } } else { @@ -442,15 +367,15 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We are in the LHS and RHS waits. Because transitions from RHS goes as last, // we need to add epsilon transitions from the waiting RHS after this transition. const size_t trans_len = running_sync_props.nft.num_of_levels - running_levels[running_state]; - const State new_composition_state = add_transition(result, composition_state, symbol_post.symbol, trans_len, jump_mode); - add_transition_with_target(result, new_composition_state, EPSILON, create_composition_state(waiting_root_state, target, 0, is_lhs_waiting), jump_mode); + const State new_composition_state = result.add_transition_with_lenght(composition_state, symbol_post.symbol, trans_len, jump_mode); + result.add_transition_with_target(new_composition_state, EPSILON, create_composition_state(waiting_root_state, target, 0, is_lhs_waiting), jump_mode); } else { // We don't need to add any other transitions, just this one. - add_transition_with_target(result, composition_state, symbol_post.symbol, create_composition_state(waiting_root_state, target, 0, is_lhs_waiting), jump_mode); + result.add_transition_with_target(composition_state, symbol_post.symbol, create_composition_state(waiting_root_state, target, 0, is_lhs_waiting), jump_mode); } } else { // Otherwise just copy the transition. - const State new_composition_state = add_transition(result, composition_state, symbol_post.symbol, target_level - running_state_level, jump_mode); + const State new_composition_state = result.add_transition_with_lenght(composition_state, symbol_post.symbol, target_level - running_state_level, jump_mode); stack.push({new_composition_state, target}); } } @@ -478,13 +403,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (vanish_sync_level) { worklist.push_back({ lhs_sync_target, rhs_sync_target }); } else { - add_transition_with_target(result, composition_source_state, symbol, + result.add_transition_with_target(composition_source_state, symbol, create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); } } } } - + // Do the synchronization on DONT_CARE in the LHS. auto lhs_dont_care_sync_it = lhs.delta[lhs_state].find(DONT_CARE); if (lhs_dont_care_sync_it != lhs.delta[lhs_state].end()) { @@ -500,7 +425,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (vanish_sync_level) { worklist.push_back({ lhs_sync_target, rhs_sync_target }); } else { - add_transition_with_target(result, composition_source_state, symbol, + result.add_transition_with_target(composition_source_state, symbol, create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); } } @@ -523,7 +448,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (vanish_sync_level) { worklist.push_back({ lhs_sync_target, rhs_sync_target }); } else { - add_transition_with_target(result, composition_source_state, symbol, + result.add_transition_with_target(composition_source_state, symbol, create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); } } @@ -569,7 +494,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (perform_wait_on_lhs[lhs_sync_type_id][rhs_sync_type_id]) { model_waiting(composition_state, lhs_state, rhs_state, true); // LHS is waiting. } - + if (perform_wait_on_rhs[lhs_sync_type_id][rhs_sync_type_id]) { model_waiting(composition_state, rhs_state, lhs_state, false); // RHS is waiting. } @@ -597,11 +522,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Level lhs_target_level = lhs.levels[lhs_target]; const size_t trans_len = lhs_target_level - lhs_level; assert(lhs_target_level > lhs_level); - + // We don't need to worry, if it's a last useful transition, and // doint the synchronization in place, because we are sure that there is/will be // at leas one unprocessed useful transition in the RHS. - add_transition_with_target(result, composition_state, lhs_symbol_post.symbol, + result.add_transition_with_target(composition_state, lhs_symbol_post.symbol, create_composition_state(lhs_target, rhs_state, composition_state_level + trans_len, true), jump_mode); } } @@ -619,7 +544,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Level rhs_target_level = rhs.levels[rhs_target]; const size_t trans_len = rhs_target_level - rhs_level; assert(rhs_target_level > rhs_level); - + const bool lhs_level_is_last = lhs_level == lhs.num_of_levels - 1; const bool rhs_target_level_is_last = rhs_target_level == rhs.num_of_levels - 1; if (lhs_level_is_last && rhs_target_level_is_last && project_out_sync_levels) { @@ -630,7 +555,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le synchronize(lhs_state, rhs_target, composition_state, true, rhs_symbol_post.symbol); } else { // Just copy the transition. - add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, + result.add_transition_with_target(composition_state, rhs_symbol_post.symbol, create_composition_state(lhs_state, rhs_target, composition_state_level + trans_len, false), jump_mode); } } @@ -648,7 +573,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Level lhs_target_level = lhs.levels[lhs_target]; const size_t trans_len = (lhs_target_level == 0 ? lhs.num_of_levels : lhs_target_level) - lhs_level; assert(lhs_target_level > lhs_level || lhs_target_level == 0); - add_transition_with_target(result, composition_state, lhs_symbol_post.symbol, + result.add_transition_with_target(composition_state, lhs_symbol_post.symbol, create_composition_state(lhs_target, rhs_state, composition_state_level + trans_len, true), jump_mode); } } @@ -661,13 +586,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Level rhs_target_level = rhs.levels[rhs_target]; const size_t trans_len = (rhs_target_level == 0 ? rhs.num_of_levels : rhs_target_level) - rhs_level; assert(rhs_target_level > rhs_level || rhs_target_level == 0); - add_transition_with_target(result, composition_state, rhs_symbol_post.symbol, + result.add_transition_with_target(composition_state, rhs_symbol_post.symbol, create_composition_state(lhs_state, rhs_target, composition_state_level + trans_len, false), jump_mode); - + } } } - + } return result.trim(); // Trim the result NFT to remove dead-end paths. diff --git a/src/nft/nft.cc b/src/nft/nft.cc index 7d80d5986..358c56162 100644 --- a/src/nft/nft.cc +++ b/src/nft/nft.cc @@ -547,6 +547,94 @@ State Nft::add_transition(const State source, const std::vector& symbols return insert_word(source, symbols); } +State Nft::add_transition_with_lenght(const State source, const Symbol symbol, const size_t length, const JumpMode jump_mode = JumpMode::RepeatSymbol) { + assert(source < num_of_states()); + + if (length == 0) { return source; } + + assert(levels[source] + length <= num_of_levels); + const Level target_level = static_cast((levels[source] + length) % num_of_levels); + const State target = add_state_with_level(target_level); + + if (length == 1 || jump_mode == JumpMode::RepeatSymbol) { + delta.add(source, symbol, target); + return target; + } + + State inner_src = source; + Level inner_level = levels[inner_src] + 1; + for (size_t i = 0; i < length - 1; ++i, ++inner_level) { + assert(inner_level < num_of_levels); + const State inner_target = add_state_with_level(inner_level); + delta.add(inner_src, symbol, inner_target); + inner_src = inner_target; + } + assert(inner_level == levels[source] + length); + delta.add(inner_src, symbol, target); + + return target; +} + +void Nft::add_transition_with_target(State source, Symbol symbol, State target, JumpMode jump_mode = JumpMode::RepeatSymbol) { + assert(source < num_of_states()); + assert(target < num_of_states()); + + assert(levels[source] < levels[target] || levels[target] == 0); + const size_t trans_len = (levels[target] == 0 ? num_of_levels : levels[target]) - levels[source]; + + if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { + delta.add(source, symbol, target); + return; + } + + State inner_src = source; + Level inner_level = levels[inner_src] + 1; + for (size_t i = 0; i < trans_len - 1; ++i, ++inner_level) { + assert(inner_level < num_of_levels); + const State inner_target = add_state_with_level(inner_level); + delta.add(inner_src, symbol, inner_target); + inner_src = inner_target; + } + assert(inner_level == levels[source] + trans_len); + delta.add(inner_src, symbol, target); +} + +void Nft::add_transition_with_same_level_targets(State source, Symbol symbol, const StateSet& targets, JumpMode jump_mode = JumpMode::RepeatSymbol) { + assert(targets.size() > 0); + assert(source < num_of_states()); + assert(std::all_of(targets.begin(), targets.end(), [&](State target) { return target < num_of_states(); })); + + const Level target_level = levels[targets.front()]; + const size_t trans_len = (target_level == 0 ? num_of_levels : target_level) - levels[source]; + assert(std::all_of(targets.begin(), targets.end(), [&](State target) { return levels[target] == target_level; })); + assert(target_level == 0 || target_level > levels[source]); + assert(trans_len > 0); + + if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { + StatePost& mutable_state_post = delta.mutable_state_post(source); + auto mutable_symbol_post_it = mutable_state_post.find(symbol); + if (mutable_symbol_post_it == mutable_state_post.end()) { + mutable_state_post.insert(std::move(SymbolPost(symbol, targets))); + return; + } + mutable_symbol_post_it->targets.insert(targets); + return; + } + + State inner_src = source; + Level inner_level = levels[inner_src] + 1; + for (size_t i = 0; i < trans_len - 1; ++i, ++inner_level) { + assert(inner_level < num_of_levels); + const State inner_target = add_state_with_level(inner_level); + delta.add(inner_src, symbol, inner_target); + inner_src = inner_target; + } + assert(inner_level == levels[source] + trans_len); + StatePost& mutable_inner_state_post = delta.mutable_state_post(inner_src); + assert(mutable_inner_state_post.find(symbol) == mutable_inner_state_post.end()); + mutable_inner_state_post.insert(std::move(SymbolPost(symbol, targets))); +} + Nft& Nft::insert_identity(const State state, const std::vector &symbols, const JumpMode jump_mode) { for (const Symbol symbol: symbols) { insert_identity(state, symbol, jump_mode); } return *this; From 0b359b41897808f9ef3046d2931d196ca3fbbf7a Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 14 Aug 2025 18:56:23 +0200 Subject: [PATCH 28/65] deduplicated --- src/nft/composition.cc | 214 ++++++++++++++++++++++------------------- 1 file changed, 114 insertions(+), 100 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 9fd5c2778..2da09ad87 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -31,6 +31,9 @@ namespace { lhs = lhs | rhs; return lhs; } + inline bool can_synchronize(const SynchronizationType lhs, const SynchronizationType rhs) { + return static_cast(lhs) & static_cast(rhs); + } /** * @brief A class to hold properties related to synchronization during the composition of NFTs. @@ -201,6 +204,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level, lhs_num_of_trans_to_replace_sync); const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level, rhs_num_of_trans_to_replace_sync); + const std::vector& lhs_reachable_sync_types = lhs_sync_props.reachable_sync_types; + const std::vector& rhs_reachable_sync_types = rhs_sync_props.reachable_sync_types; Nft result; result.num_of_levels = result_num_of_levels; @@ -390,16 +395,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Level composition_target_level = (result.levels[composition_source_state] + 1) % result.num_of_levels; const bool vanish_sync_level = !reconnect && project_out_sync_levels; - // Do the normal synchronization on symbol. - mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); - mata::utils::push_back(sync_iterator, lhs.delta[lhs_state]); - mata::utils::push_back(sync_iterator, rhs.delta[rhs_state]); - while (sync_iterator.advance()) { - const std::vector& same_symbol_posts{ sync_iterator.get_current() }; - assert(same_symbol_posts.size() == 2); // One move per state in the pair. - const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : same_symbol_posts[0]->symbol; - for (const State lhs_sync_target : same_symbol_posts[0]->targets) { - for (const State rhs_sync_target : same_symbol_posts[1]->targets) { + auto synchronize_targets = [&](const StateSet& lhs_sync_targets, const StateSet& rhs_sync_targets, const Symbol symbol) { + for (const State lhs_sync_target : lhs_sync_targets) { + for (const State rhs_sync_target : rhs_sync_targets) { if (vanish_sync_level) { worklist.push_back({ lhs_sync_target, rhs_sync_target }); } else { @@ -408,6 +406,17 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } } } + }; + + // Do the normal synchronization on symbol. + mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); + mata::utils::push_back(sync_iterator, lhs.delta[lhs_state]); + mata::utils::push_back(sync_iterator, rhs.delta[rhs_state]); + while (sync_iterator.advance()) { + const std::vector& same_symbol_posts{ sync_iterator.get_current() }; + assert(same_symbol_posts.size() == 2); // One move per state in the pair. + const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : same_symbol_posts[0]->symbol; + synchronize_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); } // Do the synchronization on DONT_CARE in the LHS. @@ -419,17 +428,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le continue; } const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : rhs_symbol_post.symbol; - for (const State rhs_sync_target : rhs_symbol_post.targets) { - for (const State lhs_sync_target : lhs_dont_care_sync_it->targets) { - // Add the transition from the LHS DONT_CARE to the RHS target. - if (vanish_sync_level) { - worklist.push_back({ lhs_sync_target, rhs_sync_target }); - } else { - result.add_transition_with_target(composition_source_state, symbol, - create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); - } - } - } + synchronize_targets(lhs_dont_care_sync_it->targets, rhs_symbol_post.targets, symbol); } } @@ -442,16 +441,48 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le continue; } const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : lhs_symbol_post.symbol; - for (const State lhs_sync_target : lhs_symbol_post.targets) { - for (const State rhs_sync_target : rhs_dont_care_sync_it->targets) { - // Add the transition from the RHS DONT_CARE to the LHS target. - if (vanish_sync_level) { - worklist.push_back({ lhs_sync_target, rhs_sync_target }); - } else { - result.add_transition_with_target(composition_source_state, symbol, - create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); - } - } + synchronize_targets(lhs_symbol_post.targets, rhs_dont_care_sync_it->targets, symbol); + } + } + }; + + auto copy_transition = [&](const State composition_state, const State copy_state, const State stationar_state, const bool is_symbol_post_lhs) { + const SynchronizationProperties& copy_sync_props = is_symbol_post_lhs ? lhs_sync_props : rhs_sync_props; + const SynchronizationProperties& stationar_sync_props = is_symbol_post_lhs ? rhs_sync_props : lhs_sync_props; + const Nft& copy_nft = copy_sync_props.nft; + const Level copy_state_level = copy_nft.levels[copy_state]; + const Level composition_state_level = result.levels[composition_state]; + const std::vector& copy_reachable_sync_types = copy_sync_props.reachable_sync_types; + const SynchronizationType stationar_state_sync_type = stationar_sync_props.reachable_sync_types[stationar_state]; + bool is_copy_state_before_sync = copy_state_level < copy_sync_props.sync_level; + + for (const SymbolPost& symbol_post : copy_nft.delta[copy_state]) { + for (const State target : symbol_post.targets) { + + if (is_copy_state_before_sync && !can_synchronize(stationar_state_sync_type, copy_reachable_sync_types[target])) { + // We are still before the synchronization level and + // the target does not lead to a synchronization. + // We don't need to explore this. + continue; + } + + const Level target_level = copy_nft.levels[target]; + const size_t trans_len = (target_level == 0 ? copy_nft.num_of_levels : target_level) - copy_state_level; + assert(target_level == 0 || target_level > copy_state_level); + assert(trans_len > 0); + + const bool rhs_target_level_is_last = target_level == rhs.num_of_levels - 1; + const bool lhs_level_is_last = copy_state_level == lhs.num_of_levels - 1; + if (!is_symbol_post_lhs && is_copy_state_before_sync && rhs_target_level_is_last && lhs_level_is_last && project_out_sync_levels) { + // LHS is at the synchronization level. There is no transition after the synchronization in LHS. + // The target in RHS is at synchronization level, at there is also no transition after the synchronization in RHS. + // Also we are projecting out the synchronization levels. + // This means, that we need now to synchronize and make a transition to the zero-level state in the result NFT. + synchronize(stationar_state, target, composition_state, true, symbol_post.symbol); + } else { + const Level new_composition_state_level = (composition_state_level + trans_len) % result.num_of_levels; + result.add_transition_with_target(composition_state, symbol_post.symbol, + create_composition_state(target, stationar_state, new_composition_state_level, is_symbol_post_lhs), jump_mode); } } } @@ -466,9 +497,6 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } } - const std::vector& lhs_reachable_sync_types = lhs_sync_props.reachable_sync_types; - const std::vector& rhs_reachable_sync_types = rhs_sync_props.reachable_sync_types; - while (!worklist.empty()) { const auto [lhs_state, rhs_state] = worklist.front(); worklist.pop_front(); @@ -511,86 +539,72 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (is_lhs_before_sync) { // LHS is before the synchronization level, so we need to first get there. - for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { - for (const State lhs_target : lhs_symbol_post.targets) { - const size_t lhs_target_sync_type_id = static_cast(lhs_reachable_sync_types[lhs_target]); - if (!perform_synchronization[lhs_sync_type_id][lhs_target_sync_type_id]) { - // The target does not lead to a synchronization. - // We don't need to explore this. - continue; - } - const Level lhs_target_level = lhs.levels[lhs_target]; - const size_t trans_len = lhs_target_level - lhs_level; - assert(lhs_target_level > lhs_level); - - // We don't need to worry, if it's a last useful transition, and - // doint the synchronization in place, because we are sure that there is/will be - // at leas one unprocessed useful transition in the RHS. - result.add_transition_with_target(composition_state, lhs_symbol_post.symbol, - create_composition_state(lhs_target, rhs_state, composition_state_level + trans_len, true), jump_mode); - } - } + copy_transition(composition_state, lhs_state, rhs_state, true); + // for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { + // for (const State lhs_target : lhs_symbol_post.targets) { + // const size_t lhs_target_sync_type_id = static_cast(lhs_reachable_sync_types[lhs_target]); + // if (!perform_synchronization[lhs_sync_type_id][lhs_target_sync_type_id]) { + // // The target does not lead to a synchronization. + // // We don't need to explore this. + // continue; + // } + // const Level lhs_target_level = lhs.levels[lhs_target]; + // const size_t trans_len = lhs_target_level - lhs_level; + // assert(lhs_target_level > lhs_level); + + // // We don't need to worry, if it's a last useful transition, and + // // doint the synchronization in place, because we are sure that there is/will be + // // at leas one unprocessed useful transition in the RHS. + // result.add_transition_with_target(composition_state, lhs_symbol_post.symbol, + // create_composition_state(lhs_target, rhs_state, composition_state_level + trans_len, true), jump_mode); + // } + // } } else if (is_rhs_before_sync) { // LHS is at synchronization level, but RHS is before the synchronization level. // RHS needs to continue. - for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { - for (const State rhs_target : rhs_symbol_post.targets) { - const size_t rhs_target_sync_type_id = static_cast(rhs_reachable_sync_types[rhs_target]); - if (!perform_synchronization[rhs_sync_type_id][rhs_target_sync_type_id]) { - // The target does not lead to a synchronization. - // We don't need to explore this. - continue; - } - const Level rhs_target_level = rhs.levels[rhs_target]; - const size_t trans_len = rhs_target_level - rhs_level; - assert(rhs_target_level > rhs_level); - - const bool lhs_level_is_last = lhs_level == lhs.num_of_levels - 1; - const bool rhs_target_level_is_last = rhs_target_level == rhs.num_of_levels - 1; - if (lhs_level_is_last && rhs_target_level_is_last && project_out_sync_levels) { - // LHS is at the synchronization level. There is no transition after the synchronization in LHS. - // The target in RHS is at synchronization level, at there is also no transition after the synchronization in RHS. - // Also we are projecting out the synchronization levels. - // This means, that we need now to synchronize and make a transition to the zero-level state in the result NFT. - synchronize(lhs_state, rhs_target, composition_state, true, rhs_symbol_post.symbol); - } else { - // Just copy the transition. - result.add_transition_with_target(composition_state, rhs_symbol_post.symbol, - create_composition_state(lhs_state, rhs_target, composition_state_level + trans_len, false), jump_mode); - } - } - } + copy_transition(composition_state, rhs_state, lhs_state, false); + // for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { + // for (const State rhs_target : rhs_symbol_post.targets) { + // const size_t rhs_target_sync_type_id = static_cast(rhs_reachable_sync_types[rhs_target]); + // if (!perform_synchronization[rhs_sync_type_id][rhs_target_sync_type_id]) { + // // The target does not lead to a synchronization. + // // We don't need to explore this. + // continue; + // } + // const Level rhs_target_level = rhs.levels[rhs_target]; + // const size_t trans_len = rhs_target_level - rhs_level; + // assert(rhs_target_level > rhs_level); + + // const bool lhs_level_is_last = lhs_level == lhs.num_of_levels - 1; + // const bool rhs_target_level_is_last = rhs_target_level == rhs.num_of_levels - 1; + // if (lhs_level_is_last && rhs_target_level_is_last && project_out_sync_levels) { + // // LHS is at the synchronization level. There is no transition after the synchronization in LHS. + // // The target in RHS is at synchronization level, at there is also no transition after the synchronization in RHS. + // // Also we are projecting out the synchronization levels. + // // This means, that we need now to synchronize and make a transition to the zero-level state in the result NFT. + // synchronize(lhs_state, rhs_target, composition_state, true, rhs_symbol_post.symbol); + // } else { + // // Just copy the transition. + // result.add_transition_with_target(composition_state, rhs_symbol_post.symbol, + // create_composition_state(lhs_state, rhs_target, composition_state_level + trans_len, false), jump_mode); + // } + // } + // } } else if (lhs_level == lhs_sync_level && rhs_level == rhs_sync_level) { // We are at the synchronization level in both NFTs. assert(lhs_level != lhs.num_of_levels - 1 && rhs_level != rhs.num_of_levels - 1); - synchronize(lhs_state, rhs_state, composition_state); + synchronize(lhs_state, rhs_state, composition_state); } else if (lhs_level != 0) { // LHS is past the synchronization level and there are some transitions remaining to remap. assert(!is_lhs_before_sync && !is_rhs_before_sync); - for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { - for (const State lhs_target : lhs_symbol_post.targets) { - const Level lhs_target_level = lhs.levels[lhs_target]; - const size_t trans_len = (lhs_target_level == 0 ? lhs.num_of_levels : lhs_target_level) - lhs_level; - assert(lhs_target_level > lhs_level || lhs_target_level == 0); - result.add_transition_with_target(composition_state, lhs_symbol_post.symbol, - create_composition_state(lhs_target, rhs_state, composition_state_level + trans_len, true), jump_mode); - } - } + copy_transition(composition_state, lhs_state, rhs_state, true); + } else { assert(!is_lhs_before_sync && !is_rhs_before_sync); assert(lhs_level == 0 && rhs_level != 0); // RHS is past the synchronization level and there are some transitions remaining to remap. - for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { - for (const State rhs_target : rhs_symbol_post.targets) { - const Level rhs_target_level = rhs.levels[rhs_target]; - const size_t trans_len = (rhs_target_level == 0 ? rhs.num_of_levels : rhs_target_level) - rhs_level; - assert(rhs_target_level > rhs_level || rhs_target_level == 0); - result.add_transition_with_target(composition_state, rhs_symbol_post.symbol, - create_composition_state(lhs_state, rhs_target, composition_state_level + trans_len, false), jump_mode); - - } - } + copy_transition(composition_state, rhs_state, lhs_state, false); } } From aaa808d9c0bc58b74a4ed4aed2d484b8775b5b8d Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 15 Aug 2025 13:16:42 +0200 Subject: [PATCH 29/65] simplify main loop --- src/nft/composition.cc | 204 ++++++++++++++--------------------------- 1 file changed, 67 insertions(+), 137 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 2da09ad87..7db2fcd2a 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -31,9 +31,6 @@ namespace { lhs = lhs | rhs; return lhs; } - inline bool can_synchronize(const SynchronizationType lhs, const SynchronizationType rhs) { - return static_cast(lhs) & static_cast(rhs); - } /** * @brief A class to hold properties related to synchronization during the composition of NFTs. @@ -122,44 +119,6 @@ namespace { [](SynchronizationType type) { return type != SynchronizationType::UNDER_COMPUTATION; })); } }; - - // We can perform synchronization only if from both lhs and rhs can be reached a synchronization - // level, that synchronizes on EPSILON and/or symbol. - // For example, there is not way that we can synchronize when only synchronization on EPSILON in lhs - // and only on symbol in rhs is possible. - static const bool perform_synchronization[4][4] = { - /* * rhs_sync_type */ - /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ - /* =======================*========================================================================= */ - /* UNINITIALIZED */ { false, false, false, false }, - /* ONLY_ON_SYMBOL */ { false, true, false, true }, - /* ONLY_ON_EPSILON */ { false, false, true, true }, - /* ON_EPSILON_AND_SYMBOL */ { false, true, true, true } - }; - - // We need to wait in the LHS state if there is a synchronization on RHS state and a possibility - // that the LHS can not synchronize on it (e.g. LHS monewhere synchronizes on a symbol). - static const bool perform_wait_on_lhs[4][4] = { - /* * rhs_sync_type */ - /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ - /* =======================*========================================================================= */ - /* UNINITIALIZED */ { false, false, false, false }, - /* ONLY_ON_SYMBOL */ { false, false, true, true }, - /* ONLY_ON_EPSILON */ { false, false, false, false }, - /* ON_EPSILON_AND_SYMBOL */ { false, false, true, true } - }; - - // We need to wait in the RHS state if there is a synchronization on LHS state and a possibility - // that the RHS can not synchronize on it (e.g. RHS monewhere synchronizes on a symbol). - static const bool perform_wait_on_rhs[4][4] = { - /* * rhs_sync_type */ - /* lhs_sync_type * UNINITIALIZED | ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL */ - /* =======================*========================================================================= */ - /* UNINITIALIZED */ { false, false, false, false }, - /* ONLY_ON_SYMBOL */ { false, false, false, false }, - /* ONLY_ON_EPSILON */ { false, true, false, true }, - /* ON_EPSILON_AND_SYMBOL */ { false, true, false, true } - }; } namespace mata::nft @@ -458,8 +417,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le for (const SymbolPost& symbol_post : copy_nft.delta[copy_state]) { for (const State target : symbol_post.targets) { + const SynchronizationType target_sync_type = copy_reachable_sync_types[target]; + const bool can_synchronize_in_the_future = stationar_state_sync_type == target_sync_type || + stationar_state_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || + target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL; - if (is_copy_state_before_sync && !can_synchronize(stationar_state_sync_type, copy_reachable_sync_types[target])) { + if (is_copy_state_before_sync && can_synchronize_in_the_future) { // We are still before the synchronization level and // the target does not lead to a synchronization. // We don't need to explore this. @@ -488,125 +451,92 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } }; - // INITIALIZATION of a worklist (side effect of a create_composition_state) + // Initialization of the worklist. for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { // Get the root state in the result NFT - const State res_root = create_composition_state(lhs_root, rhs_root, 0); - result.initial.insert(res_root); + result.initial.insert(create_composition_state(lhs_root, rhs_root, 0)); } } + // The maing loop of the composition algorithm. while (!worklist.empty()) { const auto [lhs_state, rhs_state] = worklist.front(); worklist.pop_front(); const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; const State composition_state = composition_storage.get(lhs_state, rhs_state); - assert(composition_state != Limits::max_state); - const Level composition_state_level = result.levels[composition_state]; - const size_t lhs_sync_type_id = static_cast(lhs_reachable_sync_types[lhs_state]); - const size_t rhs_sync_type_id = static_cast(rhs_reachable_sync_types[rhs_state]); - // LHS can be before the synchronization level only if RHS is before the synchronization level as well. - // Otherwise, there are both past the synchronization level. - const bool is_lhs_before_sync = (lhs_level != 0 && rhs_level == 0) && lhs_level < lhs_sync_level; - const bool is_rhs_before_sync = (lhs_level <= lhs_sync_level) && rhs_level < rhs_sync_level; - + assert(composition_state != Limits::max_state); + + const SynchronizationType lhs_sync_type = lhs_reachable_sync_types[lhs_state]; + const SynchronizationType rhs_sync_type = rhs_reachable_sync_types[rhs_state]; if (lhs_level == 0 and rhs_level == 0) { - // We are at the zero level states. + // We are at the zero level states before any synchronization level. // It is now time to decide is we want to continue the synchronization and/or - // to wait in the lhs_state and/or rhs_state, because we can not synchronize - // on epsilon and on a symbol at the same time. - // Note: Transduce that contains symbol that can not synchronize with epsilon will wait. - - if (perform_wait_on_lhs[lhs_sync_type_id][rhs_sync_type_id]) { - model_waiting(composition_state, lhs_state, rhs_state, true); // LHS is waiting. + // to wait in the lhs_state and/or rhs_state, because there is a synchronization, + // where on one of the transducers is EPSILON and on the other one is an alphabet symbol. + // Note that if we can not synchronize in the future due to the EPSILON, + // we will simulate waiting in the zero-level state of the transducer that does not + // have the EPSILON transition that cause the synchronization problem. + + // You can see the table defining operations for pair of synchronization types + // in the documentation of the SynchronizationType enum class. + const bool perform_wait_on_lhs = lhs_sync_type != SynchronizationType::ONLY_ON_EPSILON && + rhs_sync_type != SynchronizationType::ONLY_ON_SYMBOL; + const bool perform_wait_on_rhs = lhs_sync_type != SynchronizationType::ONLY_ON_SYMBOL && + rhs_sync_type != SynchronizationType::ONLY_ON_EPSILON; + const bool can_synchronize_in_the_future = lhs_sync_type == rhs_sync_type || + lhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || + rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL; + if (perform_wait_on_lhs) { + // LHS is waiting (i.e., there is an EPSILON in the RHS that we can not synchronize on). + model_waiting(composition_state, lhs_state, rhs_state, true); } - - if (perform_wait_on_rhs[lhs_sync_type_id][rhs_sync_type_id]) { - model_waiting(composition_state, rhs_state, lhs_state, false); // RHS is waiting. + if (perform_wait_on_rhs) { + // RHS is waiting (i.e., there is an EPSILON in the LHS that we can not synchronize on). + model_waiting(composition_state, rhs_state, lhs_state, false); } - - if (!perform_synchronization[lhs_sync_type_id][rhs_sync_type_id]) { - // No synchronization is needed. - // There are no symbols that would synchronize. - // There is total sismatch between lhs and rhs synchronization symbols. + if (!can_synchronize_in_the_future) { + // There is no way we would be able to synchronize in the future. + // It does not make sence to continue exploration of this pair. continue; } } - assert(perform_synchronization[lhs_sync_type_id][rhs_sync_type_id]); - - if (is_lhs_before_sync) { - // LHS is before the synchronization level, so we need to first get there. + // It makes sence to onyl continue if we beleave that we can synchronize, + // or we already passed the synchronization level (i.e., lhs_sync_type == + // rhs_sync_type == SynchronizationType::UNINITIALIZED). + assert(lhs_sync_type == rhs_sync_type || + lhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || + rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL); + + // Both LHS and RHS can do a step if they are not at the synchronization level. + // However, there is a special case when LHS already reached the last zero-level state, + // because there was no transition after the synchronization level in the LHS. Because + // of that, we need to not allow LHS to do a step in this case. + // The same does not apply to the RHS, because it always goes after the LHS. + const bool do_step_in_lhs = lhs_level != lhs_sync_level && (lhs_level != 0 || rhs_level == 0); + const bool do_step_in_rhs = rhs_level != rhs_sync_level; + + if (do_step_in_lhs) { + // LHS alway goes before the RHS. copy_transition(composition_state, lhs_state, rhs_state, true); - // for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { - // for (const State lhs_target : lhs_symbol_post.targets) { - // const size_t lhs_target_sync_type_id = static_cast(lhs_reachable_sync_types[lhs_target]); - // if (!perform_synchronization[lhs_sync_type_id][lhs_target_sync_type_id]) { - // // The target does not lead to a synchronization. - // // We don't need to explore this. - // continue; - // } - // const Level lhs_target_level = lhs.levels[lhs_target]; - // const size_t trans_len = lhs_target_level - lhs_level; - // assert(lhs_target_level > lhs_level); - - // // We don't need to worry, if it's a last useful transition, and - // // doint the synchronization in place, because we are sure that there is/will be - // // at leas one unprocessed useful transition in the RHS. - // result.add_transition_with_target(composition_state, lhs_symbol_post.symbol, - // create_composition_state(lhs_target, rhs_state, composition_state_level + trans_len, true), jump_mode); - // } - // } - } else if (is_rhs_before_sync) { - // LHS is at synchronization level, but RHS is before the synchronization level. - // RHS needs to continue. + } else if (do_step_in_rhs) { + // LHS is probably at the synchronization level or at the final zero-level state. + // It's time for RHS to make a step. copy_transition(composition_state, rhs_state, lhs_state, false); - // for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { - // for (const State rhs_target : rhs_symbol_post.targets) { - // const size_t rhs_target_sync_type_id = static_cast(rhs_reachable_sync_types[rhs_target]); - // if (!perform_synchronization[rhs_sync_type_id][rhs_target_sync_type_id]) { - // // The target does not lead to a synchronization. - // // We don't need to explore this. - // continue; - // } - // const Level rhs_target_level = rhs.levels[rhs_target]; - // const size_t trans_len = rhs_target_level - rhs_level; - // assert(rhs_target_level > rhs_level); - - // const bool lhs_level_is_last = lhs_level == lhs.num_of_levels - 1; - // const bool rhs_target_level_is_last = rhs_target_level == rhs.num_of_levels - 1; - // if (lhs_level_is_last && rhs_target_level_is_last && project_out_sync_levels) { - // // LHS is at the synchronization level. There is no transition after the synchronization in LHS. - // // The target in RHS is at synchronization level, at there is also no transition after the synchronization in RHS. - // // Also we are projecting out the synchronization levels. - // // This means, that we need now to synchronize and make a transition to the zero-level state in the result NFT. - // synchronize(lhs_state, rhs_target, composition_state, true, rhs_symbol_post.symbol); - // } else { - // // Just copy the transition. - // result.add_transition_with_target(composition_state, rhs_symbol_post.symbol, - // create_composition_state(lhs_state, rhs_target, composition_state_level + trans_len, false), jump_mode); - // } - // } - // } - - } else if (lhs_level == lhs_sync_level && rhs_level == rhs_sync_level) { - // We are at the synchronization level in both NFTs. - assert(lhs_level != lhs.num_of_levels - 1 && rhs_level != rhs.num_of_levels - 1); - synchronize(lhs_state, rhs_state, composition_state); - } else if (lhs_level != 0) { - // LHS is past the synchronization level and there are some transitions remaining to remap. - assert(!is_lhs_before_sync && !is_rhs_before_sync); - copy_transition(composition_state, lhs_state, rhs_state, true); - } else { - assert(!is_lhs_before_sync && !is_rhs_before_sync); - assert(lhs_level == 0 && rhs_level != 0); - // RHS is past the synchronization level and there are some transitions remaining to remap. - copy_transition(composition_state, rhs_state, lhs_state, false); + // Both LHS and RHS are at the synchronization level. + // Perform the synchronization this way is only possible if: + // 1) Some transition in the LHS or RHS that follows after + // the synchronization level (so we can connect to the successors), or + // 2) There is not transition in the LHS or RHS that follows after + // the synchronization level, but we are not projecting out the + // synchronization levels. We need to use the synchronization + // transition to connect to the zero-level successors of the synchronization. + assert(lhs_level == lhs_sync_level && rhs_level == rhs_sync_level); + synchronize(lhs_state, rhs_state, composition_state); } - } return result.trim(); // Trim the result NFT to remove dead-end paths. From c70012f2b02599fcfb15578520ff93cf1544426b Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 15 Aug 2025 15:24:26 +0200 Subject: [PATCH 30/65] copy_transition lambda function done --- src/nft/composition.cc | 164 +++++++++++++++++++++++++++++------------ 1 file changed, 118 insertions(+), 46 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 7db2fcd2a..d97622df1 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -40,11 +40,17 @@ namespace { public: const Nft& nft; const Level sync_level; + const size_t num_of_levels_before_sync; + const size_t num_of_levels_after_sync; const size_t num_of_trans_to_replace_sync; ///< Number of transitions to replace synchronization. - const std::vector reachable_sync_types{}; + const std::vector sync_types_v{}; SynchronizationProperties(const Nft& nft, const Level sync_level, const size_t num_of_trans_to_replace_sync) - : nft(nft), sync_level(sync_level), num_of_trans_to_replace_sync(num_of_trans_to_replace_sync) + : nft(nft), + sync_level(sync_level), + num_of_levels_before_sync(sync_level), + num_of_levels_after_sync(nft.num_of_levels - sync_level - 1), + num_of_trans_to_replace_sync(num_of_trans_to_replace_sync) { get_synchronization_types(); } @@ -163,8 +169,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level, lhs_num_of_trans_to_replace_sync); const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level, rhs_num_of_trans_to_replace_sync); - const std::vector& lhs_reachable_sync_types = lhs_sync_props.reachable_sync_types; - const std::vector& rhs_reachable_sync_types = rhs_sync_props.reachable_sync_types; + const std::vector& lhs_sync_types_v = lhs_sync_props.sync_types_v; + const std::vector& rhs_sync_types_v = rhs_sync_props.sync_types_v; Nft result; result.num_of_levels = result_num_of_levels; @@ -233,13 +239,17 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le * @param running_root_state The root state of the NFT that is running (LHS or RHS). * @param is_lhs_waiting If true, the LHS is waiting, otherwise the RHS is waiting. */ - auto model_waiting = [&](const State composition_root_state, const State waiting_root_state, const State running_root_state, const bool is_lhs_waiting) { - const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props : lhs_sync_props; + auto model_waiting = [&](const State waiting_root_state, const State running_root_state, const bool is_lhs_waiting) { + const State composition_root_state = is_lhs_waiting ? composition_storage.get(waiting_root_state, running_root_state) + : composition_storage.get(running_root_state, waiting_root_state); + assert(composition_root_state != Limits::max_state); + const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props + : lhs_sync_props; const Level sync_level = running_sync_props.sync_level; const Levels& running_levels = running_sync_props.nft.levels; const Delta& running_delta = running_sync_props.nft.delta; const size_t num_of_trans_to_replace_sync = running_sync_props.num_of_trans_to_replace_sync; - const std::vector& reachable_sync_types = running_sync_props.reachable_sync_types; + const std::vector& reachable_sync_types = running_sync_props.sync_types_v; // If the synchronization level is the last level, and it cannot be replaced by any sequence of epsilon transitions // (i.e., it wanishes), we need to do "in place" synchronization step and connect previous state to the next // zero-level state. @@ -348,7 +358,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } }; - auto synchronize = [&](const State lhs_state, const State rhs_state, const State composition_source_state, const bool reconnect = false, const Symbol symbol_when_reconnect = Limits::max_symbol) { + auto synchronize = [&](const State composition_source_state, const State lhs_state, const State rhs_state, const bool reconnect = false, const Symbol symbol_when_reconnect = Limits::max_symbol) { + assert(composition_source_state != Limits::max_state); const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; const Level composition_target_level = (result.levels[composition_source_state] + 1) % result.num_of_levels; @@ -405,47 +416,108 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } }; - auto copy_transition = [&](const State composition_state, const State copy_state, const State stationar_state, const bool is_symbol_post_lhs) { - const SynchronizationProperties& copy_sync_props = is_symbol_post_lhs ? lhs_sync_props : rhs_sync_props; - const SynchronizationProperties& stationar_sync_props = is_symbol_post_lhs ? rhs_sync_props : lhs_sync_props; + /** + * @brief Copy transitions from the copy NFT to the composition NFT. + * + * @param composition_state The source state in the composition NFT where the transitions will be connected to. + * @param copy_state The state in the copy NFT from which the transitions will be copied. + * @param stationar_state The state in the other NFT (stationary NFT) that does not move. + * @param is_copy_state_lhs If true, the copy_state is from the LHS NFT, otherwise it is from the RHS NFT. + * @param called_from_waiting_simulation If true, the function is called from the waiting simulation, + meaning that we are waiting in the virtual loop at the stationary state. + */ + auto copy_transition = [&](const State composition_state, + const State copy_state, + const State stationar_state, + const bool is_copy_state_lhs, + const bool called_from_waiting_simulation = false) + { + const SynchronizationProperties& copy_sync_props = is_copy_state_lhs ? lhs_sync_props + : rhs_sync_props; + const SynchronizationProperties& stationar_sync_props = is_copy_state_lhs ? rhs_sync_props + : lhs_sync_props; const Nft& copy_nft = copy_sync_props.nft; - const Level copy_state_level = copy_nft.levels[copy_state]; const Level composition_state_level = result.levels[composition_state]; - const std::vector& copy_reachable_sync_types = copy_sync_props.reachable_sync_types; - const SynchronizationType stationar_state_sync_type = stationar_sync_props.reachable_sync_types[stationar_state]; - bool is_copy_state_before_sync = copy_state_level < copy_sync_props.sync_level; - - for (const SymbolPost& symbol_post : copy_nft.delta[copy_state]) { - for (const State target : symbol_post.targets) { - const SynchronizationType target_sync_type = copy_reachable_sync_types[target]; - const bool can_synchronize_in_the_future = stationar_state_sync_type == target_sync_type || - stationar_state_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || - target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL; - - if (is_copy_state_before_sync && can_synchronize_in_the_future) { - // We are still before the synchronization level and - // the target does not lead to a synchronization. - // We don't need to explore this. + const Level copy_state_level = copy_nft.levels[copy_state]; + const Level stationar_state_level = stationar_sync_props.nft.levels[stationar_state]; + const Level copy_sync_level = copy_sync_props.sync_level; + const std::vector& copy_sync_types = copy_sync_props.sync_types_v; + const SynchronizationType stationar_state_sync_type = stationar_sync_props.sync_types_v[stationar_state]; + + // We need to handle any synchronization here in place, if we want to project out + // the synchronization level and if there is no transition remainging after the + // synchronization level in any of the NFTs. + const bool handle_synchronization_in_place = project_out_sync_levels && + stationar_sync_props.num_of_levels_after_sync == 0 && + copy_sync_props.num_of_levels_after_sync == 0 && ( + called_from_waiting_simulation || + stationar_state_level == stationar_sync_props.sync_level + ); + + for (const SymbolPost& copy_symbol_post : copy_nft.delta[copy_state]) { + for (const State copy_target : copy_symbol_post.targets) { + const SynchronizationType copy_target_sync_type = copy_sync_types[copy_target]; + // It makes sence to onyl continue if we beleave that we can synchronize. + // When the function has been called from the waiting simulation, we are + // interested in synchronizations on EPSILON. + // This should also work for the case when we are past the synchronization level. + const bool can_synchronize_in_the_future = ( + called_from_waiting_simulation ? copy_target_sync_type != SynchronizationType::ONLY_ON_SYMBOL + : stationar_state_sync_type == copy_target_sync_type || + stationar_state_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || + copy_target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL + ); + if (!can_synchronize_in_the_future) { + // There is no way we would be able to synchronize. + // We don't need to explore this path. continue; } - const Level target_level = copy_nft.levels[target]; + const Level target_level = copy_nft.levels[copy_target]; const size_t trans_len = (target_level == 0 ? copy_nft.num_of_levels : target_level) - copy_state_level; assert(target_level == 0 || target_level > copy_state_level); assert(trans_len > 0); - const bool rhs_target_level_is_last = target_level == rhs.num_of_levels - 1; - const bool lhs_level_is_last = copy_state_level == lhs.num_of_levels - 1; - if (!is_symbol_post_lhs && is_copy_state_before_sync && rhs_target_level_is_last && lhs_level_is_last && project_out_sync_levels) { - // LHS is at the synchronization level. There is no transition after the synchronization in LHS. - // The target in RHS is at synchronization level, at there is also no transition after the synchronization in RHS. - // Also we are projecting out the synchronization levels. - // This means, that we need now to synchronize and make a transition to the zero-level state in the result NFT. - synchronize(stationar_state, target, composition_state, true, symbol_post.symbol); + if (handle_synchronization_in_place && target_level == copy_sync_level) { + // The target is at the synchronization level that will be projected out. + // We know that there are no transitions after the synchronization level + // in any of the NFTs. Therefore, we have to do the synchronization and + // make this transition go all the way to the next zero-level state. + if (called_from_waiting_simulation) { + // We have beed called from the simulation of the waiting. + // We are waiting in the stationar state. We need to handle the waiting + // synchronization over EPSILON here. + const auto& copy_symbol_post_eps = copy_nft.delta[copy_target].find(EPSILON); + assert(copy_symbol_post_eps != copy_nft.delta[copy_target].end()); + for (const State inplace_copy_target : copy_symbol_post_eps->targets) { + assert(copy_nft.levels[inplace_copy_target] == 0); + result.add_transition_with_target(composition_state, copy_symbol_post.symbol, + create_composition_state(inplace_copy_target, stationar_state, 0, is_copy_state_lhs), jump_mode); + } + } else { + // We encountered normal synchronization. Call the synchronization function. + // The only way we can reach synchronization point in both states, while + // waiting in one state them, is when the waiting state is the LHS state. + // Wecause each transition in the LHS goes before the transitions in the RHS. + assert(!is_copy_state_lhs); + synchronize(composition_state, stationar_state, copy_target, true, copy_symbol_post.symbol); + } + } else { + // We are not at the synchronization point. + // We just need to copy the transtion. const Level new_composition_state_level = (composition_state_level + trans_len) % result.num_of_levels; - result.add_transition_with_target(composition_state, symbol_post.symbol, - create_composition_state(target, stationar_state, new_composition_state_level, is_symbol_post_lhs), jump_mode); + // The creation of new composition state depends on whether we were + // called from the waiting simulation or not. + // If we were called from the waiting simulation, we need to create state that copy_target + // and a virtual state over the virtual waiting loop over the stationar state. + // Because the virtual state is not part of the waiting NFT, we can not use create_composition_state + // function, because it uses the composition_storage that does not contain the virtual state. + const State new_composition_state = ( + called_from_waiting_simulation ? result.add_state_with_level(new_composition_state_level) + : create_composition_state(copy_target, stationar_state, new_composition_state_level, is_copy_state_lhs) + ); + result.add_transition_with_target(composition_state, copy_symbol_post.symbol, new_composition_state, jump_mode); } } } @@ -463,13 +535,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le while (!worklist.empty()) { const auto [lhs_state, rhs_state] = worklist.front(); worklist.pop_front(); + const State composition_state = composition_storage.get(lhs_state, rhs_state); + assert(composition_state != Limits::max_state); const Level lhs_level = lhs.levels[lhs_state]; - const Level rhs_level = rhs.levels[rhs_state]; - const State composition_state = composition_storage.get(lhs_state, rhs_state); - assert(composition_state != Limits::max_state); + const Level rhs_level = rhs.levels[rhs_state]; - const SynchronizationType lhs_sync_type = lhs_reachable_sync_types[lhs_state]; - const SynchronizationType rhs_sync_type = rhs_reachable_sync_types[rhs_state]; + const SynchronizationType lhs_sync_type = lhs_sync_types_v[lhs_state]; + const SynchronizationType rhs_sync_type = rhs_sync_types_v[rhs_state]; if (lhs_level == 0 and rhs_level == 0) { // We are at the zero level states before any synchronization level. // It is now time to decide is we want to continue the synchronization and/or @@ -490,11 +562,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL; if (perform_wait_on_lhs) { // LHS is waiting (i.e., there is an EPSILON in the RHS that we can not synchronize on). - model_waiting(composition_state, lhs_state, rhs_state, true); + model_waiting(lhs_state, rhs_state, true); } if (perform_wait_on_rhs) { // RHS is waiting (i.e., there is an EPSILON in the LHS that we can not synchronize on). - model_waiting(composition_state, rhs_state, lhs_state, false); + model_waiting(rhs_state, lhs_state, false); } if (!can_synchronize_in_the_future) { // There is no way we would be able to synchronize in the future. @@ -535,7 +607,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // synchronization levels. We need to use the synchronization // transition to connect to the zero-level successors of the synchronization. assert(lhs_level == lhs_sync_level && rhs_level == rhs_sync_level); - synchronize(lhs_state, rhs_state, composition_state); + synchronize(composition_state, lhs_state, rhs_state); } } From a7bcce5119f048d38bf7509904a9de047f72a335 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 15 Aug 2025 15:37:58 +0200 Subject: [PATCH 31/65] synchronize done --- src/nft/composition.cc | 49 +++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index d97622df1..c398abcfd 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -358,47 +358,66 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } }; - auto synchronize = [&](const State composition_source_state, const State lhs_state, const State rhs_state, const bool reconnect = false, const Symbol symbol_when_reconnect = Limits::max_symbol) { - assert(composition_source_state != Limits::max_state); + /** + * @brief Perform the synchronization of LHS and RHS at the given state pair. + * + * @param composition_state The state in the composition NFT from where the synchronization result will lead. + * @param lhs_state The state in the LHS NFT that is being synchronized. + * @param rhs_state The state in the RHS NFT that is being synchronized. + * @param reconnect If true, the synchronization will reconnect the predecessors of the synchronization level to + * its successors, otherwise it will just synchronize and keep/remove the synchronization level + * depending on the project_out_sync_levels flag. + * @param reconnection_symbol The symbol to use for the reconnection transition if reconnect is true. + */ + auto synchronize = [&](const State composition_state, + const State lhs_state, + const State rhs_state, + const bool reconnect = false, + const Symbol reconnection_symbol = Limits::max_symbol) + { const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; - const Level composition_target_level = (result.levels[composition_source_state] + 1) % result.num_of_levels; + const Level composition_target_level = (result.levels[composition_state] + 1) % result.num_of_levels; + // When we are projecting out the synchronization levels, and we don't want to connect its + // predecessors to its successors, we will just do the synchronization, note reached targets + // and remove (vanish) the synchronization level with its transition from the result NFT. const bool vanish_sync_level = !reconnect && project_out_sync_levels; - auto synchronize_targets = [&](const StateSet& lhs_sync_targets, const StateSet& rhs_sync_targets, const Symbol symbol) { + // Helper function to combine LHS and RHS targets over the given symbol. + auto combine_targets = [&](const StateSet& lhs_sync_targets, const StateSet& rhs_sync_targets, const Symbol symbol) { for (const State lhs_sync_target : lhs_sync_targets) { for (const State rhs_sync_target : rhs_sync_targets) { if (vanish_sync_level) { worklist.push_back({ lhs_sync_target, rhs_sync_target }); } else { - result.add_transition_with_target(composition_source_state, symbol, + result.add_transition_with_target(composition_state, symbol, create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); } } } }; - // Do the normal synchronization on symbol. + // Synchronization using SynchronizedUniversalIterator. mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); mata::utils::push_back(sync_iterator, lhs.delta[lhs_state]); mata::utils::push_back(sync_iterator, rhs.delta[rhs_state]); while (sync_iterator.advance()) { const std::vector& same_symbol_posts{ sync_iterator.get_current() }; assert(same_symbol_posts.size() == 2); // One move per state in the pair. - const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : same_symbol_posts[0]->symbol; - synchronize_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); + const Symbol symbol = project_out_sync_levels ? reconnection_symbol : same_symbol_posts[0]->symbol; + combine_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); } - // Do the synchronization on DONT_CARE in the LHS. + // Synchronization on DONT_CARE in the LHS. auto lhs_dont_care_sync_it = lhs.delta[lhs_state].find(DONT_CARE); if (lhs_dont_care_sync_it != lhs.delta[lhs_state].end()) { for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { if (rhs_symbol_post.symbol == EPSILON) { - // We don't want to synchronize on DONT_CARE with EPSILON. + // We don't want to synchronize DONT_CARE with EPSILON. continue; } - const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : rhs_symbol_post.symbol; - synchronize_targets(lhs_dont_care_sync_it->targets, rhs_symbol_post.targets, symbol); + const Symbol symbol = project_out_sync_levels ? reconnection_symbol : rhs_symbol_post.symbol; + combine_targets(lhs_dont_care_sync_it->targets, rhs_symbol_post.targets, symbol); } } @@ -407,11 +426,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (rhs_dont_care_sync_it != rhs.delta[rhs_state].end()) { for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { if (lhs_symbol_post.symbol == EPSILON) { - // We don't want to synchronize on DONT_CARE with EPSILON. + // We don't want to synchronize DONT_CARE with EPSILON. continue; } - const Symbol symbol = project_out_sync_levels ? symbol_when_reconnect : lhs_symbol_post.symbol; - synchronize_targets(lhs_symbol_post.targets, rhs_dont_care_sync_it->targets, symbol); + const Symbol symbol = project_out_sync_levels ? reconnection_symbol : lhs_symbol_post.symbol; + combine_targets(lhs_symbol_post.targets, rhs_dont_care_sync_it->targets, symbol); } } }; From 9008edf861560a566550931110ea8a0bbe782ebc Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 15 Aug 2025 19:40:24 +0200 Subject: [PATCH 32/65] model waiting - some halucinations --- src/nft/composition.cc | 268 ++++++++++++++++++----------------------- 1 file changed, 115 insertions(+), 153 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index c398abcfd..aca9f681a 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -221,143 +221,6 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le return new_state; }; - /** - * @brief Model the waiting in one of the NFTs if they could not synchronize due to the EPSILON or symbol transition. - * - * For example, if there is EPSILON on the synchronization level in the RHS and LHS can not synchronize on it, - * then the LHS needs to wait in the root (zero-level) state that precedes the synchronization and RHS will - * continue in the corresponding RHS root ower the problematic EPSILON synchronization to the next zero-level state. - * The RHS is going to act al if it is interleaving with the LHS transitions (LHS transitions always go before RHS transitions), - * however it will place the EPSILON on each such a transition from the LHS. - * - * The problem is if we want to project out the synchronization levels, but there are not transitions following them - * in the LHS not RHS (the synchronization level is the last level in both of them). Then one level before the - * synchronization level, we need to connect successors of such states to the zero-level state that lies after the synchronization level. - * - * @param composition_root_state The root state of the composition NFT. - * @param waiting_root_state The root state of the NFT that is waiting (LHS or RHS). - * @param running_root_state The root state of the NFT that is running (LHS or RHS). - * @param is_lhs_waiting If true, the LHS is waiting, otherwise the RHS is waiting. - */ - auto model_waiting = [&](const State waiting_root_state, const State running_root_state, const bool is_lhs_waiting) { - const State composition_root_state = is_lhs_waiting ? composition_storage.get(waiting_root_state, running_root_state) - : composition_storage.get(running_root_state, waiting_root_state); - assert(composition_root_state != Limits::max_state); - const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props - : lhs_sync_props; - const Level sync_level = running_sync_props.sync_level; - const Levels& running_levels = running_sync_props.nft.levels; - const Delta& running_delta = running_sync_props.nft.delta; - const size_t num_of_trans_to_replace_sync = running_sync_props.num_of_trans_to_replace_sync; - const std::vector& reachable_sync_types = running_sync_props.sync_types_v; - // If the synchronization level is the last level, and it cannot be replaced by any sequence of epsilon transitions - // (i.e., it wanishes), we need to do "in place" synchronization step and connect previous state to the next - // zero-level state. - const bool handle_sync_in_place = running_sync_props.sync_level == running_sync_props.nft.num_of_levels - 1 && - num_of_trans_to_replace_sync == 0; - - // We cannot use inverted storage here, because there can be one composition state for multiple original pairs of states. - // Therefore we need to keep track of it in the stack instead. - std::stack> stack; - stack.push({ composition_root_state, running_root_state}); - while (!stack.empty()) { - auto [composition_state, running_state] = stack.top(); - const Level running_state_level = running_levels[running_state]; - stack.pop(); - - // If we are in RHS and LHS is waiting and also the synchronization level is not the first level, - // then we need to add the epsilon transition from the waiting LHS, because transitions from the LHS - // goes always before the transitions from the RHS. - if (is_lhs_waiting && sync_level != 0 && running_state_level == 0 && lhs_levels_before_sync > 0) { - composition_state = result.add_transition_with_lenght(composition_state, EPSILON, lhs_levels_before_sync, jump_mode); - } - - if (running_state_level < sync_level) { - // We are before the synchronization level. - for (const SymbolPost& running_symbol_post : running_delta[running_state]) { - for (const State running_target : running_symbol_post.targets) { - if (reachable_sync_types[running_target] == SynchronizationType::ONLY_ON_SYMBOL) { - // The target state leads to only a synchronization on a symbol. - // We don't need this. We need to synchronize on epsilon. - continue; - } - - const Level running_target_level = running_levels[running_target]; - const size_t trans_len = running_target_level - running_state_level; - assert(running_target_level > running_state_level); - assert(trans_len > 0); - - if (running_target_level == sync_level && handle_sync_in_place) { - // We are at the synchronization level after which there will be nothing added. - // We need to connect running_state to epsilon successors of the running_target. - auto epsilon_sync_symbol_post = running_delta[running_target].find(EPSILON); - assert(epsilon_sync_symbol_post != running_delta[running_target].end()); - for (const State epsilon_target : epsilon_sync_symbol_post->targets) { - assert(running_levels[epsilon_target] == 0); - result.add_transition_with_target(composition_state, running_symbol_post.symbol, create_composition_state(waiting_root_state, epsilon_target, 0, is_lhs_waiting), jump_mode); - } - } else { - // It does not matter if the target is a synchronization level or not - // (we can handle any synchronization later, because it will be replaced - // by epsilon transitions). - const State new_composition_state = result.add_transition_with_lenght(composition_state, running_symbol_post.symbol, trans_len, jump_mode); - stack.push({ std::move(new_composition_state), running_target }); - } - } - } - - } else if (running_state_level == sync_level) { - // We are exactly at the synchronization level. - assert(!handle_sync_in_place); - auto epsilon_sync_symbol_post = running_delta[running_state].find(EPSILON); - assert(epsilon_sync_symbol_post != running_delta[running_state].end()); - for (const State epsilon_target : epsilon_sync_symbol_post->targets) { - if (num_of_trans_to_replace_sync > 0) { - // We will add some transitions on the place of the synchronization transition. - if (running_levels[epsilon_target] == 0) { - // We are connecting directly to the zero-level state. - result.add_transition_with_target(composition_state, EPSILON, create_composition_state(waiting_root_state, epsilon_target, 0, is_lhs_waiting), jump_mode); - } else { - // We are connecting to an internal state. Create new auxiliar state for it. - const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, num_of_trans_to_replace_sync, jump_mode); - stack.push({ std::move(new_composition_state), epsilon_target }); - } - } else { - // Just ignore this transition. - // (It is being projected out and there are no transitions in the waiting NFT - // that would be place directly before or after the synchronization level.) - stack.push({ composition_state, epsilon_target }); - } - } - } else { - // We are past the synchronization level. - // We just need to get to the next zero-level state. - for (const SymbolPost& symbol_post : running_delta[running_state]) { - for (const State target : symbol_post.targets) { - const Level target_level = running_levels[target]; - if (target_level == 0) { - // We are connecting to a zero-level state. - if (!is_lhs_waiting && rhs_levels_after_sync > 0) { - // We are in the LHS and RHS waits. Because transitions from RHS goes as last, - // we need to add epsilon transitions from the waiting RHS after this transition. - const size_t trans_len = running_sync_props.nft.num_of_levels - running_levels[running_state]; - const State new_composition_state = result.add_transition_with_lenght(composition_state, symbol_post.symbol, trans_len, jump_mode); - result.add_transition_with_target(new_composition_state, EPSILON, create_composition_state(waiting_root_state, target, 0, is_lhs_waiting), jump_mode); - } else { - // We don't need to add any other transitions, just this one. - result.add_transition_with_target(composition_state, symbol_post.symbol, create_composition_state(waiting_root_state, target, 0, is_lhs_waiting), jump_mode); - } - } else { - // Otherwise just copy the transition. - const State new_composition_state = result.add_transition_with_lenght(composition_state, symbol_post.symbol, target_level - running_state_level, jump_mode); - stack.push({new_composition_state, target}); - } - } - } - } - } - }; - /** * @brief Perform the synchronization of LHS and RHS at the given state pair. * @@ -442,14 +305,15 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le * @param copy_state The state in the copy NFT from which the transitions will be copied. * @param stationar_state The state in the other NFT (stationary NFT) that does not move. * @param is_copy_state_lhs If true, the copy_state is from the LHS NFT, otherwise it is from the RHS NFT. - * @param called_from_waiting_simulation If true, the function is called from the waiting simulation, - meaning that we are waiting in the virtual loop at the stationary state. + * @param waiting_worklist If set, the function is called from the waiting simulation, + meaning that we are waiting in the virtual loop at the stationary state, + and we will use the waiting_worklist to put next state pairs to. */ auto copy_transition = [&](const State composition_state, const State copy_state, const State stationar_state, const bool is_copy_state_lhs, - const bool called_from_waiting_simulation = false) + std::stack>* waiting_worklist = nullptr) { const SynchronizationProperties& copy_sync_props = is_copy_state_lhs ? lhs_sync_props : rhs_sync_props; @@ -469,7 +333,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const bool handle_synchronization_in_place = project_out_sync_levels && stationar_sync_props.num_of_levels_after_sync == 0 && copy_sync_props.num_of_levels_after_sync == 0 && ( - called_from_waiting_simulation || + waiting_worklist != nullptr || stationar_state_level == stationar_sync_props.sync_level ); @@ -481,10 +345,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // interested in synchronizations on EPSILON. // This should also work for the case when we are past the synchronization level. const bool can_synchronize_in_the_future = ( - called_from_waiting_simulation ? copy_target_sync_type != SynchronizationType::ONLY_ON_SYMBOL - : stationar_state_sync_type == copy_target_sync_type || - stationar_state_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || - copy_target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL + waiting_worklist != nullptr ? copy_target_sync_type != SynchronizationType::ONLY_ON_SYMBOL + : stationar_state_sync_type == copy_target_sync_type || + stationar_state_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || + copy_target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL ); if (!can_synchronize_in_the_future) { // There is no way we would be able to synchronize. @@ -502,7 +366,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We know that there are no transitions after the synchronization level // in any of the NFTs. Therefore, we have to do the synchronization and // make this transition go all the way to the next zero-level state. - if (called_from_waiting_simulation) { + if (waiting_worklist != nullptr) { // We have beed called from the simulation of the waiting. // We are waiting in the stationar state. We need to handle the waiting // synchronization over EPSILON here. @@ -532,12 +396,110 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // and a virtual state over the virtual waiting loop over the stationar state. // Because the virtual state is not part of the waiting NFT, we can not use create_composition_state // function, because it uses the composition_storage that does not contain the virtual state. - const State new_composition_state = ( - called_from_waiting_simulation ? result.add_state_with_level(new_composition_state_level) - : create_composition_state(copy_target, stationar_state, new_composition_state_level, is_copy_state_lhs) - ); - result.add_transition_with_target(composition_state, copy_symbol_post.symbol, new_composition_state, jump_mode); + if (waiting_worklist != nullptr && new_composition_state_level != 0) { + const State new_composition_state = result.add_state_with_level(new_composition_state_level); + result.add_transition_with_target(composition_state, copy_symbol_post.symbol, new_composition_state, jump_mode); + waiting_worklist->push({ new_composition_state, stationar_state }); + } else { + result.add_transition_with_target( + composition_state, copy_symbol_post.symbol, + create_composition_state(copy_target, stationar_state, new_composition_state_level, is_copy_state_lhs), jump_mode + ); + } + } + } + } + }; + + /** + * @brief Model the waiting in one of the NFTs if they could not synchronize due to the EPSILON or symbol transition. + * + * For example, if there is EPSILON on the synchronization level in the RHS and LHS can not synchronize on it, + * then the LHS needs to wait in the root (zero-level) state that precedes the synchronization and RHS will + * continue in the corresponding RHS root ower the problematic EPSILON synchronization to the next zero-level state. + * The RHS is going to act al if it is interleaving with the LHS transitions (LHS transitions always go before RHS transitions), + * however it will place the EPSILON on each such a transition from the LHS. + * + * The problem is if we want to project out the synchronization levels, but there are not transitions following them + * in the LHS not RHS (the synchronization level is the last level in both of them). Then one level before the + * synchronization level, we need to connect successors of such states to the zero-level state that lies after the synchronization level. + * + * @param composition_root_state The root state of the composition NFT. + * @param waiting_root_state The root state of the NFT that is waiting (LHS or RHS). + * @param running_root_state The root state of the NFT that is running (LHS or RHS). + * @param is_lhs_waiting If true, the LHS is waiting, otherwise the RHS is waiting. + */ + auto model_waiting = [&](const State composition_root_state, const State waiting_root_state, const State running_root_state, const bool is_lhs_waiting) { + const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props + : lhs_sync_props; + const SynchronizationProperties& waiting_sync_props = is_lhs_waiting ? lhs_sync_props + : rhs_sync_props; + const size_t waiting_trans_before_sync = waiting_sync_props.num_of_levels_before_sync; + const size_t waiting_trans_after_sync = waiting_sync_props.num_of_levels_after_sync; + const Level sync_level = running_sync_props.sync_level; + const Level composition_sync_level = running_sync_props.sync_level + waiting_sync_props.sync_level; + const Levels& running_levels = running_sync_props.nft.levels; + const Delta& running_delta = running_sync_props.nft.delta; + const size_t num_of_trans_to_replace_sync = running_sync_props.num_of_trans_to_replace_sync; + const std::vector& sync_types_v = running_sync_props.sync_types_v; + // If the synchronization level is the last level, and it cannot be replaced by any sequence of epsilon transitions + // (i.e., it wanishes), we need to do "in place" synchronization step and connect previous state to the next + // zero-level state. + const bool handle_sync_in_place = running_sync_props.sync_level == running_sync_props.nft.num_of_levels - 1 && + num_of_trans_to_replace_sync == 0; + + + const bool put_at_0 = is_lhs_waiting && waiting_trans_before_sync > 0; + const bool put_before_sync = !is_lhs_waiting && waiting_trans_before_sync > 0; + const bool put_after_sync = is_lhs_waiting && waiting_trans_after_sync > 0; + const bool put_at_last = !is_lhs_waiting && waiting_trans_after_sync > 0; + + // We cannot use inverted storage here, because there can be one composition state for multiple original pairs of states. + // Therefore we need to keep track of it in the stack instead. + std::stack> stack; + stack.push({ composition_root_state, running_root_state}); + while (!stack.empty()) { + auto [composition_state, running_state] = stack.top(); + const Level composition_state_level = result.levels[composition_state]; + const Level running_state_level = running_levels[running_state]; + stack.pop(); + + const bool running_state_is_not_leaf_zero_level_state = (running_state_level != 0 || running_state == running_root_state); + + const bool running_state_before_sync = running_state_level < sync_level && running_state_is_not_leaf_zero_level_state; + const bool running_state_at_sync = running_state_level == sync_level && running_state_is_not_leaf_zero_level_state; + const bool running_state_after_sync = running_state_level > sync_level || !running_state_is_not_leaf_zero_level_state; + + const bool at_0 = composition_state_level == 0 && running_state != running_root_state; + const bool just_before_sync = composition_state_level < composition_sync_level && running_state_at_sync; + const bool just_after_sync = running_state_level == (sync_level + 1) % running_sync_props.nft.num_of_levels && running_state != running_root_state; + const bool at_last = !running_state_is_not_leaf_zero_level_state; + + if (put_at_0 && at_0) { + const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, waiting_trans_before_sync, jump_mode); + stack.push({ std::move(new_composition_state), running_root_state }); + } else if (running_state_before_sync) { + copy_transition(composition_state, running_state, waiting_root_state, !is_lhs_waiting, &stack); + } else if (put_before_sync && just_before_sync) { + const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, waiting_trans_before_sync, jump_mode); + stack.push({ std::move(new_composition_state), running_root_state }); + } else if (running_state_at_sync) { + const auto& running_symbol_epsilon_post = running_delta[running_state].find(EPSILON); + assert(running_symbol_epsilon_post != running_delta[running_state].end()); + for (const State running_target : running_symbol_epsilon_post->targets) { + const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, 1, jump_mode); + stack.push({ std::move(new_composition_state), running_target }); } + } else if (put_after_sync && just_after_sync) { + const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, waiting_trans_after_sync, jump_mode); + stack.push({ std::move(new_composition_state), running_root_state }); + } else if (running_state_after_sync) { + copy_transition(composition_state, running_state, waiting_root_state, !is_lhs_waiting, &stack); + } else if (put_at_last && at_last) { + const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, waiting_trans_after_sync, jump_mode); + stack.push({ std::move(new_composition_state), running_root_state }); + } else { + throw std::runtime_error("Bug detected: unexpected state in nft::composition::model_waiting function."); } } }; @@ -581,11 +543,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL; if (perform_wait_on_lhs) { // LHS is waiting (i.e., there is an EPSILON in the RHS that we can not synchronize on). - model_waiting(lhs_state, rhs_state, true); + model_waiting(composition_state, lhs_state, rhs_state, true); } if (perform_wait_on_rhs) { // RHS is waiting (i.e., there is an EPSILON in the LHS that we can not synchronize on). - model_waiting(rhs_state, lhs_state, false); + model_waiting(composition_state, rhs_state, lhs_state, false); } if (!can_synchronize_in_the_future) { // There is no way we would be able to synchronize in the future. From ed56cd6ee05bb37d0c627224827adcc1465bfcbd Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sat, 16 Aug 2025 13:35:25 +0200 Subject: [PATCH 33/65] much better model_waiting function --- src/nft/composition.cc | 176 ++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index aca9f681a..7537939a4 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -42,15 +42,13 @@ namespace { const Level sync_level; const size_t num_of_levels_before_sync; const size_t num_of_levels_after_sync; - const size_t num_of_trans_to_replace_sync; ///< Number of transitions to replace synchronization. - const std::vector sync_types_v{}; + std::vector sync_types_v{}; - SynchronizationProperties(const Nft& nft, const Level sync_level, const size_t num_of_trans_to_replace_sync) + SynchronizationProperties(const Nft& nft, const Level sync_level) : nft(nft), sync_level(sync_level), num_of_levels_before_sync(sync_level), - num_of_levels_after_sync(nft.num_of_levels - sync_level - 1), - num_of_trans_to_replace_sync(num_of_trans_to_replace_sync) + num_of_levels_after_sync(nft.num_of_levels - sync_level - 1) { get_synchronization_types(); } @@ -147,28 +145,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const size_t rhs_levels_before_sync = rhs_sync_level; const size_t lhs_levels_after_sync = lhs.num_of_levels - lhs_sync_level - 1; const size_t rhs_levels_after_sync = rhs.num_of_levels - rhs_sync_level - 1; - // If we are waiting on the RHS, we need to know how many epsilon transitions - // will replace the synchronization transition in the LHS. - // 1) Transitions from RHS will always go as last before the snychronization level. - // 2) If the synchronization level is the last level in the LHS, we need to add - // all remaining transitions from the RHS that goes after the synchronization level. - // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. - const size_t lhs_num_of_trans_to_replace_sync = (rhs_levels_before_sync) + - (lhs_sync_level == lhs.num_of_levels - 1 ? rhs_levels_after_sync : 0) + - (project_out_sync_levels ? 0 : 1); - // If we are waiting on the LHS, we need to know how many epsilon transitions - // will replace the synchronization transition in the RHS. - // 1) Because transtions from LHS always go as first, the only time when they go immediately before the synchronization level - // is when the synchronization level is the first level in the RHS. - // 2) LHS transitions go always before RHS transitions, so after the synchronization level, - // we know that there will be transitions from the LHS. - // 3) If we are not projecting out the synchronization levels, we need to incorporate this into the count. - const size_t rhs_num_of_trans_to_replace_sync = (rhs_sync_level == 0 ? lhs_levels_before_sync : 0) + - (lhs_levels_after_sync) + - (project_out_sync_levels ? 0 : 1); - - const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level, lhs_num_of_trans_to_replace_sync); - const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level, rhs_num_of_trans_to_replace_sync); + + const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level); + const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level); const std::vector& lhs_sync_types_v = lhs_sync_props.sync_types_v; const std::vector& rhs_sync_types_v = rhs_sync_props.sync_types_v; @@ -313,7 +292,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const State copy_state, const State stationar_state, const bool is_copy_state_lhs, - std::stack>* waiting_worklist = nullptr) + std::queue>* waiting_worklist = nullptr) { const SynchronizationProperties& copy_sync_props = is_copy_state_lhs ? lhs_sync_props : rhs_sync_props; @@ -434,72 +413,93 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le : lhs_sync_props; const SynchronizationProperties& waiting_sync_props = is_lhs_waiting ? lhs_sync_props : rhs_sync_props; - const size_t waiting_trans_before_sync = waiting_sync_props.num_of_levels_before_sync; - const size_t waiting_trans_after_sync = waiting_sync_props.num_of_levels_after_sync; const Level sync_level = running_sync_props.sync_level; - const Level composition_sync_level = running_sync_props.sync_level + waiting_sync_props.sync_level; const Levels& running_levels = running_sync_props.nft.levels; const Delta& running_delta = running_sync_props.nft.delta; - const size_t num_of_trans_to_replace_sync = running_sync_props.num_of_trans_to_replace_sync; - const std::vector& sync_types_v = running_sync_props.sync_types_v; - // If the synchronization level is the last level, and it cannot be replaced by any sequence of epsilon transitions - // (i.e., it wanishes), we need to do "in place" synchronization step and connect previous state to the next - // zero-level state. - const bool handle_sync_in_place = running_sync_props.sync_level == running_sync_props.nft.num_of_levels - 1 && - num_of_trans_to_replace_sync == 0; - - - const bool put_at_0 = is_lhs_waiting && waiting_trans_before_sync > 0; - const bool put_before_sync = !is_lhs_waiting && waiting_trans_before_sync > 0; - const bool put_after_sync = is_lhs_waiting && waiting_trans_after_sync > 0; - const bool put_at_last = !is_lhs_waiting && waiting_trans_after_sync > 0; - - // We cannot use inverted storage here, because there can be one composition state for multiple original pairs of states. - // Therefore we need to keep track of it in the stack instead. - std::stack> stack; - stack.push({ composition_root_state, running_root_state}); - while (!stack.empty()) { - auto [composition_state, running_state] = stack.top(); - const Level composition_state_level = result.levels[composition_state]; + const size_t waiting_trans_before_sync = waiting_sync_props.num_of_levels_before_sync; + const size_t waiting_trans_after_sync = waiting_sync_props.num_of_levels_after_sync; + const size_t num_of_epsilons_to_replace_sync = ( + (is_lhs_waiting ? waiting_trans_after_sync : waiting_trans_before_sync) + + (project_out_sync_levels ? 0 : 1) + ); + + // We cannot use inverted storage here, because there can be one composition + // state for multiple original pairs of states. Therefore, we need to keep + // track of it in the queue instead. + std::queue> worklist; + + // Initialization of the worklist. + // If we know at this point, that waiting is LHS and there are transtiion before + // the synchronization level, that we need to represent in the resulting NFT, + // as EPSILON transitions, and because LHS goes before RHS, we can add this + // transition immediately here during the initialization of the worklist. + if (!is_lhs_waiting || waiting_trans_before_sync == 0) { + worklist.push({ composition_root_state, running_root_state}); + } else { + const State new_composition_state = result.add_transition_with_lenght(composition_root_state, EPSILON, waiting_trans_before_sync, jump_mode); + worklist.push({ std::move(new_composition_state), running_root_state }); + } + + // The main loop of the waiting simulation. + while (!worklist.empty()) { + auto [composition_state, running_state] = worklist.front(); + worklist.pop(); const Level running_state_level = running_levels[running_state]; - stack.pop(); - - const bool running_state_is_not_leaf_zero_level_state = (running_state_level != 0 || running_state == running_root_state); - - const bool running_state_before_sync = running_state_level < sync_level && running_state_is_not_leaf_zero_level_state; - const bool running_state_at_sync = running_state_level == sync_level && running_state_is_not_leaf_zero_level_state; - const bool running_state_after_sync = running_state_level > sync_level || !running_state_is_not_leaf_zero_level_state; - - const bool at_0 = composition_state_level == 0 && running_state != running_root_state; - const bool just_before_sync = composition_state_level < composition_sync_level && running_state_at_sync; - const bool just_after_sync = running_state_level == (sync_level + 1) % running_sync_props.nft.num_of_levels && running_state != running_root_state; - const bool at_last = !running_state_is_not_leaf_zero_level_state; - - if (put_at_0 && at_0) { - const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, waiting_trans_before_sync, jump_mode); - stack.push({ std::move(new_composition_state), running_root_state }); - } else if (running_state_before_sync) { - copy_transition(composition_state, running_state, waiting_root_state, !is_lhs_waiting, &stack); - } else if (put_before_sync && just_before_sync) { - const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, waiting_trans_before_sync, jump_mode); - stack.push({ std::move(new_composition_state), running_root_state }); - } else if (running_state_at_sync) { - const auto& running_symbol_epsilon_post = running_delta[running_state].find(EPSILON); - assert(running_symbol_epsilon_post != running_delta[running_state].end()); - for (const State running_target : running_symbol_epsilon_post->targets) { - const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, 1, jump_mode); - stack.push({ std::move(new_composition_state), running_target }); + + if (running_state_level == sync_level) { + // We are at the synchronization level. + if (num_of_epsilons_to_replace_sync > 1) { + // If the synchronization is going to be replace by multiple EPSILON transitions, + // we can reduce the number of redundant EPSILON transtions by adding n-1 transitions + // now and then use the last one to connect with the target state. + composition_state = result.add_transition_with_lenght(composition_state, EPSILON, num_of_epsilons_to_replace_sync - 1, jump_mode); + } + + const bool is_last_transition = (running_state_level == running_sync_props.nft.num_of_levels - 1); + const auto& running_epsilon_post_it = running_delta[running_state].find(EPSILON); + assert(running_epsilon_post_it != running_delta[running_state].end()); + for (const State running_epsilon_target : running_epsilon_post_it->targets) { + if (num_of_epsilons_to_replace_sync == 0) { + // This synchronization level is being projected out. + // We don't need to worry about if there is any useful transition + // after this, because if not, that it would be already handled + // by the copy_transition function. + assert(!is_last_transition); + worklist.push({ composition_state, running_epsilon_target }); + } else if (is_last_transition) { + // We are connecting to the zero-level state, therefore we have to + // create a new composition state that will be put into the main worklist + // (side effect of the create_composition_state). + result.add_transition_with_target(composition_state, EPSILON, + create_composition_state(running_epsilon_target, running_root_state, 0, is_lhs_waiting), jump_mode); + } else { + // We are connecting to the next state in the waiting loop, + // therefore we can just add a transition to the next state and + // continue the waiting simulation. + const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, num_of_epsilons_to_replace_sync, jump_mode); + worklist.push({ new_composition_state, running_epsilon_target }); + } } - } else if (put_after_sync && just_after_sync) { - const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, waiting_trans_after_sync, jump_mode); - stack.push({ std::move(new_composition_state), running_root_state }); - } else if (running_state_after_sync) { - copy_transition(composition_state, running_state, waiting_root_state, !is_lhs_waiting, &stack); - } else if (put_at_last && at_last) { - const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, waiting_trans_after_sync, jump_mode); - stack.push({ std::move(new_composition_state), running_root_state }); + } else if (running_state_level == 0 && running_state != running_root_state) { + // We are at the zero-level state at the running NFT. + // Because of previous checks, we know that this could only happen, + // if the running NFT is the LHS NFT and there is are remaining transitions + // in the waiting RHS NFT. Because RHS always goes after LHS, we can + // just add a transition to the zero-level state and continue from there + // in the main composition loop. + assert(!is_lhs_waiting); + assert(waiting_trans_after_sync > 0); + assert((running_state_level + waiting_trans_after_sync) % result.num_of_levels == 0); + result.add_transition_with_target(composition_state, EPSILON, + create_composition_state(running_state, running_root_state, 0, is_lhs_waiting), jump_mode); } else { - throw std::runtime_error("Bug detected: unexpected state in nft::composition::model_waiting function."); + // We are at some internal level in the running NFT. + // We can just copy the transitions to next states. + // We don't need to worry about potential synchronization just + // after this level that would go to zero-level state, + // because if such a synchronization exists, + // it will be handled by the copy_transition function. + copy_transition(composition_state, running_state, running_root_state, is_lhs_waiting, &worklist); } } }; From d3a8f241e849ba0dd4f94c02c8ae2aaaef3a815a Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sat, 16 Aug 2025 14:18:54 +0200 Subject: [PATCH 34/65] draft done --- src/nft/composition.cc | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 7537939a4..d2cf9dbd4 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -138,14 +138,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const size_t result_num_of_levels = lhs.num_of_levels + rhs.num_of_levels - (project_out_sync_levels ? (2) : 1); assert(result_num_of_levels > 0); - // Calculate the number of epsilon transitions that will replace one epsilon synchronization transition during waiting. - // This depends on whether we are projecting out the synchronization level and if there are any transitions in the waiting - // transducer, that we need to place right before or right after the synchronization level. - const size_t lhs_levels_before_sync = lhs_sync_level; - const size_t rhs_levels_before_sync = rhs_sync_level; - const size_t lhs_levels_after_sync = lhs.num_of_levels - lhs_sync_level - 1; - const size_t rhs_levels_after_sync = rhs.num_of_levels - rhs_sync_level - 1; - + // Initialize the synchronization properties for both NFTs. const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level); const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level); const std::vector& lhs_sync_types_v = lhs_sync_props.sync_types_v; @@ -153,6 +146,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le Nft result; result.num_of_levels = result_num_of_levels; + // Use composition storage without tracking inverted indices, + // as the waiting in virtual states would spoit the inverse mapping. TwoDimensionalMap composition_storage(lhs_num_of_states, rhs_num_of_states); std::deque> worklist; From 6e5311508b1ff37f0ecfe30033b4583e5fc3bde9 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sat, 16 Aug 2025 14:19:08 +0200 Subject: [PATCH 35/65] draft done --- src/nft/composition.cc | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index d2cf9dbd4..87541561e 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -16,7 +16,28 @@ namespace { using namespace mata::nft; // Enum class for a state flag to indicate which synchronization - //types (synchronization on epsilon or symbol) can be reached from the state. + // types (synchronization on epsilon or symbol) can be reached from the state. + // The synchronization type helps to determine is a state from lhs can + // somewhere in the next levels synchronize with a state from rhs. + // The wolowing table shows all possible combinations of synchronization types + // for lhs and rhs states and if they can synchronize of one of them will wait + // because there way an EPSILON on the synchronization in the other that it + // could not synchronize with. + // || RHS sync type + // LHS sync type || ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL + // ======================||================|=================|====================== + // || synchornize | | synchornize + // ONLY_ON_SYMBOL || | wait on LHS | wait on LHS + // || | | + // ----------------------||----------------|-----------------|---------------------- + // || | synchronize | synchornize + // ONLY_ON_EPSILON || | | + // || wait on RHS | | wait on RHS + // ----------------------||----------------|-----------------|---------------------- + // || synchronize | synchronize | synchornize + // ON_EPSILON_AND_SYMBOL || | lait on LHS | wait on LHS + // || wait on RHS | | wait on RHS + // ================================================================================= enum class SynchronizationType : uint8_t { UNINITIALIZED = 0b0000'0000, ///< Default value. ONLY_ON_SYMBOL = 0b0000'0001, ///< Synchronization on EPSILON. From 523283bfa643f060a083a49f6511708e960b5c87 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sat, 16 Aug 2025 19:09:35 +0200 Subject: [PATCH 36/65] draft final version --- src/nft/composition.cc | 543 ++++++++++++++++++++++++----------------- 1 file changed, 321 insertions(+), 222 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 87541561e..e47d67078 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -15,35 +15,37 @@ namespace { using mata::Symbol; using namespace mata::nft; - // Enum class for a state flag to indicate which synchronization - // types (synchronization on epsilon or symbol) can be reached from the state. - // The synchronization type helps to determine is a state from lhs can - // somewhere in the next levels synchronize with a state from rhs. - // The wolowing table shows all possible combinations of synchronization types - // for lhs and rhs states and if they can synchronize of one of them will wait - // because there way an EPSILON on the synchronization in the other that it - // could not synchronize with. - // || RHS sync type - // LHS sync type || ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL - // ======================||================|=================|====================== - // || synchornize | | synchornize - // ONLY_ON_SYMBOL || | wait on LHS | wait on LHS - // || | | - // ----------------------||----------------|-----------------|---------------------- - // || | synchronize | synchornize - // ONLY_ON_EPSILON || | | - // || wait on RHS | | wait on RHS - // ----------------------||----------------|-----------------|---------------------- - // || synchronize | synchronize | synchornize - // ON_EPSILON_AND_SYMBOL || | lait on LHS | wait on LHS - // || wait on RHS | | wait on RHS - // ================================================================================= + /** + * Enum class for a state flag to indicate which synchronization + * types (synchronization on epsilon or symbol) can be reached from the state. + * The synchronization type helps to determine if a state from lhs can + * at some point in the next levels synchronize with a state from rhs. + * The following table shows all possible combinations of synchronization types + * for lhs and rhs states, and whether they can synchronize or if one of them will wait + * because there is an EPSILON on the synchronization in the other that it + * could not synchronize with. + * || RHS sync type + * LHS sync type || ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL + * ======================||================|=================|====================== + * || synchornize | | synchornize + * ONLY_ON_SYMBOL || | wait on LHS | wait on LHS + * || | | + * ----------------------||----------------|-----------------|---------------------- + * || | synchronize | synchornize + * ONLY_ON_EPSILON || | | + * || wait on RHS | | wait on RHS + * ----------------------||----------------|-----------------|---------------------- + * || synchronize | synchronize | synchornize + * ON_EPSILON_AND_SYMBOL || | lait on LHS | wait on LHS + * || wait on RHS | | wait on RHS + * ================================================================================= + */ enum class SynchronizationType : uint8_t { - UNINITIALIZED = 0b0000'0000, ///< Default value. + UNDEFINED = 0b0000'0000, ///< Default value. Or used if we are past the synchronization. ONLY_ON_SYMBOL = 0b0000'0001, ///< Synchronization on EPSILON. ONLY_ON_EPSILON = 0b0000'0010, ///< Synchronization on symbol. ON_EPSILON_AND_SYMBOL = 0b0000'0011, ///< Synchronization on EPSILON and symbol. - UNDER_COMPUTATION = 0b0000'0100 ///< The synchronization is being computed (only helper value). + UNDER_COMPUTATION = 0b0000'0100 ///< The synchronization is being computed (only a helper value). }; inline SynchronizationType operator|(SynchronizationType lhs, SynchronizationType rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); @@ -55,7 +57,7 @@ namespace { /** * @brief A class to hold properties related to synchronization during the composition of NFTs. - * This simplifies the passing of multiple synchronization-related parameters. + * This simplifies passing multiple synchronization-related parameters. */ class SynchronizationProperties { public: @@ -63,53 +65,60 @@ namespace { const Level sync_level; const size_t num_of_levels_before_sync; const size_t num_of_levels_after_sync; - std::vector sync_types_v{}; + std::vector sync_types_v; SynchronizationProperties(const Nft& nft, const Level sync_level) : nft(nft), sync_level(sync_level), num_of_levels_before_sync(sync_level), - num_of_levels_after_sync(nft.num_of_levels - sync_level - 1) + num_of_levels_after_sync(nft.num_of_levels - sync_level - 1), + sync_types_v(nft.num_of_states(), SynchronizationType::UNDEFINED) { get_synchronization_types(); } private: /** - * @brief For each state sets a type of synchronization that follows. + * @brief For each state, sets the type of synchronization that follows. + * + * The scope of the synchronization type ends at the next zero-level state. + * Each state has a synchronization type that is a combination of the + * synchronization types of its children. */ void get_synchronization_types() { const size_t num_of_states = nft.num_of_states(); - std::vector sync_types(num_of_states, SynchronizationType::UNINITIALIZED); - for (State root = 0; root < num_of_states; ++root) { - if (nft.levels[root] != sync_level) { - continue; // Skip states not on the sync level. + if (nft.levels[root] != 0) { + continue; // Skip states not on the zero level. } + // To perform the computation, which is a post-order traversal, we do not + // use recursion but a stack for better performance. To determine if a parent state + // has already been visited, we use the helper value SynchronizationType::UNDER_COMPUTATION. std::stack stack; stack.push(root); while (!stack.empty()) { const State state = stack.top(); + stack.pop(); const Level state_level = nft.levels[state]; const StatePost& state_post = nft.delta[state]; - SynchronizationType current_sync_type = sync_types[state]; - assert(current_sync_type == SynchronizationType::UNINITIALIZED || current_sync_type == SynchronizationType::UNDER_COMPUTATION); - stack.pop(); + SynchronizationType current_sync_type = sync_types_v[state]; + assert(current_sync_type == SynchronizationType::UNDEFINED || current_sync_type == SynchronizationType::UNDER_COMPUTATION); - // If it has been visited, than by now we know that it has been already computed for its children. + // If it has been visited, then we know that it has already been computed for its children. if (current_sync_type == SynchronizationType::UNDER_COMPUTATION) { - current_sync_type = SynchronizationType::UNINITIALIZED; + // Combine the synchronization types of its children. + current_sync_type = SynchronizationType::UNDEFINED; for (const SymbolPost& symbol_post : state_post) { for (const State target : symbol_post.targets) { - current_sync_type |= sync_types[target]; + current_sync_type |= sync_types_v[target]; } } - sync_types[state] = current_sync_type; + sync_types_v[state] = current_sync_type; continue; // We already visited its children. } - // If we are on the sync level, we can compute the synchronization type. + // If we are on the sync level, we can determine the synchronization type. if (state_level == sync_level) { if (state_post.find(EPSILON) != state_post.end()) { current_sync_type = SynchronizationType::ONLY_ON_EPSILON; @@ -119,28 +128,28 @@ namespace { } else if (!state_post.empty()) { current_sync_type = SynchronizationType::ONLY_ON_SYMBOL; } - sync_types[state] = current_sync_type; + sync_types_v[state] = current_sync_type; continue; // No need to visit its children. } // We need to visit its children. - sync_types[state] = SynchronizationType::UNDER_COMPUTATION; // Mark this state as under computation. + sync_types_v[state] = SynchronizationType::UNDER_COMPUTATION; // Mark this state as under computation. stack.push(state); // Push it back to the stack to compute it later. for (const SymbolPost& symbol_post : state_post) { for (const State target : symbol_post.targets) { assert(nft.levels[target] > nft.levels[state]); - assert(sync_types[target] != SynchronizationType::UNDER_COMPUTATION); - if (sync_types[target] != SynchronizationType::UNINITIALIZED) { - current_sync_type |= sync_types[target]; + assert(sync_types_v[target] != SynchronizationType::UNDER_COMPUTATION); + if (sync_types_v[target] != SynchronizationType::UNDEFINED) { + current_sync_type |= sync_types_v[target]; } else { - stack.push(target); // Push the target state to visit it later. + stack.push(target); // Push the target state onto the stack to visit it. } } } } } - assert(std::all_of(sync_types.begin(), sync_types.end(), + assert(std::all_of(sync_types_v.begin(), sync_types_v.end(), [](SynchronizationType type) { return type != SynchronizationType::UNDER_COMPUTATION; })); } }; @@ -162,70 +171,74 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Initialize the synchronization properties for both NFTs. const SynchronizationProperties lhs_sync_props(lhs, lhs_sync_level); const SynchronizationProperties rhs_sync_props(rhs, rhs_sync_level); - const std::vector& lhs_sync_types_v = lhs_sync_props.sync_types_v; - const std::vector& rhs_sync_types_v = rhs_sync_props.sync_types_v; Nft result; result.num_of_levels = result_num_of_levels; // Use composition storage without tracking inverted indices, - // as the waiting in virtual states would spoit the inverse mapping. + // because waiting in virtual states would spoil the inverse mapping. TwoDimensionalMap composition_storage(lhs_num_of_states, rhs_num_of_states); - std::deque> worklist; + // I use a queue for the worklist to process states in a breadth-first manner. + // This helps the branch prediction in the CPU because all processed states + // are likely to be at the same level and thus enter the same branch. + std::queue> worklist; /** - * @brief Create a new composition state for the given pair of states, if it does not already exist. + * @brief Creates a new composition state for the given pair of states, if it does not already exist. * - * @param state_a The first state (lhs or rhs). - * @param state_b The second state (rhs or lhs). + * @param first The first state (from lhs or rhs). + * @param second The second state (from rhs or lhs). * @param level The level of the new composition state. - * @param is_a_lhs If true, state_a is from the lhs NFT, otherwise it is from the rhs NFT. - * @param composition_state_to_add If provided, this is the state to be added to the result NFT. - * If not provided, a new state will be created. + * @param is_first_lhs If true, @p first is from the lhs NFT; otherwise, it is from the rhs NFT. + * @param composition_state_to_add If provided, this is the state to be added to the result NFT; + * if not provided, a new state will be created. * * @return The composition state for the given pair of states. */ - auto create_composition_state = [&](const State state_a, const State state_b, const Level level, const bool is_a_lhs = true, const State composition_state_to_add = Limits::max_state) { - assert(composition_state_to_add == Limits::max_state || result.levels[composition_state_to_add] == level); - + auto create_composition_state = [&](const State first, + const State second, + const Level level, + const bool is_first_lhs = true, + const State composition_state_to_add = Limits::max_state) + { + const auto key = is_first_lhs ? std::make_pair(first, second) + : std::make_pair(second, first); + // Try to find the entry in the state map. - const auto key = is_a_lhs ? std::make_pair(state_a, state_b) : std::make_pair(state_b, state_a); - const State lhs_state = key.first; - const State rhs_state = key.second; - const State found_state = composition_storage.get(lhs_state, rhs_state); + const State found_state = composition_storage.get(key.first, key.second); + assert(found_state == composition_state_to_add); if (found_state != Limits::max_state) { - assert(found_state == composition_state_to_add || composition_state_to_add == Limits::max_state); return found_state; } // If not found, add a new state to the result NFT. - State new_state = (composition_state_to_add != Limits::max_state) ? composition_state_to_add : result.add_state_with_level(level); - composition_storage.insert(lhs_state, rhs_state, new_state); - - // If the level is zero, we need to check for final states and add to the worklist. + // Since the key pair was not found in the map, we can be certain that the state is not yet in the worklist. + const State new_state = (composition_state_to_add != Limits::max_state) ? composition_state_to_add + : result.add_state_with_level(level); + composition_storage.insert(key.first, key.second, new_state); if (level == 0) { - // Because the key pair was not found in the map, we are sure that the state was not in the worklist yet either. - worklist.push_back(key); - if ((is_a_lhs && lhs.final.contains(state_a) && rhs.final.contains(state_b)) || - (!is_a_lhs && rhs.final.contains(state_a) && lhs.final.contains(state_b))) { + // If the level is zero, check for final states and add the state to the worklist. + if ((is_first_lhs && lhs.final.contains(first) && rhs.final.contains(second)) || + (!is_first_lhs && rhs.final.contains(first) && lhs.final.contains(second))) + { result.final.insert(new_state); } - } else { - worklist.push_front(key); // Push to the front for non-zero levels. } - + worklist.push(key); return new_state; }; /** - * @brief Perform the synchronization of LHS and RHS at the given state pair. - * - * @param composition_state The state in the composition NFT from where the synchronization result will lead. - * @param lhs_state The state in the LHS NFT that is being synchronized. - * @param rhs_state The state in the RHS NFT that is being synchronized. - * @param reconnect If true, the synchronization will reconnect the predecessors of the synchronization level to - * its successors, otherwise it will just synchronize and keep/remove the synchronization level - * depending on the project_out_sync_levels flag. - * @param reconnection_symbol The symbol to use for the reconnection transition if reconnect is true. + * @brief Perform the synchronization of the LHS and RHS at the given state pair. + * + * @param composition_state The state in the composition NFT from which the synchronization result will proceed. + * @param lhs_state The state in the LHS NFT being synchronized. + * @param rhs_state The state in the RHS NFT being synchronized. + * @param reconnect If true, the synchronization will use the specified symbol on the resulting + * synchronization transition, virtually reconnecting the predecessors of + * the synchronization level to its successors; otherwise, it will simply + * synchronize and keep or remove the synchronization level depending on + * the project_out_sync_levels flag. + * @param reconnection_symbol The symbol to use for the reconnection transition if reconnect is true. */ auto synchronize = [&](const State composition_state, const State lhs_state, @@ -236,9 +249,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; const Level composition_target_level = (result.levels[composition_state] + 1) % result.num_of_levels; - // When we are projecting out the synchronization levels, and we don't want to connect its - // predecessors to its successors, we will just do the synchronization, note reached targets - // and remove (vanish) the synchronization level with its transition from the result NFT. + // When projecting out the synchronization levels, and we do not want to connect their + // predecessors to their successors, we simply perform the synchronization, note the reached targets, + // and remove (vanish) the synchronization level and its transition from the result NFT. const bool vanish_sync_level = !reconnect && project_out_sync_levels; // Helper function to combine LHS and RHS targets over the given symbol. @@ -246,10 +259,17 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le for (const State lhs_sync_target : lhs_sync_targets) { for (const State rhs_sync_target : rhs_sync_targets) { if (vanish_sync_level) { - worklist.push_back({ lhs_sync_target, rhs_sync_target }); + worklist.push({ lhs_sync_target, rhs_sync_target }); } else { - result.add_transition_with_target(composition_state, symbol, - create_composition_state(lhs_sync_target, rhs_sync_target, composition_target_level, true), jump_mode); + result.add_transition_with_target( + composition_state, + symbol, + create_composition_state(lhs_sync_target, + rhs_sync_target, + composition_target_level, + true), + jump_mode + ); } } } @@ -262,7 +282,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le while (sync_iterator.advance()) { const std::vector& same_symbol_posts{ sync_iterator.get_current() }; assert(same_symbol_posts.size() == 2); // One move per state in the pair. - const Symbol symbol = project_out_sync_levels ? reconnection_symbol : same_symbol_posts[0]->symbol; + const Symbol symbol = reconnect ? reconnection_symbol + : same_symbol_posts[0]->symbol; combine_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); } @@ -274,7 +295,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We don't want to synchronize DONT_CARE with EPSILON. continue; } - const Symbol symbol = project_out_sync_levels ? reconnection_symbol : rhs_symbol_post.symbol; + const Symbol symbol = reconnect ? reconnection_symbol + : rhs_symbol_post.symbol; combine_targets(lhs_dont_care_sync_it->targets, rhs_symbol_post.targets, symbol); } } @@ -287,22 +309,24 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We don't want to synchronize DONT_CARE with EPSILON. continue; } - const Symbol symbol = project_out_sync_levels ? reconnection_symbol : lhs_symbol_post.symbol; + const Symbol symbol = reconnect ? reconnection_symbol + : lhs_symbol_post.symbol; combine_targets(lhs_symbol_post.targets, rhs_dont_care_sync_it->targets, symbol); } } }; + /** * @brief Copy transitions from the copy NFT to the composition NFT. * - * @param composition_state The source state in the composition NFT where the transitions will be connected to. + * @param composition_state The source state in the composition NFT from which the transitions will be connected. * @param copy_state The state in the copy NFT from which the transitions will be copied. - * @param stationar_state The state in the other NFT (stationary NFT) that does not move. - * @param is_copy_state_lhs If true, the copy_state is from the LHS NFT, otherwise it is from the RHS NFT. + * @param stationar_state The state in the other NFT (the stationary NFT) that does not move. + * @param is_copy_state_lhs If true, the copy_state is from the LHS NFT; otherwise, it is from the RHS NFT. * @param waiting_worklist If set, the function is called from the waiting simulation, - meaning that we are waiting in the virtual loop at the stationary state, - and we will use the waiting_worklist to put next state pairs to. + * meaning that we are waiting in the virtual loop at the stationary state, + * and we will use the waiting_worklist to store the next state pairs. */ auto copy_transition = [&](const State composition_state, const State copy_state, @@ -322,9 +346,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const std::vector& copy_sync_types = copy_sync_props.sync_types_v; const SynchronizationType stationar_state_sync_type = stationar_sync_props.sync_types_v[stationar_state]; - // We need to handle any synchronization here in place, if we want to project out - // the synchronization level and if there is no transition remainging after the - // synchronization level in any of the NFTs. + + // It may happen that we encounter a transition whose target is at the synchronization level, + // the state in the stationary NFT is also at the synchronization level (or we came from the waiting simulation), + // project_out_sync_levels is true, and there is no other transition that would be added + // before the next zero-level state. In this case, we need to handle this synchronization + // here in place and make the connection directly to the next zero-level state. const bool handle_synchronization_in_place = project_out_sync_levels && stationar_sync_props.num_of_levels_after_sync == 0 && copy_sync_props.num_of_levels_after_sync == 0 && ( @@ -335,10 +362,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le for (const SymbolPost& copy_symbol_post : copy_nft.delta[copy_state]) { for (const State copy_target : copy_symbol_post.targets) { const SynchronizationType copy_target_sync_type = copy_sync_types[copy_target]; - // It makes sence to onyl continue if we beleave that we can synchronize. - // When the function has been called from the waiting simulation, we are - // interested in synchronizations on EPSILON. - // This should also work for the case when we are past the synchronization level. + // It makes sense to continue only if we believe that we will not get stuck + // later due to an inability to synchronize. When this function is called + // from the waiting simulation, we are interested in onyl synchronizations on EPSILON. const bool can_synchronize_in_the_future = ( waiting_worklist != nullptr ? copy_target_sync_type != SynchronizationType::ONLY_ON_SYMBOL : stationar_state_sync_type == copy_target_sync_type || @@ -346,8 +372,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le copy_target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL ); if (!can_synchronize_in_the_future) { - // There is no way we would be able to synchronize. - // We don't need to explore this path. + // There is no way we would be able to synchronize in the future. + // We do not need to explore this path further. continue; } @@ -359,46 +385,63 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (handle_synchronization_in_place && target_level == copy_sync_level) { // The target is at the synchronization level that will be projected out. // We know that there are no transitions after the synchronization level - // in any of the NFTs. Therefore, we have to do the synchronization and - // make this transition go all the way to the next zero-level state. + // in any of the NFTs. Therefore, we must perform the synchronization here + // and connect this transition directly to the next zero-level state. if (waiting_worklist != nullptr) { - // We have beed called from the simulation of the waiting. - // We are waiting in the stationar state. We need to handle the waiting - // synchronization over EPSILON here. - const auto& copy_symbol_post_eps = copy_nft.delta[copy_target].find(EPSILON); - assert(copy_symbol_post_eps != copy_nft.delta[copy_target].end()); - for (const State inplace_copy_target : copy_symbol_post_eps->targets) { - assert(copy_nft.levels[inplace_copy_target] == 0); - result.add_transition_with_target(composition_state, copy_symbol_post.symbol, - create_composition_state(inplace_copy_target, stationar_state, 0, is_copy_state_lhs), jump_mode); + // We have been called from the waiting simulation. + // We are waiting in the stationary state. + // During this, only EPSILON synchronizations are allowed. + const auto& copy_eps_symbol_post = copy_nft.delta[copy_target].find(EPSILON); + assert(copy_eps_symbol_post != copy_nft.delta[copy_target].end()); + for (const State sync_target : copy_eps_symbol_post->targets) { + assert(copy_nft.levels[sync_target] == 0); + result.add_transition_with_target( + composition_state, + copy_symbol_post.symbol, + create_composition_state(sync_target, + stationar_state, + 0, + is_copy_state_lhs), + jump_mode + ); } + // We do not need to add new pairs to the waiting_worklist, + // because, if necessary, they have already been added to the main + // worklist by the create_composition_state function. } else { - // We encountered normal synchronization. Call the synchronization function. - // The only way we can reach synchronization point in both states, while - // waiting in one state them, is when the waiting state is the LHS state. - // Wecause each transition in the LHS goes before the transitions in the RHS. + // We encountered a standard synchronization and need to synchronize the LHS and RHS. + // Call the synchronization function with the reconnection parameters set. + // The only way to reach this synchronization point in both states, while waiting + // in one of them, is when the waiting state was the LHS state. + // This is because each transition in the LHS occurs before the transitions in the RHS. assert(!is_copy_state_lhs); synchronize(composition_state, stationar_state, copy_target, true, copy_symbol_post.symbol); } - } else { - // We are not at the synchronization point. - // We just need to copy the transtion. + // We are not at a synchronization point. + // We just need to copy the transition. const Level new_composition_state_level = (composition_state_level + trans_len) % result.num_of_levels; - // The creation of new composition state depends on whether we were - // called from the waiting simulation or not. - // If we were called from the waiting simulation, we need to create state that copy_target - // and a virtual state over the virtual waiting loop over the stationar state. - // Because the virtual state is not part of the waiting NFT, we can not use create_composition_state - // function, because it uses the composition_storage that does not contain the virtual state. if (waiting_worklist != nullptr && new_composition_state_level != 0) { + // We are not connecting to the zero-level state, and we were + // called from the waiting simulation. Therefore, we need to create an auxiliary state + // that will not be tracked in the composition_storage. We cannot use composition_storage + // because, while waiting in the stationary state, we are not exactly at that state, + // but rather in virtual (nonexistent) states of the waiting loop over it. const State new_composition_state = result.add_state_with_level(new_composition_state_level); result.add_transition_with_target(composition_state, copy_symbol_post.symbol, new_composition_state, jump_mode); + // Because we are waiting, use the waiting_worklist. waiting_worklist->push({ new_composition_state, stationar_state }); } else { + // Just copy this transition. + assert(waiting_worklist == nullptr); result.add_transition_with_target( - composition_state, copy_symbol_post.symbol, - create_composition_state(copy_target, stationar_state, new_composition_state_level, is_copy_state_lhs), jump_mode + composition_state, + copy_symbol_post.symbol, + create_composition_state(copy_target, + stationar_state, + new_composition_state_level, + is_copy_state_lhs), + jump_mode ); } } @@ -407,24 +450,34 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le }; /** - * @brief Model the waiting in one of the NFTs if they could not synchronize due to the EPSILON or symbol transition. + * @brief Models the "waiting" in one of the NFTs when it cannot synchronize + * with the other NFT doe to a lacking EPSILON on the synchronization level. * - * For example, if there is EPSILON on the synchronization level in the RHS and LHS can not synchronize on it, - * then the LHS needs to wait in the root (zero-level) state that precedes the synchronization and RHS will - * continue in the corresponding RHS root ower the problematic EPSILON synchronization to the next zero-level state. - * The RHS is going to act al if it is interleaving with the LHS transitions (LHS transitions always go before RHS transitions), - * however it will place the EPSILON on each such a transition from the LHS. + * For example, if there is an EPSILON transition at the synchronization level + * in the RHS, and the LHS cannot synchronize on it, then the LHS must "wait" + * in its root (zero-level) state that precedes the synchronization. Meanwhile, + * the RHS continues from its corresponding root state, traversing the problematic + * EPSILON synchronization to the next zero-level state. The RHS behaves as if it + * is interleaving with the LHS transitions (LHS transitions always occur before RHS + * transitions), but it will insert an EPSILON for each such transition from the LHS. * - * The problem is if we want to project out the synchronization levels, but there are not transitions following them - * in the LHS not RHS (the synchronization level is the last level in both of them). Then one level before the - * synchronization level, we need to connect successors of such states to the zero-level state that lies after the synchronization level. + * A special case arises when we want to project out synchronization levels, + * but there are no transitions following them in either the LHS or RHS (i.e., + * the synchronization level is the last level in both NFTs). In this situation, + * one state before the synchronization level, we need to connect the successors + * of such states to the zero-level state that follows the synchronization level. * * @param composition_root_state The root state of the composition NFT. - * @param waiting_root_state The root state of the NFT that is waiting (LHS or RHS). - * @param running_root_state The root state of the NFT that is running (LHS or RHS). - * @param is_lhs_waiting If true, the LHS is waiting, otherwise the RHS is waiting. + * @param waiting_root_state The root state of the NFT that is waiting (either LHS or RHS). + * @param running_root_state The root state of the NFT that is running (either LHS or RHS). + * @param is_lhs_waiting If true, the waiting root is from the LHS; + * otherwise, it is from the RHS. */ - auto model_waiting = [&](const State composition_root_state, const State waiting_root_state, const State running_root_state, const bool is_lhs_waiting) { + auto model_waiting = [&](const State composition_root_state, + const State waiting_root_state, + const State running_root_state, + const bool is_lhs_waiting) + { const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props : lhs_sync_props; const SynchronizationProperties& waiting_sync_props = is_lhs_waiting ? lhs_sync_props @@ -434,26 +487,32 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Delta& running_delta = running_sync_props.nft.delta; const size_t waiting_trans_before_sync = waiting_sync_props.num_of_levels_before_sync; const size_t waiting_trans_after_sync = waiting_sync_props.num_of_levels_after_sync; + // EPSILON synchronization is handled differently here than in the main loop. + // To avoid duplicate transitions and speed up the algorithm, we replace each + // EPSILON synchronization with N EPSILON transitions, where: + // - Add 1 to N if we are NOT projecting out synchronization levels. + // - Add waiting_trans_before_sync to N if waiting in RHS + // (RHS transitions go after LHS, just before the synchronization). + // - Add waiting_trans_after_sync to N if waiting in LHS + // (LHS transitions go before RHS, just after the synchronization). const size_t num_of_epsilons_to_replace_sync = ( (is_lhs_waiting ? waiting_trans_after_sync : waiting_trans_before_sync) + (project_out_sync_levels ? 0 : 1) ); - // We cannot use inverted storage here, because there can be one composition - // state for multiple original pairs of states. Therefore, we need to keep - // track of it in the queue instead. + // Initialization of the local worklist. + // I chose to use a queue instead of a stack to help the branch predictor + // with conditions inside the loop. With the queue, the algorithm hits the + // same branch more ofthen, because all states being explored advance "together". std::queue> worklist; - - // Initialization of the worklist. - // If we know at this point, that waiting is LHS and there are transtiion before - // the synchronization level, that we need to represent in the resulting NFT, - // as EPSILON transitions, and because LHS goes before RHS, we can add this - // transition immediately here during the initialization of the worklist. - if (!is_lhs_waiting || waiting_trans_before_sync == 0) { - worklist.push({ composition_root_state, running_root_state}); - } else { + if (is_lhs_waiting && waiting_trans_before_sync > 0) { + // Since LHS is waiting and LHS transitions have to be put before + // RHS transitions, we now add waiting_trans_before_sync EPSILON + // transitions. Doing this here simplifies the conditions in the loop below. const State new_composition_state = result.add_transition_with_lenght(composition_root_state, EPSILON, waiting_trans_before_sync, jump_mode); worklist.push({ std::move(new_composition_state), running_root_state }); + } else { + worklist.push({ composition_root_state, running_root_state }); } // The main loop of the waiting simulation. @@ -464,63 +523,92 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (running_state_level == sync_level) { // We are at the synchronization level. + const bool is_last_running_transition = (running_state_level == running_sync_props.nft.num_of_levels - 1); + + // It cannot happen that the synchronization level is projected out + // and there are no other transitions to addbefore reaching the next zero-level state. + // This is because such a scenario would already be handled by the copy_transition function. + assert(!project_out_sync_levels || !is_last_running_transition || waiting_trans_after_sync > 0); + if (num_of_epsilons_to_replace_sync > 1) { - // If the synchronization is going to be replace by multiple EPSILON transitions, - // we can reduce the number of redundant EPSILON transtions by adding n-1 transitions - // now and then use the last one to connect with the target state. + // The synchronization will be replaced by an EPSILON transition anyway. + // To reduce the number of redundant EPSILON transitions in the resulting NFT, + // we can add N-1 EPSILON transitions now, and then use the last one to connect + // to the adequate target state, where N is num_of_epsilons_to_replace_sync. composition_state = result.add_transition_with_lenght(composition_state, EPSILON, num_of_epsilons_to_replace_sync - 1, jump_mode); } - const bool is_last_transition = (running_state_level == running_sync_props.nft.num_of_levels - 1); + // Do the EPSILON synchronization. + // We cannot use the synchronize function here, because we want to + // focus only on the EPSILON transitions. const auto& running_epsilon_post_it = running_delta[running_state].find(EPSILON); + // There should be an EPSILON transition. It has been told by the SynchronizationType. assert(running_epsilon_post_it != running_delta[running_state].end()); for (const State running_epsilon_target : running_epsilon_post_it->targets) { + assert(running_levels[running_epsilon_target] == 0 || running_levels[running_epsilon_target] > running_state_level); if (num_of_epsilons_to_replace_sync == 0) { - // This synchronization level is being projected out. - // We don't need to worry about if there is any useful transition - // after this, because if not, that it would be already handled - // by the copy_transition function. - assert(!is_last_transition); + // This synchronization level is being projected out (it has vanished). + // There are no EPSILON transitions to add before or after the synchronization level. + // However, we are sure, that there will be a least one "running" transition after this. + assert(!is_last_running_transition); worklist.push({ composition_state, running_epsilon_target }); - } else if (is_last_transition) { + } else if (is_last_running_transition) { // We are connecting to the zero-level state, therefore we have to // create a new composition state that will be put into the main worklist // (side effect of the create_composition_state). - result.add_transition_with_target(composition_state, EPSILON, - create_composition_state(running_epsilon_target, running_root_state, 0, is_lhs_waiting), jump_mode); + assert(result.levels[composition_state] + 1 == result.num_of_levels); + result.add_transition_with_target( + composition_state, + EPSILON, + create_composition_state(waiting_root_state, + running_epsilon_target, + 0, + is_lhs_waiting), + jump_mode + ); } else { // We are connecting to the next state in the waiting loop, - // therefore we can just add a transition to the next state and - // continue the waiting simulation. - const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, num_of_epsilons_to_replace_sync, jump_mode); + // so we simply add a transition to the next state and + // continue the waiting + const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, 1, jump_mode); worklist.push({ new_composition_state, running_epsilon_target }); } } } else if (running_state_level == 0 && running_state != running_root_state) { - // We are at the zero-level state at the running NFT. - // Because of previous checks, we know that this could only happen, - // if the running NFT is the LHS NFT and there is are remaining transitions - // in the waiting RHS NFT. Because RHS always goes after LHS, we can - // just add a transition to the zero-level state and continue from there - // in the main composition loop. + // We are at a leaf zero-level state in the running NFT. + // We will create a connection to the zero-level state in the composition NFT + // and continue exploring that state in the main composition loop + // (i.e., we push it to the main worklist, if not already visited). + // Note: This situation can only occur if the waiting NFT is the RHS, + // because RHS transitions follow LHS transitions. Moreover, the last + // transition in the running LHS NFT should not be at the synchronization + // level; otherwise, it would be handled by the condition above. assert(!is_lhs_waiting); + assert(sync_level != running_sync_props.nft.num_of_levels - 1); assert(waiting_trans_after_sync > 0); assert((running_state_level + waiting_trans_after_sync) % result.num_of_levels == 0); - result.add_transition_with_target(composition_state, EPSILON, - create_composition_state(running_state, running_root_state, 0, is_lhs_waiting), jump_mode); + result.add_transition_with_target( + composition_state, + EPSILON, + create_composition_state(running_state, + running_root_state, + 0, + is_lhs_waiting), + jump_mode + ); } else { - // We are at some internal level in the running NFT. - // We can just copy the transitions to next states. - // We don't need to worry about potential synchronization just - // after this level that would go to zero-level state, - // because if such a synchronization exists, - // it will be handled by the copy_transition function. - copy_transition(composition_state, running_state, running_root_state, is_lhs_waiting, &worklist); + // We are at an internal (non-sync) level in the running NFT. + // We can simply copy the transitions to the next states. + // We do not need to worry that the target state might be at the + // synchronization level with no further transitions in either + // the LHS or RHS after it, because such cases will be handled + // inside the copy_transition function. + copy_transition(composition_state, running_state, waiting_root_state, !is_lhs_waiting, &worklist); } } }; - // Initialization of the worklist. + // Initialization of the main worklist. for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { // Get the root state in the result NFT @@ -531,22 +619,22 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // The maing loop of the composition algorithm. while (!worklist.empty()) { const auto [lhs_state, rhs_state] = worklist.front(); - worklist.pop_front(); + worklist.pop(); const State composition_state = composition_storage.get(lhs_state, rhs_state); assert(composition_state != Limits::max_state); const Level lhs_level = lhs.levels[lhs_state]; const Level rhs_level = rhs.levels[rhs_state]; + const SynchronizationType lhs_sync_type = lhs_sync_props.sync_types_v[lhs_state]; + const SynchronizationType rhs_sync_type = rhs_sync_props.sync_types_v[rhs_state]; - const SynchronizationType lhs_sync_type = lhs_sync_types_v[lhs_state]; - const SynchronizationType rhs_sync_type = rhs_sync_types_v[rhs_state]; if (lhs_level == 0 and rhs_level == 0) { - // We are at the zero level states before any synchronization level. - // It is now time to decide is we want to continue the synchronization and/or - // to wait in the lhs_state and/or rhs_state, because there is a synchronization, - // where on one of the transducers is EPSILON and on the other one is an alphabet symbol. - // Note that if we can not synchronize in the future due to the EPSILON, - // we will simulate waiting in the zero-level state of the transducer that does not - // have the EPSILON transition that cause the synchronization problem. + // We are at the zero-level states before any synchronization level. + // Now is the time to decide whether to continue synchronization and/or + // to wait in the lhs_state and/or rhs_state. This is necessary when + // there is a synchronization where one of the transducers has an EPSILON + // and the other has an alphabet symbol. Note that if future synchronization + // is impossible due to the EPSILON, we will simulate waiting in the zero-level + // state of the transducer that does not have that EPSILON. // You can see the table defining operations for pair of synchronization types // in the documentation of the SynchronizationType enum class. @@ -558,52 +646,63 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le lhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL; if (perform_wait_on_lhs) { - // LHS is waiting (i.e., there is an EPSILON in the RHS that we can not synchronize on). + // LHS is waiting (i.e., there is an EPSILON in the RHS that LHS can not synchronize on). model_waiting(composition_state, lhs_state, rhs_state, true); } if (perform_wait_on_rhs) { - // RHS is waiting (i.e., there is an EPSILON in the LHS that we can not synchronize on). + // RHS is waiting (i.e., there is an EPSILON in the LHS that RHS can not synchronize on). model_waiting(composition_state, rhs_state, lhs_state, false); } if (!can_synchronize_in_the_future) { - // There is no way we would be able to synchronize in the future. - // It does not make sence to continue exploration of this pair. + // There is no way to perform the synchronization after these states. + // Therefore, it does not make sense to continue exploring this pair. continue; } } - // It makes sence to onyl continue if we beleave that we can synchronize, - // or we already passed the synchronization level (i.e., lhs_sync_type == - // rhs_sync_type == SynchronizationType::UNINITIALIZED). + // It makes sense to continue only if we believe that synchronization is possible, + // or if we have already passed the synchronization level (i.e., lhs_sync_type == + // rhs_sync_type == SynchronizationType::UNDEFINED). assert(lhs_sync_type == rhs_sync_type || lhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL); - // Both LHS and RHS can do a step if they are not at the synchronization level. - // However, there is a special case when LHS already reached the last zero-level state, - // because there was no transition after the synchronization level in the LHS. Because - // of that, we need to not allow LHS to do a step in this case. - // The same does not apply to the RHS, because it always goes after the LHS. + // Both LHS and RHS can take a step if they are not at the synchronization level. + // The LHS always moves before the RHS (i.e., lhs_level < rhs_level). + // However, there is a special case when lhs_level < rhs_level, but the LHS cannot move. + // This occurs when the LHS has already reached its last (leave) zero-level state, + // which happens if there are no transitions after the synchronization level in the LHS. const bool do_step_in_lhs = lhs_level != lhs_sync_level && (lhs_level != 0 || rhs_level == 0); const bool do_step_in_rhs = rhs_level != rhs_sync_level; if (do_step_in_lhs) { - // LHS alway goes before the RHS. + // Always try to do the step in the LHS first. + // We don't need to worry about the case when this is the last + // transition in the LHS, the RHS is already at the synchronization level, + // and we are projecting out the synchronization levels. + // In such situation, the copy_transition function will handle it. copy_transition(composition_state, lhs_state, rhs_state, true); } else if (do_step_in_rhs) { - // LHS is probably at the synchronization level or at the final zero-level state. + // LHS is probably at the synchronization level or at the last (leave) zero-level state. // It's time for RHS to make a step. + // We don't need to worry about the case when this is the last + // transition in the RHS, the LHS is already at the synchronization level, + // and we are projecting out the synchronization levels. + // In such situation, the copy_transition function will handle it. copy_transition(composition_state, rhs_state, lhs_state, false); } else { // Both LHS and RHS are at the synchronization level. - // Perform the synchronization this way is only possible if: - // 1) Some transition in the LHS or RHS that follows after - // the synchronization level (so we can connect to the successors), or - // 2) There is not transition in the LHS or RHS that follows after - // the synchronization level, but we are not projecting out the - // synchronization levels. We need to use the synchronization - // transition to connect to the zero-level successors of the synchronization. + // Synchronization at this point is only possible if: + // 1) There is at least one transition in either the LHS or + // RHS that follows the synchronization level + // (so we can connect to their successors), or + // 2) We are not projecting out the synchronization levels. + // In this case, we use the synchronization transition to + // connect to the zero-level successors that follow. assert(lhs_level == lhs_sync_level && rhs_level == rhs_sync_level); + assert(!project_out_sync_levels || + lhs_sync_props.num_of_levels_after_sync > 0 || + rhs_sync_props.num_of_levels_after_sync > 0); synchronize(composition_state, lhs_state, rhs_state); } } From 0a564aa386db724f7806be6bc3018889e80c0647 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 21 Aug 2025 11:23:25 +0200 Subject: [PATCH 37/65] before testing --- src/nft/composition.cc | 142 +++--- src/nft/nft.cc | 28 +- tests/nft/nft-composition.cc | 868 +++++++++++++++++------------------ 3 files changed, 507 insertions(+), 531 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index e47d67078..d25b6b59d 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -68,8 +68,8 @@ namespace { std::vector sync_types_v; SynchronizationProperties(const Nft& nft, const Level sync_level) - : nft(nft), - sync_level(sync_level), + : nft(nft), + sync_level(sync_level), num_of_levels_before_sync(sync_level), num_of_levels_after_sync(nft.num_of_levels - sync_level - 1), sync_types_v(nft.num_of_states(), SynchronizationType::UNDEFINED) @@ -161,6 +161,8 @@ namespace mata::nft Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, const bool project_out_sync_levels, const JumpMode jump_mode) { assert(lhs_sync_level < lhs.num_of_levels && rhs_sync_level < rhs.num_of_levels); + return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels, jump_mode); + throw std::invalid_argument("Synchronization levels must be less than the number of levels in the NFTs."); // Number of Levels and States const size_t lhs_num_of_states = lhs.num_of_states(); @@ -195,14 +197,14 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le * @return The composition state for the given pair of states. */ auto create_composition_state = [&](const State first, - const State second, - const Level level, - const bool is_first_lhs = true, - const State composition_state_to_add = Limits::max_state) + const State second, + const Level level, + const bool is_first_lhs = true, + const State composition_state_to_add = Limits::max_state) { - const auto key = is_first_lhs ? std::make_pair(first, second) + const auto key = is_first_lhs ? std::make_pair(first, second) : std::make_pair(second, first); - + // Try to find the entry in the state map. const State found_state = composition_storage.get(key.first, key.second); assert(found_state == composition_state_to_add); @@ -212,13 +214,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // If not found, add a new state to the result NFT. // Since the key pair was not found in the map, we can be certain that the state is not yet in the worklist. - const State new_state = (composition_state_to_add != Limits::max_state) ? composition_state_to_add + const State new_state = (composition_state_to_add != Limits::max_state) ? composition_state_to_add : result.add_state_with_level(level); composition_storage.insert(key.first, key.second, new_state); if (level == 0) { // If the level is zero, check for final states and add the state to the worklist. if ((is_first_lhs && lhs.final.contains(first) && rhs.final.contains(second)) || - (!is_first_lhs && rhs.final.contains(first) && lhs.final.contains(second))) + (!is_first_lhs && rhs.final.contains(first) && lhs.final.contains(second))) { result.final.insert(new_state); } @@ -244,11 +246,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const State lhs_state, const State rhs_state, const bool reconnect = false, - const Symbol reconnection_symbol = Limits::max_symbol) + const Symbol reconnection_symbol = Limits::max_symbol) { - const Level lhs_level = lhs.levels[lhs_state]; - const Level rhs_level = rhs.levels[rhs_state]; - const Level composition_target_level = (result.levels[composition_state] + 1) % result.num_of_levels; + const Level composition_target_level = static_cast((result.levels[composition_state] + 1) % result.num_of_levels); // When projecting out the synchronization levels, and we do not want to connect their // predecessors to their successors, we simply perform the synchronization, note the reached targets, // and remove (vanish) the synchronization level and its transition from the result NFT. @@ -262,12 +262,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le worklist.push({ lhs_sync_target, rhs_sync_target }); } else { result.add_transition_with_target( - composition_state, + composition_state, symbol, - create_composition_state(lhs_sync_target, - rhs_sync_target, + create_composition_state(lhs_sync_target, + rhs_sync_target, composition_target_level, - true), + true), jump_mode ); } @@ -282,7 +282,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le while (sync_iterator.advance()) { const std::vector& same_symbol_posts{ sync_iterator.get_current() }; assert(same_symbol_posts.size() == 2); // One move per state in the pair. - const Symbol symbol = reconnect ? reconnection_symbol + const Symbol symbol = reconnect ? reconnection_symbol : same_symbol_posts[0]->symbol; combine_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); } @@ -295,7 +295,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We don't want to synchronize DONT_CARE with EPSILON. continue; } - const Symbol symbol = reconnect ? reconnection_symbol + const Symbol symbol = reconnect ? reconnection_symbol : rhs_symbol_post.symbol; combine_targets(lhs_dont_care_sync_it->targets, rhs_symbol_post.targets, symbol); } @@ -309,17 +309,17 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We don't want to synchronize DONT_CARE with EPSILON. continue; } - const Symbol symbol = reconnect ? reconnection_symbol + const Symbol symbol = reconnect ? reconnection_symbol : lhs_symbol_post.symbol; combine_targets(lhs_symbol_post.targets, rhs_dont_care_sync_it->targets, symbol); } } }; - + /** * @brief Copy transitions from the copy NFT to the composition NFT. - * + * * @param composition_state The source state in the composition NFT from which the transitions will be connected. * @param copy_state The state in the copy NFT from which the transitions will be copied. * @param stationar_state The state in the other NFT (the stationary NFT) that does not move. @@ -330,13 +330,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le */ auto copy_transition = [&](const State composition_state, const State copy_state, - const State stationar_state, + const State stationar_state, const bool is_copy_state_lhs, - std::queue>* waiting_worklist = nullptr) + std::queue>* waiting_worklist = nullptr) { - const SynchronizationProperties& copy_sync_props = is_copy_state_lhs ? lhs_sync_props + const SynchronizationProperties& copy_sync_props = is_copy_state_lhs ? lhs_sync_props : rhs_sync_props; - const SynchronizationProperties& stationar_sync_props = is_copy_state_lhs ? rhs_sync_props + const SynchronizationProperties& stationar_sync_props = is_copy_state_lhs ? rhs_sync_props : lhs_sync_props; const Nft& copy_nft = copy_sync_props.nft; const Level composition_state_level = result.levels[composition_state]; @@ -345,7 +345,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Level copy_sync_level = copy_sync_props.sync_level; const std::vector& copy_sync_types = copy_sync_props.sync_types_v; const SynchronizationType stationar_state_sync_type = stationar_sync_props.sync_types_v[stationar_state]; - + // It may happen that we encounter a transition whose target is at the synchronization level, // the state in the stationary NFT is also at the synchronization level (or we came from the waiting simulation), @@ -355,10 +355,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const bool handle_synchronization_in_place = project_out_sync_levels && stationar_sync_props.num_of_levels_after_sync == 0 && copy_sync_props.num_of_levels_after_sync == 0 && ( - waiting_worklist != nullptr || + waiting_worklist != nullptr || stationar_state_level == stationar_sync_props.sync_level ); - + for (const SymbolPost& copy_symbol_post : copy_nft.delta[copy_state]) { for (const State copy_target : copy_symbol_post.targets) { const SynchronizationType copy_target_sync_type = copy_sync_types[copy_target]; @@ -396,9 +396,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le for (const State sync_target : copy_eps_symbol_post->targets) { assert(copy_nft.levels[sync_target] == 0); result.add_transition_with_target( - composition_state, + composition_state, copy_symbol_post.symbol, - create_composition_state(sync_target, + create_composition_state(sync_target, stationar_state, 0, is_copy_state_lhs), @@ -420,7 +420,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } else { // We are not at a synchronization point. // We just need to copy the transition. - const Level new_composition_state_level = (composition_state_level + trans_len) % result.num_of_levels; + const Level new_composition_state_level = static_cast((composition_state_level + trans_len) % result.num_of_levels); if (waiting_worklist != nullptr && new_composition_state_level != 0) { // We are not connecting to the zero-level state, and we were // called from the waiting simulation. Therefore, we need to create an auxiliary state @@ -435,12 +435,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Just copy this transition. assert(waiting_worklist == nullptr); result.add_transition_with_target( - composition_state, + composition_state, copy_symbol_post.symbol, - create_composition_state(copy_target, - stationar_state, - new_composition_state_level, - is_copy_state_lhs), + create_composition_state(copy_target, + stationar_state, + new_composition_state_level, + is_copy_state_lhs), jump_mode ); } @@ -474,13 +474,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le * otherwise, it is from the RHS. */ auto model_waiting = [&](const State composition_root_state, - const State waiting_root_state, - const State running_root_state, - const bool is_lhs_waiting) + const State waiting_root_state, + const State running_root_state, + const bool is_lhs_waiting) { - const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props + const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props : lhs_sync_props; - const SynchronizationProperties& waiting_sync_props = is_lhs_waiting ? lhs_sync_props + const SynchronizationProperties& waiting_sync_props = is_lhs_waiting ? lhs_sync_props : rhs_sync_props; const Level sync_level = running_sync_props.sync_level; const Levels& running_levels = running_sync_props.nft.levels; @@ -491,14 +491,14 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // To avoid duplicate transitions and speed up the algorithm, we replace each // EPSILON synchronization with N EPSILON transitions, where: // - Add 1 to N if we are NOT projecting out synchronization levels. - // - Add waiting_trans_before_sync to N if waiting in RHS + // - Add waiting_trans_before_sync to N if waiting in RHS // (RHS transitions go after LHS, just before the synchronization). // - Add waiting_trans_after_sync to N if waiting in LHS // (LHS transitions go before RHS, just after the synchronization). const size_t num_of_epsilons_to_replace_sync = ( (is_lhs_waiting ? waiting_trans_after_sync : waiting_trans_before_sync) + (project_out_sync_levels ? 0 : 1) - ); + ); // Initialization of the local worklist. // I chose to use a queue instead of a stack to help the branch predictor @@ -524,7 +524,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (running_state_level == sync_level) { // We are at the synchronization level. const bool is_last_running_transition = (running_state_level == running_sync_props.nft.num_of_levels - 1); - + // It cannot happen that the synchronization level is projected out // and there are no other transitions to addbefore reaching the next zero-level state. // This is because such a scenario would already be handled by the copy_transition function. @@ -539,7 +539,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } // Do the EPSILON synchronization. - // We cannot use the synchronize function here, because we want to + // We cannot use the synchronize function here, because we want to // focus only on the EPSILON transitions. const auto& running_epsilon_post_it = running_delta[running_state].find(EPSILON); // There should be an EPSILON transition. It has been told by the SynchronizationType. @@ -558,12 +558,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // (side effect of the create_composition_state). assert(result.levels[composition_state] + 1 == result.num_of_levels); result.add_transition_with_target( - composition_state, + composition_state, EPSILON, - create_composition_state(waiting_root_state, - running_epsilon_target, - 0, - is_lhs_waiting), + create_composition_state(waiting_root_state, + running_epsilon_target, + 0, + is_lhs_waiting), jump_mode ); } else { @@ -580,7 +580,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // and continue exploring that state in the main composition loop // (i.e., we push it to the main worklist, if not already visited). // Note: This situation can only occur if the waiting NFT is the RHS, - // because RHS transitions follow LHS transitions. Moreover, the last + // because RHS transitions follow LHS transitions. Moreover, the last // transition in the running LHS NFT should not be at the synchronization // level; otherwise, it would be handled by the condition above. assert(!is_lhs_waiting); @@ -588,12 +588,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(waiting_trans_after_sync > 0); assert((running_state_level + waiting_trans_after_sync) % result.num_of_levels == 0); result.add_transition_with_target( - composition_state, + composition_state, EPSILON, - create_composition_state(running_state, - running_root_state, - 0, - is_lhs_waiting), + create_composition_state(running_state, + running_root_state, + 0, + is_lhs_waiting), jump_mode ); } else { @@ -620,17 +620,17 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le while (!worklist.empty()) { const auto [lhs_state, rhs_state] = worklist.front(); worklist.pop(); - const State composition_state = composition_storage.get(lhs_state, rhs_state); + const State composition_state = composition_storage.get(lhs_state, rhs_state); assert(composition_state != Limits::max_state); const Level lhs_level = lhs.levels[lhs_state]; - const Level rhs_level = rhs.levels[rhs_state]; + const Level rhs_level = rhs.levels[rhs_state]; const SynchronizationType lhs_sync_type = lhs_sync_props.sync_types_v[lhs_state]; const SynchronizationType rhs_sync_type = rhs_sync_props.sync_types_v[rhs_state]; - + if (lhs_level == 0 and rhs_level == 0) { // We are at the zero-level states before any synchronization level. // Now is the time to decide whether to continue synchronization and/or - // to wait in the lhs_state and/or rhs_state. This is necessary when + // to wait in the lhs_state and/or rhs_state. This is necessary when // there is a synchronization where one of the transducers has an EPSILON // and the other has an alphabet symbol. Note that if future synchronization // is impossible due to the EPSILON, we will simulate waiting in the zero-level @@ -651,7 +651,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } if (perform_wait_on_rhs) { // RHS is waiting (i.e., there is an EPSILON in the LHS that RHS can not synchronize on). - model_waiting(composition_state, rhs_state, lhs_state, false); + model_waiting(composition_state, rhs_state, lhs_state, false); } if (!can_synchronize_in_the_future) { // There is no way to perform the synchronization after these states. @@ -666,7 +666,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(lhs_sync_type == rhs_sync_type || lhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL); - + // Both LHS and RHS can take a step if they are not at the synchronization level. // The LHS always moves before the RHS (i.e., lhs_level < rhs_level). // However, there is a special case when lhs_level < rhs_level, but the LHS cannot move. @@ -693,15 +693,15 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } else { // Both LHS and RHS are at the synchronization level. // Synchronization at this point is only possible if: - // 1) There is at least one transition in either the LHS or + // 1) There is at least one transition in either the LHS or // RHS that follows the synchronization level // (so we can connect to their successors), or // 2) We are not projecting out the synchronization levels. // In this case, we use the synchronization transition to // connect to the zero-level successors that follow. assert(lhs_level == lhs_sync_level && rhs_level == rhs_sync_level); - assert(!project_out_sync_levels || - lhs_sync_props.num_of_levels_after_sync > 0 || + assert(!project_out_sync_levels || + lhs_sync_props.num_of_levels_after_sync > 0 || rhs_sync_props.num_of_levels_after_sync > 0); synchronize(composition_state, lhs_state, rhs_state); } @@ -714,10 +714,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_lev assert(!lhs_sync_levels.empty()); assert(lhs_sync_levels.size() == rhs_sync_levels.size()); - if (lhs_sync_levels.size() == 1 && rhs_sync_levels.size() == 1) { - // If we have only one synchronization level we can do it faster. - return compose(lhs, rhs, lhs_sync_levels.front(), lhs_sync_levels.front(), project_out_sync_levels, jump_mode); - } + // if (lhs_sync_levels.size() == 1 && rhs_sync_levels.size() == 1) { + // // If we have only one synchronization level we can do it faster. + // return compose(lhs, rhs, lhs_sync_levels.front(), lhs_sync_levels.front(), project_out_sync_levels, jump_mode); + // } // Inserts loop into the given Nft for each state with level 0. // The loop word is constructed using the EPSILON symbol for all levels, except for the levels diff --git a/src/nft/nft.cc b/src/nft/nft.cc index 358c56162..809ed61f1 100644 --- a/src/nft/nft.cc +++ b/src/nft/nft.cc @@ -547,7 +547,7 @@ State Nft::add_transition(const State source, const std::vector& symbols return insert_word(source, symbols); } -State Nft::add_transition_with_lenght(const State source, const Symbol symbol, const size_t length, const JumpMode jump_mode = JumpMode::RepeatSymbol) { +State Nft::add_transition_with_lenght(const State source, const Symbol symbol, const size_t length, const JumpMode jump_mode) { assert(source < num_of_states()); if (length == 0) { return source; } @@ -575,31 +575,7 @@ State Nft::add_transition_with_lenght(const State source, const Symbol symbol, c return target; } -void Nft::add_transition_with_target(State source, Symbol symbol, State target, JumpMode jump_mode = JumpMode::RepeatSymbol) { - assert(source < num_of_states()); - assert(target < num_of_states()); - - assert(levels[source] < levels[target] || levels[target] == 0); - const size_t trans_len = (levels[target] == 0 ? num_of_levels : levels[target]) - levels[source]; - - if (trans_len == 1 || jump_mode == JumpMode::RepeatSymbol) { - delta.add(source, symbol, target); - return; - } - - State inner_src = source; - Level inner_level = levels[inner_src] + 1; - for (size_t i = 0; i < trans_len - 1; ++i, ++inner_level) { - assert(inner_level < num_of_levels); - const State inner_target = add_state_with_level(inner_level); - delta.add(inner_src, symbol, inner_target); - inner_src = inner_target; - } - assert(inner_level == levels[source] + trans_len); - delta.add(inner_src, symbol, target); -} - -void Nft::add_transition_with_same_level_targets(State source, Symbol symbol, const StateSet& targets, JumpMode jump_mode = JumpMode::RepeatSymbol) { +void Nft::add_transition_with_same_level_targets(State source, Symbol symbol, const StateSet& targets, JumpMode jump_mode) { assert(targets.size() > 0); assert(source < num_of_states()); assert(std::all_of(targets.begin(), targets.end(), [&](State target) { return target < num_of_states(); })); diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 29bb2f15c..9745065af 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -552,437 +552,437 @@ TEST_CASE("Mata::nft::compose()") { } } -TEST_CASE("nft::compose_fast()") { - SECTION("project_out == true") { - SECTION("two levels easy match") { - Nft lhs; - lhs.num_of_levels = 2; - lhs.levels = { 0, 1, 0, 1, 0 }; - lhs.initial.insert(0); - lhs.final.insert(4); - lhs.delta.add(0, 'x', 1); - lhs.delta.add(1, 'a', 2); - lhs.delta.add(1, 'b', 2); - lhs.delta.add(2, 'y', 3); - lhs.delta.add(3, 'c', 4); - lhs.delta.add(3, 'd', 4); - - Nft rhs; - rhs.num_of_levels = 2; - rhs.levels = { 0, 1, 0, 1, 0 }; - rhs.initial.insert(0); - rhs.final.insert(4); - rhs.delta.add(0, 'x', 1); - rhs.delta.add(1, 'e', 2); - rhs.delta.add(1, 'f', 2); - rhs.delta.add(2, 'y', 3); - rhs.delta.add(3, 'g', 4); - rhs.delta.add(3, 'h', 4); - - Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, true, true, JumpMode::NoJump); - - Nft expected; - expected.num_of_levels = 2; - expected.levels = { 0, 1, 0, 1, 0 }; - expected.initial.insert(0); - expected.final.insert(4); - expected.delta.add(0, 'a', 1); - expected.delta.add(0, 'b', 1); - expected.delta.add(1, 'e', 2); - expected.delta.add(1, 'f', 2); - expected.delta.add(2, 'c', 3); - expected.delta.add(2, 'd', 3); - expected.delta.add(3, 'g', 4); - expected.delta.add(3, 'h', 4); - - CHECK(result.num_of_levels == 2); - CHECK(are_equivalent(result, expected)); - } - - SECTION("four levels with loop") { - Nft lhs; - lhs.num_of_levels = 4; - lhs.levels = { 0, 1, 2, 3, 0 }; - lhs.initial.insert(0); - lhs.final.insert(4); - lhs.delta.add(0, 'x', 1); - lhs.delta.add(1, 'a', 2); - lhs.delta.add(1, 'b', 2); - lhs.delta.add(2, 'y', 3); - lhs.delta.add(3, 'c', 4); - lhs.delta.add(3, 'd', 4); - lhs.delta.add(3, 'e', 0); - lhs.delta.add(3, 'f', 0); - - Nft rhs; - rhs.num_of_levels = 4; - rhs.levels = { 0, 1, 2, 3, 0 }; - rhs.initial.insert(0); - rhs.final.insert(4); - rhs.delta.add(0, 'g', 1); - rhs.delta.add(0, 'h', 1); - rhs.delta.add(1, 'x', 2); - rhs.delta.add(2, 'i', 3); - rhs.delta.add(2, 'j', 3); - rhs.delta.add(3, 'y', 4); - rhs.delta.add(3, 'y', 0); - - Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, true, true, JumpMode::NoJump); - - Nft expected; - expected.num_of_levels = 4; - expected.levels = { 0, 1, 2, 3, 0 }; - expected.initial.insert(0); - expected.final.insert(4); - expected.delta.add(0, 'g', 1); - expected.delta.add(0, 'h', 1); - expected.delta.add(1, 'a', 2); - expected.delta.add(1, 'b', 2); - expected.delta.add(2, 'i', 3); - expected.delta.add(2, 'j', 3); - expected.delta.add(3, 'c', 4); - expected.delta.add(3, 'd', 4); - expected.delta.add(3, 'e', 0); - expected.delta.add(3, 'f', 0); - - CHECK(result.num_of_levels == 4); - CHECK(are_equivalent(result, expected)); - } - - SECTION("synchronization of lst two levels with one mismatch") { - Nft lhs; - lhs.num_of_levels = 3; - lhs.levels = { 0, 1, 2 }; - lhs.initial.insert(0); - lhs.final.insert(0); - lhs.delta.add(0, 'u', 1); - lhs.delta.add(0, 'v', 1); - lhs.delta.add(1, 'x', 2); - lhs.delta.add(2, 'y', 0); - - Nft rhs; - rhs.num_of_levels = 4; - rhs.levels = { 0, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0}; - rhs.initial.insert(0); - rhs.final.insert(7); - rhs.final.insert(14); - rhs.final.insert(15); - rhs.delta.add(0, 'a', 1); - rhs.delta.add(0, 'b', 1); - rhs.delta.add(1, 'c', 2); - rhs.delta.add(1, 'd', 2); - rhs.delta.add(1, 'c', 3); - rhs.delta.add(1, 'd', 3); - rhs.delta.add(2, 'x', 4); - rhs.delta.add(4, 'y', 6); - rhs.delta.add(6, 'e', 8); - rhs.delta.add(8, 'f', 10); - rhs.delta.add(10, 'x', 12); - rhs.delta.add(12, 'y', 14); - rhs.delta.add(3, 'x', 5); - rhs.delta.add(5, 'z', 7); - rhs.delta.add(7, 'g', 9); - rhs.delta.add(9, 'h', 11); - rhs.delta.add(11, 'x', 13); - rhs.delta.add(13, 'y', 15); - - Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, true, true, JumpMode::NoJump); - - Nft expected; - expected.num_of_levels = 3; - expected.levels = { 0, 1, 2, 0, 1, 2, 0 }; - expected.initial.insert(0); - expected.final.insert(6); - expected.delta.add(0, 'u', 1); - expected.delta.add(0, 'v', 1); - expected.delta.add(1, 'a', 2); - expected.delta.add(1, 'b', 2); - expected.delta.add(2, 'c', 3); - expected.delta.add(2, 'd', 3); - expected.delta.add(3, 'u', 4); - expected.delta.add(3, 'v', 4); - expected.delta.add(4, 'e', 5); - expected.delta.add(5, 'f', 6); - - CHECK(result.num_of_levels == 3); - CHECK(are_equivalent(result, expected)); - } - - SECTION("epsilon on synchronization") { - Nft lhs; - lhs.num_of_levels = 3; - lhs.levels = { 0, 1, 2, 0 }; - lhs.initial.insert(0); - lhs.final.insert(3); - lhs.delta.add(0, 'u', 1); - lhs.delta.add(0, 'v', 1); - lhs.delta.add(1, 'x', 2); - lhs.delta.add(2, 'y', 3); - - Nft rhs; - rhs.num_of_levels = 3; - rhs.levels = { 0, 1, 2, 2, 0, 2, 0, 1, 1, 2, 2, 0, 0 }; - rhs.initial.insert(0); - rhs.final.insert(11); - rhs.final.insert(12); - rhs.delta.add(0, 'a', 1); - rhs.delta.add(0, 'b', 1); - rhs.delta.add(1, 'c', 2); - rhs.delta.add(1, EPSILON, 3); - rhs.delta.add(1, EPSILON, 5); - rhs.delta.add(2, EPSILON, 4); - rhs.delta.add(3, EPSILON, 4); - rhs.delta.add(4, 'e', 7); - rhs.delta.add(4, 'f', 7); - rhs.delta.add(7, 'x', 9); - rhs.delta.add(9, 'y', 11); - rhs.delta.add(5, 'd', 6); - rhs.delta.add(6, 'g', 8); - rhs.delta.add(8, 'x', 10); - rhs.delta.add(10, 'y', 12); - - Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, true, true, JumpMode::NoJump); - - Nft expected; - expected.num_of_levels = 2; - expected.levels = { 0, 1, 0, 1, 0 }; - expected.initial.insert(0); - expected.final.insert(4); - expected.delta.add(0, EPSILON, 1); - expected.delta.add(1, 'a', 2); - expected.delta.add(1, 'b', 2); - expected.delta.add(2, 'u', 3); - expected.delta.add(2, 'v', 3); - expected.delta.add(3, 'e', 4); - expected.delta.add(3, 'f', 4); - - CHECK(result.num_of_levels == 2); - CHECK(are_equivalent(result, expected)); - } - } - - SECTION("project_out == false") { - SECTION("two levels easy match") { - Nft lhs; - lhs.num_of_levels = 2; - lhs.levels = { 0, 1, 0, 1, 0 }; - lhs.initial.insert(0); - lhs.final.insert(4); - lhs.delta.add(0, 'x', 1); - lhs.delta.add(1, 'a', 2); - lhs.delta.add(1, 'b', 2); - lhs.delta.add(2, 'y', 3); - lhs.delta.add(3, 'c', 4); - lhs.delta.add(3, 'd', 4); - - Nft rhs; - rhs.num_of_levels = 2; - rhs.levels = { 0, 1, 0, 1, 0 }; - rhs.initial.insert(0); - rhs.final.insert(4); - rhs.delta.add(0, 'x', 1); - rhs.delta.add(1, 'e', 2); - rhs.delta.add(1, 'f', 2); - rhs.delta.add(2, 'y', 3); - rhs.delta.add(3, 'g', 4); - rhs.delta.add(3, 'h', 4); - - Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, false, true, JumpMode::NoJump); - - Nft expected; - expected.num_of_levels = 3; - expected.levels = { 0, 2, 0, 2, 0, 1, 1 }; - expected.initial.insert(0); - expected.final.insert(4); - expected.delta.add(0, 'x', 5); - expected.delta.add(5, 'a', 1); - expected.delta.add(5, 'b', 1); - expected.delta.add(1, 'e', 2); - expected.delta.add(1, 'f', 2); - expected.delta.add(2, 'y', 6); - expected.delta.add(6, 'c', 3); - expected.delta.add(6, 'd', 3); - expected.delta.add(3, 'g', 4); - expected.delta.add(3, 'h', 4); - - CHECK(result.num_of_levels == 3); - CHECK(are_equivalent(result, expected)); - } - - - SECTION("four levels with loop") { - Nft lhs; - lhs.num_of_levels = 4; - lhs.levels = { 0, 1, 2, 3, 0 }; - lhs.initial.insert(0); - lhs.final.insert(4); - lhs.delta.add(0, 'x', 1); - lhs.delta.add(1, 'a', 2); - lhs.delta.add(1, 'b', 2); - lhs.delta.add(2, 'y', 3); - lhs.delta.add(3, 'c', 4); - lhs.delta.add(3, 'd', 4); - lhs.delta.add(3, 'e', 0); - lhs.delta.add(3, 'f', 0); - lhs.print_to_dot(std::string("lhs.dot"), true, true); - - Nft rhs; - rhs.num_of_levels = 4; - rhs.levels = { 0, 1, 2, 3, 0 }; - rhs.initial.insert(0); - rhs.final.insert(4); - rhs.delta.add(0, 'g', 1); - rhs.delta.add(0, 'h', 1); - rhs.delta.add(1, 'x', 2); - rhs.delta.add(2, 'i', 3); - rhs.delta.add(2, 'j', 3); - rhs.delta.add(3, 'y', 4); - rhs.delta.add(3, 'y', 0); - rhs.print_to_dot(std::string("rhs.dot"), true, true); - - Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, false, true, JumpMode::NoJump); - result.print_to_dot(std::string("result.dot"), true, true); - - Nft expected; - expected.num_of_levels = 6; - expected.levels = { 0, 2, 3, 4, 0, 1, 5 }; - expected.initial.insert(0); - expected.final.insert(4); - expected.delta.add(0, 'g', 5); - expected.delta.add(0, 'h', 5); - expected.delta.add(5, 'x', 1); - expected.delta.add(1, 'a', 2); - expected.delta.add(1, 'b', 2); - expected.delta.add(2, 'i', 3); - expected.delta.add(2, 'j', 3); - expected.delta.add(3, 'y', 6); - expected.delta.add(6, 'c', 4); - expected.delta.add(6, 'd', 4); - expected.delta.add(6, 'e', 0); - expected.delta.add(6, 'f', 0); - expected.print_to_dot(std::string("expected.dot"), true, true); - - CHECK(result.num_of_levels == 6); - CHECK(are_equivalent(result, expected)); - } - - SECTION("synchronization of lst two levels with one mismatch") { - Nft lhs; - lhs.num_of_levels = 3; - lhs.levels = { 0, 1, 2 }; - lhs.initial.insert(0); - lhs.final.insert(0); - lhs.delta.add(0, 'u', 1); - lhs.delta.add(0, 'v', 1); - lhs.delta.add(1, 'x', 2); - lhs.delta.add(2, 'y', 0); - - Nft rhs; - rhs.num_of_levels = 4; - rhs.levels = { 0, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0}; - rhs.initial.insert(0); - rhs.final.insert(7); - rhs.final.insert(14); - rhs.final.insert(15); - rhs.delta.add(0, 'a', 1); - rhs.delta.add(0, 'b', 1); - rhs.delta.add(1, 'c', 2); - rhs.delta.add(1, 'd', 2); - rhs.delta.add(1, 'c', 3); - rhs.delta.add(1, 'd', 3); - rhs.delta.add(2, 'x', 4); - rhs.delta.add(4, 'y', 6); - rhs.delta.add(6, 'e', 8); - rhs.delta.add(8, 'f', 10); - rhs.delta.add(10, 'x', 12); - rhs.delta.add(12, 'y', 14); - rhs.delta.add(3, 'x', 5); - rhs.delta.add(5, 'z', 7); - rhs.delta.add(7, 'g', 9); - rhs.delta.add(9, 'h', 11); - rhs.delta.add(11, 'x', 13); - rhs.delta.add(13, 'y', 15); - - Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, false, true, JumpMode::NoJump); - - Nft expected; - expected.num_of_levels = 5; - expected.levels = { 0, 1, 2, 3, 1, 2, 3, 4, 0, 4, 0 }; - expected.initial.insert(0); - expected.final.insert(10); - expected.delta.add(0, 'u', 1); - expected.delta.add(0, 'v', 1); - expected.delta.add(1, 'a', 2); - expected.delta.add(1, 'b', 2); - expected.delta.add(2, 'c', 3); - expected.delta.add(2, 'd', 3); - expected.delta.add(3, 'x', 7); - expected.delta.add(7, 'y', 8); - expected.delta.add(8, 'u', 4); - expected.delta.add(8, 'v', 4); - expected.delta.add(4, 'e', 5); - expected.delta.add(5, 'f', 6); - expected.delta.add(6, 'x', 9); - expected.delta.add(9, 'y', 10); - - CHECK(result.num_of_levels == 5); - CHECK(are_equivalent(result, expected)); - } - - SECTION("epsilon on synchronization") { - Nft lhs; - lhs.num_of_levels = 3; - lhs.levels = { 0, 1, 2, 0 }; - lhs.initial.insert(0); - lhs.final.insert(3); - lhs.delta.add(0, 'u', 1); - lhs.delta.add(0, 'v', 1); - lhs.delta.add(1, 'x', 2); - lhs.delta.add(2, 'y', 3); - - Nft rhs; - rhs.num_of_levels = 3; - rhs.levels = { 0, 1, 2, 2, 0, 2, 0, 1, 1, 2, 2, 0, 0 }; - rhs.initial.insert(0); - rhs.final.insert(11); - rhs.final.insert(12); - rhs.delta.add(0, 'a', 1); - rhs.delta.add(0, 'b', 1); - rhs.delta.add(1, 'c', 2); - rhs.delta.add(1, EPSILON, 3); - rhs.delta.add(1, EPSILON, 5); - rhs.delta.add(2, EPSILON, 4); - rhs.delta.add(3, EPSILON, 4); - rhs.delta.add(4, 'e', 7); - rhs.delta.add(4, 'f', 7); - rhs.delta.add(7, 'x', 9); - rhs.delta.add(9, 'y', 11); - rhs.delta.add(5, 'd', 6); - rhs.delta.add(6, 'g', 8); - rhs.delta.add(8, 'x', 10); - rhs.delta.add(10, 'y', 12); - - Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, false, true, JumpMode::NoJump); - - Nft expected; - expected.num_of_levels = 4; - expected.levels = { 0, 1, 2, 1, 2, 3, 0, 3, 0 }; - expected.initial.insert(0); - expected.final.insert(8); - expected.delta.add(0, EPSILON, 1); - expected.delta.add(1, 'a', 2); - expected.delta.add(1, 'b', 2); - expected.delta.add(2, EPSILON, 5); - expected.delta.add(5, EPSILON, 6); - expected.delta.add(6, 'u', 3); - expected.delta.add(6, 'v', 3); - expected.delta.add(3, 'e', 4); - expected.delta.add(3, 'f', 4); - expected.delta.add(4, 'x', 7); - expected.delta.add(7, 'y', 8); - - CHECK(result.num_of_levels == 4); - CHECK(are_equivalent(result, expected)); - } - } -} \ No newline at end of file +// TEST_CASE("nft::compose_fast()") { +// SECTION("project_out == true") { +// SECTION("two levels easy match") { +// Nft lhs; +// lhs.num_of_levels = 2; +// lhs.levels = { 0, 1, 0, 1, 0 }; +// lhs.initial.insert(0); +// lhs.final.insert(4); +// lhs.delta.add(0, 'x', 1); +// lhs.delta.add(1, 'a', 2); +// lhs.delta.add(1, 'b', 2); +// lhs.delta.add(2, 'y', 3); +// lhs.delta.add(3, 'c', 4); +// lhs.delta.add(3, 'd', 4); + +// Nft rhs; +// rhs.num_of_levels = 2; +// rhs.levels = { 0, 1, 0, 1, 0 }; +// rhs.initial.insert(0); +// rhs.final.insert(4); +// rhs.delta.add(0, 'x', 1); +// rhs.delta.add(1, 'e', 2); +// rhs.delta.add(1, 'f', 2); +// rhs.delta.add(2, 'y', 3); +// rhs.delta.add(3, 'g', 4); +// rhs.delta.add(3, 'h', 4); + +// Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, true, true, JumpMode::NoJump); + +// Nft expected; +// expected.num_of_levels = 2; +// expected.levels = { 0, 1, 0, 1, 0 }; +// expected.initial.insert(0); +// expected.final.insert(4); +// expected.delta.add(0, 'a', 1); +// expected.delta.add(0, 'b', 1); +// expected.delta.add(1, 'e', 2); +// expected.delta.add(1, 'f', 2); +// expected.delta.add(2, 'c', 3); +// expected.delta.add(2, 'd', 3); +// expected.delta.add(3, 'g', 4); +// expected.delta.add(3, 'h', 4); + +// CHECK(result.num_of_levels == 2); +// CHECK(are_equivalent(result, expected)); +// } + +// SECTION("four levels with loop") { +// Nft lhs; +// lhs.num_of_levels = 4; +// lhs.levels = { 0, 1, 2, 3, 0 }; +// lhs.initial.insert(0); +// lhs.final.insert(4); +// lhs.delta.add(0, 'x', 1); +// lhs.delta.add(1, 'a', 2); +// lhs.delta.add(1, 'b', 2); +// lhs.delta.add(2, 'y', 3); +// lhs.delta.add(3, 'c', 4); +// lhs.delta.add(3, 'd', 4); +// lhs.delta.add(3, 'e', 0); +// lhs.delta.add(3, 'f', 0); + +// Nft rhs; +// rhs.num_of_levels = 4; +// rhs.levels = { 0, 1, 2, 3, 0 }; +// rhs.initial.insert(0); +// rhs.final.insert(4); +// rhs.delta.add(0, 'g', 1); +// rhs.delta.add(0, 'h', 1); +// rhs.delta.add(1, 'x', 2); +// rhs.delta.add(2, 'i', 3); +// rhs.delta.add(2, 'j', 3); +// rhs.delta.add(3, 'y', 4); +// rhs.delta.add(3, 'y', 0); + +// Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, true, true, JumpMode::NoJump); + +// Nft expected; +// expected.num_of_levels = 4; +// expected.levels = { 0, 1, 2, 3, 0 }; +// expected.initial.insert(0); +// expected.final.insert(4); +// expected.delta.add(0, 'g', 1); +// expected.delta.add(0, 'h', 1); +// expected.delta.add(1, 'a', 2); +// expected.delta.add(1, 'b', 2); +// expected.delta.add(2, 'i', 3); +// expected.delta.add(2, 'j', 3); +// expected.delta.add(3, 'c', 4); +// expected.delta.add(3, 'd', 4); +// expected.delta.add(3, 'e', 0); +// expected.delta.add(3, 'f', 0); + +// CHECK(result.num_of_levels == 4); +// CHECK(are_equivalent(result, expected)); +// } + +// SECTION("synchronization of lst two levels with one mismatch") { +// Nft lhs; +// lhs.num_of_levels = 3; +// lhs.levels = { 0, 1, 2 }; +// lhs.initial.insert(0); +// lhs.final.insert(0); +// lhs.delta.add(0, 'u', 1); +// lhs.delta.add(0, 'v', 1); +// lhs.delta.add(1, 'x', 2); +// lhs.delta.add(2, 'y', 0); + +// Nft rhs; +// rhs.num_of_levels = 4; +// rhs.levels = { 0, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0}; +// rhs.initial.insert(0); +// rhs.final.insert(7); +// rhs.final.insert(14); +// rhs.final.insert(15); +// rhs.delta.add(0, 'a', 1); +// rhs.delta.add(0, 'b', 1); +// rhs.delta.add(1, 'c', 2); +// rhs.delta.add(1, 'd', 2); +// rhs.delta.add(1, 'c', 3); +// rhs.delta.add(1, 'd', 3); +// rhs.delta.add(2, 'x', 4); +// rhs.delta.add(4, 'y', 6); +// rhs.delta.add(6, 'e', 8); +// rhs.delta.add(8, 'f', 10); +// rhs.delta.add(10, 'x', 12); +// rhs.delta.add(12, 'y', 14); +// rhs.delta.add(3, 'x', 5); +// rhs.delta.add(5, 'z', 7); +// rhs.delta.add(7, 'g', 9); +// rhs.delta.add(9, 'h', 11); +// rhs.delta.add(11, 'x', 13); +// rhs.delta.add(13, 'y', 15); + +// Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, true, true, JumpMode::NoJump); + +// Nft expected; +// expected.num_of_levels = 3; +// expected.levels = { 0, 1, 2, 0, 1, 2, 0 }; +// expected.initial.insert(0); +// expected.final.insert(6); +// expected.delta.add(0, 'u', 1); +// expected.delta.add(0, 'v', 1); +// expected.delta.add(1, 'a', 2); +// expected.delta.add(1, 'b', 2); +// expected.delta.add(2, 'c', 3); +// expected.delta.add(2, 'd', 3); +// expected.delta.add(3, 'u', 4); +// expected.delta.add(3, 'v', 4); +// expected.delta.add(4, 'e', 5); +// expected.delta.add(5, 'f', 6); + +// CHECK(result.num_of_levels == 3); +// CHECK(are_equivalent(result, expected)); +// } + +// SECTION("epsilon on synchronization") { +// Nft lhs; +// lhs.num_of_levels = 3; +// lhs.levels = { 0, 1, 2, 0 }; +// lhs.initial.insert(0); +// lhs.final.insert(3); +// lhs.delta.add(0, 'u', 1); +// lhs.delta.add(0, 'v', 1); +// lhs.delta.add(1, 'x', 2); +// lhs.delta.add(2, 'y', 3); + +// Nft rhs; +// rhs.num_of_levels = 3; +// rhs.levels = { 0, 1, 2, 2, 0, 2, 0, 1, 1, 2, 2, 0, 0 }; +// rhs.initial.insert(0); +// rhs.final.insert(11); +// rhs.final.insert(12); +// rhs.delta.add(0, 'a', 1); +// rhs.delta.add(0, 'b', 1); +// rhs.delta.add(1, 'c', 2); +// rhs.delta.add(1, EPSILON, 3); +// rhs.delta.add(1, EPSILON, 5); +// rhs.delta.add(2, EPSILON, 4); +// rhs.delta.add(3, EPSILON, 4); +// rhs.delta.add(4, 'e', 7); +// rhs.delta.add(4, 'f', 7); +// rhs.delta.add(7, 'x', 9); +// rhs.delta.add(9, 'y', 11); +// rhs.delta.add(5, 'd', 6); +// rhs.delta.add(6, 'g', 8); +// rhs.delta.add(8, 'x', 10); +// rhs.delta.add(10, 'y', 12); + +// Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, true, true, JumpMode::NoJump); + +// Nft expected; +// expected.num_of_levels = 2; +// expected.levels = { 0, 1, 0, 1, 0 }; +// expected.initial.insert(0); +// expected.final.insert(4); +// expected.delta.add(0, EPSILON, 1); +// expected.delta.add(1, 'a', 2); +// expected.delta.add(1, 'b', 2); +// expected.delta.add(2, 'u', 3); +// expected.delta.add(2, 'v', 3); +// expected.delta.add(3, 'e', 4); +// expected.delta.add(3, 'f', 4); + +// CHECK(result.num_of_levels == 2); +// CHECK(are_equivalent(result, expected)); +// } +// } + +// SECTION("project_out == false") { +// SECTION("two levels easy match") { +// Nft lhs; +// lhs.num_of_levels = 2; +// lhs.levels = { 0, 1, 0, 1, 0 }; +// lhs.initial.insert(0); +// lhs.final.insert(4); +// lhs.delta.add(0, 'x', 1); +// lhs.delta.add(1, 'a', 2); +// lhs.delta.add(1, 'b', 2); +// lhs.delta.add(2, 'y', 3); +// lhs.delta.add(3, 'c', 4); +// lhs.delta.add(3, 'd', 4); + +// Nft rhs; +// rhs.num_of_levels = 2; +// rhs.levels = { 0, 1, 0, 1, 0 }; +// rhs.initial.insert(0); +// rhs.final.insert(4); +// rhs.delta.add(0, 'x', 1); +// rhs.delta.add(1, 'e', 2); +// rhs.delta.add(1, 'f', 2); +// rhs.delta.add(2, 'y', 3); +// rhs.delta.add(3, 'g', 4); +// rhs.delta.add(3, 'h', 4); + +// Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, false, true, JumpMode::NoJump); + +// Nft expected; +// expected.num_of_levels = 3; +// expected.levels = { 0, 2, 0, 2, 0, 1, 1 }; +// expected.initial.insert(0); +// expected.final.insert(4); +// expected.delta.add(0, 'x', 5); +// expected.delta.add(5, 'a', 1); +// expected.delta.add(5, 'b', 1); +// expected.delta.add(1, 'e', 2); +// expected.delta.add(1, 'f', 2); +// expected.delta.add(2, 'y', 6); +// expected.delta.add(6, 'c', 3); +// expected.delta.add(6, 'd', 3); +// expected.delta.add(3, 'g', 4); +// expected.delta.add(3, 'h', 4); + +// CHECK(result.num_of_levels == 3); +// CHECK(are_equivalent(result, expected)); +// } + + +// SECTION("four levels with loop") { +// Nft lhs; +// lhs.num_of_levels = 4; +// lhs.levels = { 0, 1, 2, 3, 0 }; +// lhs.initial.insert(0); +// lhs.final.insert(4); +// lhs.delta.add(0, 'x', 1); +// lhs.delta.add(1, 'a', 2); +// lhs.delta.add(1, 'b', 2); +// lhs.delta.add(2, 'y', 3); +// lhs.delta.add(3, 'c', 4); +// lhs.delta.add(3, 'd', 4); +// lhs.delta.add(3, 'e', 0); +// lhs.delta.add(3, 'f', 0); +// lhs.print_to_dot(std::string("lhs.dot"), true, true); + +// Nft rhs; +// rhs.num_of_levels = 4; +// rhs.levels = { 0, 1, 2, 3, 0 }; +// rhs.initial.insert(0); +// rhs.final.insert(4); +// rhs.delta.add(0, 'g', 1); +// rhs.delta.add(0, 'h', 1); +// rhs.delta.add(1, 'x', 2); +// rhs.delta.add(2, 'i', 3); +// rhs.delta.add(2, 'j', 3); +// rhs.delta.add(3, 'y', 4); +// rhs.delta.add(3, 'y', 0); +// rhs.print_to_dot(std::string("rhs.dot"), true, true); + +// Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, false, true, JumpMode::NoJump); +// result.print_to_dot(std::string("result.dot"), true, true); + +// Nft expected; +// expected.num_of_levels = 6; +// expected.levels = { 0, 2, 3, 4, 0, 1, 5 }; +// expected.initial.insert(0); +// expected.final.insert(4); +// expected.delta.add(0, 'g', 5); +// expected.delta.add(0, 'h', 5); +// expected.delta.add(5, 'x', 1); +// expected.delta.add(1, 'a', 2); +// expected.delta.add(1, 'b', 2); +// expected.delta.add(2, 'i', 3); +// expected.delta.add(2, 'j', 3); +// expected.delta.add(3, 'y', 6); +// expected.delta.add(6, 'c', 4); +// expected.delta.add(6, 'd', 4); +// expected.delta.add(6, 'e', 0); +// expected.delta.add(6, 'f', 0); +// expected.print_to_dot(std::string("expected.dot"), true, true); + +// CHECK(result.num_of_levels == 6); +// CHECK(are_equivalent(result, expected)); +// } + +// SECTION("synchronization of lst two levels with one mismatch") { +// Nft lhs; +// lhs.num_of_levels = 3; +// lhs.levels = { 0, 1, 2 }; +// lhs.initial.insert(0); +// lhs.final.insert(0); +// lhs.delta.add(0, 'u', 1); +// lhs.delta.add(0, 'v', 1); +// lhs.delta.add(1, 'x', 2); +// lhs.delta.add(2, 'y', 0); + +// Nft rhs; +// rhs.num_of_levels = 4; +// rhs.levels = { 0, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0}; +// rhs.initial.insert(0); +// rhs.final.insert(7); +// rhs.final.insert(14); +// rhs.final.insert(15); +// rhs.delta.add(0, 'a', 1); +// rhs.delta.add(0, 'b', 1); +// rhs.delta.add(1, 'c', 2); +// rhs.delta.add(1, 'd', 2); +// rhs.delta.add(1, 'c', 3); +// rhs.delta.add(1, 'd', 3); +// rhs.delta.add(2, 'x', 4); +// rhs.delta.add(4, 'y', 6); +// rhs.delta.add(6, 'e', 8); +// rhs.delta.add(8, 'f', 10); +// rhs.delta.add(10, 'x', 12); +// rhs.delta.add(12, 'y', 14); +// rhs.delta.add(3, 'x', 5); +// rhs.delta.add(5, 'z', 7); +// rhs.delta.add(7, 'g', 9); +// rhs.delta.add(9, 'h', 11); +// rhs.delta.add(11, 'x', 13); +// rhs.delta.add(13, 'y', 15); + +// Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, false, true, JumpMode::NoJump); + +// Nft expected; +// expected.num_of_levels = 5; +// expected.levels = { 0, 1, 2, 3, 1, 2, 3, 4, 0, 4, 0 }; +// expected.initial.insert(0); +// expected.final.insert(10); +// expected.delta.add(0, 'u', 1); +// expected.delta.add(0, 'v', 1); +// expected.delta.add(1, 'a', 2); +// expected.delta.add(1, 'b', 2); +// expected.delta.add(2, 'c', 3); +// expected.delta.add(2, 'd', 3); +// expected.delta.add(3, 'x', 7); +// expected.delta.add(7, 'y', 8); +// expected.delta.add(8, 'u', 4); +// expected.delta.add(8, 'v', 4); +// expected.delta.add(4, 'e', 5); +// expected.delta.add(5, 'f', 6); +// expected.delta.add(6, 'x', 9); +// expected.delta.add(9, 'y', 10); + +// CHECK(result.num_of_levels == 5); +// CHECK(are_equivalent(result, expected)); +// } + +// SECTION("epsilon on synchronization") { +// Nft lhs; +// lhs.num_of_levels = 3; +// lhs.levels = { 0, 1, 2, 0 }; +// lhs.initial.insert(0); +// lhs.final.insert(3); +// lhs.delta.add(0, 'u', 1); +// lhs.delta.add(0, 'v', 1); +// lhs.delta.add(1, 'x', 2); +// lhs.delta.add(2, 'y', 3); + +// Nft rhs; +// rhs.num_of_levels = 3; +// rhs.levels = { 0, 1, 2, 2, 0, 2, 0, 1, 1, 2, 2, 0, 0 }; +// rhs.initial.insert(0); +// rhs.final.insert(11); +// rhs.final.insert(12); +// rhs.delta.add(0, 'a', 1); +// rhs.delta.add(0, 'b', 1); +// rhs.delta.add(1, 'c', 2); +// rhs.delta.add(1, EPSILON, 3); +// rhs.delta.add(1, EPSILON, 5); +// rhs.delta.add(2, EPSILON, 4); +// rhs.delta.add(3, EPSILON, 4); +// rhs.delta.add(4, 'e', 7); +// rhs.delta.add(4, 'f', 7); +// rhs.delta.add(7, 'x', 9); +// rhs.delta.add(9, 'y', 11); +// rhs.delta.add(5, 'd', 6); +// rhs.delta.add(6, 'g', 8); +// rhs.delta.add(8, 'x', 10); +// rhs.delta.add(10, 'y', 12); + +// Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, false, true, JumpMode::NoJump); + +// Nft expected; +// expected.num_of_levels = 4; +// expected.levels = { 0, 1, 2, 1, 2, 3, 0, 3, 0 }; +// expected.initial.insert(0); +// expected.final.insert(8); +// expected.delta.add(0, EPSILON, 1); +// expected.delta.add(1, 'a', 2); +// expected.delta.add(1, 'b', 2); +// expected.delta.add(2, EPSILON, 5); +// expected.delta.add(5, EPSILON, 6); +// expected.delta.add(6, 'u', 3); +// expected.delta.add(6, 'v', 3); +// expected.delta.add(3, 'e', 4); +// expected.delta.add(3, 'f', 4); +// expected.delta.add(4, 'x', 7); +// expected.delta.add(7, 'y', 8); + +// CHECK(result.num_of_levels == 4); +// CHECK(are_equivalent(result, expected)); +// } +// } +// } \ No newline at end of file From 11f4d0a63028de6e4c921035491c1f638c7a0f0c Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 21 Aug 2025 11:57:11 +0200 Subject: [PATCH 38/65] old implementation is working --- include/mata/nft/nft.hh | 4 +- src/nft/composition.cc | 9 ++++- src/strings/nfa-noodlification.cc | 1 + tests/nft/nft-composition.cc | 65 ++++++++++++++++++------------- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index 668e5f6ad..7d5888ab4 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -794,7 +794,7 @@ Nft intersection(const Nft& lhs, const Nft& rhs, Nft compose(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, bool project_out_sync_levels = true, - JumpMode jump_mode = JumpMode::NoJump); + JumpMode jump_mode = JumpMode::RepeatSymbol); /** * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs"). @@ -813,7 +813,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, * is interpreted as a sequence repeating the same symbol or as a single instance of the symbol followed by a sequence of @c DONT_CARE. * @return A new NFT after the composition. */ -Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::NoJump); +Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::RepeatSymbol); /** * @brief Concatenate two NFTs. diff --git a/src/nft/composition.cc b/src/nft/composition.cc index d25b6b59d..df1e55186 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -161,7 +161,9 @@ namespace mata::nft Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, const bool project_out_sync_levels, const JumpMode jump_mode) { assert(lhs_sync_level < lhs.num_of_levels && rhs_sync_level < rhs.num_of_levels); - return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels, jump_mode); + + // Just for testing. + return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels); throw std::invalid_argument("Synchronization levels must be less than the number of levels in the NFTs."); // Number of Levels and States @@ -707,12 +709,15 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } } - return result.trim(); // Trim the result NFT to remove dead-end paths. + // Cannot do the trim to remove dead ends, + // because some algorithms work with useless states. + return result; } Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { assert(!lhs_sync_levels.empty()); assert(lhs_sync_levels.size() == rhs_sync_levels.size()); + assert(jump_mode != JumpMode::NoJump); // if (lhs_sync_levels.size() == 1 && rhs_sync_levels.size() == 1) { // // If we have only one synchronization level we can do it faster. diff --git a/src/strings/nfa-noodlification.cc b/src/strings/nfa-noodlification.cc index fe0bdf7ee..95b3551be 100644 --- a/src/strings/nfa-noodlification.cc +++ b/src/strings/nfa-noodlification.cc @@ -461,6 +461,7 @@ std::vector seg_nfa::noodlify_for_transducer( add_self_loop_for_every_default_state(concatenated_output_nft, INPUT_DELIMITER); add_self_loop_for_every_default_state(intersection, OUTPUT_DELIMITER); intersection = mata::nft::compose(concatenated_output_nft, intersection, 0, 1, false); + intersection.trim(); if(intersection.final.empty()) { return {}; diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 9745065af..0d98db929 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -49,36 +49,42 @@ TEST_CASE("Mata::nft::compose()") { CHECK(are_equivalent(result, expected)); } - SECTION("Epsilon perfectly matches.") { - lhs = Nft(7, { 0 }, { 6 }, { 0, 1, 0, 1, 0, 1, 0 }, 2); - lhs.delta.add(0, 'e', 1); - lhs.delta.add(1, 'a', 2); - lhs.delta.add(2, 'g', 3); + SECTION("Epsilon perfectly matches") { + lhs = Nft(9, { 0 }, { 8 }, { 0, 1, 2, 3, 0, 1, 2, 3, 0 }, 4); + lhs.delta.add(0, 'i', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); lhs.delta.add(3, EPSILON, 4); - lhs.delta.add(4, EPSILON, 5); - lhs.delta.add(5, 'c', 6); + lhs.delta.add(4, 'k', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(6, 'g', 7); + lhs.delta.add(7, 'l', 8); - rhs = Nft(7, { 0 }, { 6 }, { 0, 1, 0, 1, 0, 1, 0 }, 2); + rhs = Nft(9, { 0 }, { 8 }, { 0, 1, 2, 3, 0, 1, 2, 3, 0 }, 4); rhs.delta.add(0, 'a', 1); - rhs.delta.add(1, EPSILON, 2); + rhs.delta.add(1, 'i', 2); rhs.delta.add(2, EPSILON, 3); - rhs.delta.add(3, 'h', 4); - rhs.delta.add(4, 'c', 5); - rhs.delta.add(5, 'j', 6); - - expected = Nft(7, { 0 }, { 6 }, { 0, 1, 0, 1, 0, 1, 0 }, 2); - expected.delta.add(0, 'e', 1); - expected.delta.add(1, EPSILON, 2); - expected.delta.add(2, 'g', 3); - expected.delta.add(3, 'h', 4); - expected.delta.add(4, EPSILON, 5); - expected.delta.add(5, 'j', 6); - - - result = compose(lhs, rhs, 1, 0); + rhs.delta.add(3, EPSILON, 4); + rhs.delta.add(4, 'e', 5); + rhs.delta.add(5, 'k', 6); + rhs.delta.add(6, 'l', 7); + rhs.delta.add(7, 'h', 8); + + expected= Nft(9, { 0 }, { 8 }, { 0, 1, 2, 3, 0, 1, 2, 3, 0 }, 4); + expected.delta.add(0, 'a', 1); + expected.delta.add(1, 'b', 2); + expected.delta.add(2, 'c', 3); + expected.delta.add(3, EPSILON, 4); + expected.delta.add(4, 'e', 5); + expected.delta.add(5, 'f', 6); + expected.delta.add(6, 'g', 7); + expected.delta.add(7, 'h', 8); + + result = compose(lhs, rhs, { 0, 3 }, { 1, 2 }); CHECK(are_equivalent(result, expected)); } + } SECTION("Branching") { @@ -222,27 +228,32 @@ TEST_CASE("Mata::nft::compose()") { lhs.delta.add(3, 'a', 4); lhs.delta.add(4, 'x', 5); lhs.delta.add(5, EPSILON, 6); - rhs = Nft(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); rhs.delta.add(0, 'a', 1); rhs.delta.add(1, 'b', 2); rhs.delta.add(2, EPSILON, 3); rhs.delta.add(3, 'd', 4); - expected = Nft(7, { 0 }, { 6 }, { 0, 1, 0, 1, 0, 1, 0 }, 2); + expected = Nft(13, { 0 }, { 6 }, { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1 }, 2); expected.delta.add(0, 'x', 1); expected.delta.add(1, EPSILON, 2); expected.delta.add(2, EPSILON, 3); expected.delta.add(3, 'b', 4); expected.delta.add(4, 'x', 5); + expected.delta.add(4, EPSILON, 10); + expected.delta.add(10, 'd', 11); + expected.delta.add(11, 'x', 12); + expected.delta.add(12, EPSILON, 6); + expected.delta.add(4, 'x', 7); + expected.delta.add(7, EPSILON, 8); + expected.delta.add(8, EPSILON, 9); + expected.delta.add(9, 'd', 6); expected.delta.add(5, 'd', 6); - expected.print_to_dot(std::string("expected.dot"), true); result = compose(lhs, rhs); CHECK(are_equivalent(result, expected)); } - } } From e98278a030fd32431b60f212bb41dc9fae7062fd Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 21 Aug 2025 12:47:57 +0200 Subject: [PATCH 39/65] update algorithms to work with JumpMode::NoJump --- include/mata/nft/nft.hh | 4 ++-- src/nft/intersection.cc | 4 ++-- src/nft/operations.cc | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index 7d5888ab4..d38aee67e 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -660,7 +660,7 @@ public: */ Nft apply( const nfa::Nfa& nfa, Level level_to_apply_on = 0, bool project_out_applied_level = true, - JumpMode jump_mode = JumpMode::NoJump) const; + JumpMode jump_mode = JumpMode::RepeatSymbol) const; /** * @brief Apply @p word to @c this. @@ -678,7 +678,7 @@ public: */ Nft apply( const Word& word, Level level_to_apply_on = 0, bool project_out_applied_level = true, - JumpMode jump_mode = JumpMode::NoJump) const; + JumpMode jump_mode = JumpMode::RepeatSymbol) const; /** * @brief Copy NFT as NFA. diff --git a/src/nft/intersection.cc b/src/nft/intersection.cc index c6c2bbf87..fb18bfeb7 100644 --- a/src/nft/intersection.cc +++ b/src/nft/intersection.cc @@ -56,7 +56,7 @@ Nft mata::nft::algorithms::product(const Nft& lhs, const Nft& rhs, const std::fu if (product_target == Limits::max_state) { - product_target = product.add_state_with_level((jump_mode == JumpMode::RepeatSymbol || lhs.levels[lhs_target] == 0 || rhs.levels[rhs_target] == 0) ? + product_target = product.add_state_with_level((jump_mode != JumpMode::AppendDontCares || lhs.levels[lhs_target] == 0 || rhs.levels[rhs_target] == 0) ? std::max(lhs.levels[lhs_target], rhs.levels[rhs_target]) : std::min(lhs.levels[lhs_target], rhs.levels[rhs_target])); assert(product_target < Limits::max_state); @@ -161,7 +161,7 @@ Nft mata::nft::algorithms::product(const Nft& lhs, const Nft& rhs, const std::fu const bool sources_are_on_the_same_level = lhs_source_level == rhs_source_level; const bool rhs_source_is_deeper = (lhs_source_level < rhs_source_level && lhs_source_level != 0) || (lhs_source_level != 0 && rhs_source_level == 0); - if (sources_are_on_the_same_level || jump_mode == JumpMode::RepeatSymbol) { + if (sources_are_on_the_same_level || jump_mode != JumpMode::AppendDontCares) { // Compute classic product for current state pair. mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); mata::utils::push_back(sync_iterator, lhs.delta[lhs_source]); diff --git a/src/nft/operations.cc b/src/nft/operations.cc index b0cb6085b..0c383b8b0 100644 --- a/src/nft/operations.cc +++ b/src/nft/operations.cc @@ -367,7 +367,7 @@ Nft mata::nft::project_out(const Nft& nft, const utils::OrdVector& levels if (is_projected_out(cls_state)) { // If there are remaining levels between cls_state and tgt_state // on a transition with a length greater than 1, then these levels must be preserved. - if (jump_mode == JumpMode::RepeatSymbol) { + if (jump_mode != JumpMode::AppendDontCares) { result.delta.add(src_state, move.symbol, tgt_state); } else { result.delta.add(src_state, DONT_CARE, tgt_state); @@ -467,6 +467,7 @@ Nft mata::nft::insert_levels(const Nft& nft, const BoolVector& new_levels_mask, // Example: // Input (new_levels_mask): 0 1 1 0 1 0 1 1 0 1 1 1 // Output (JumpMode::RepeatSymbol): 1 3 3 4 5 6 8 8 9 12 12 12 + // Output (JumpMode::NoJump): 1 3 3 4 5 6 8 8 9 12 12 12 // Output (JumpMode::AppendDontCares): 3 3 3 5 5 8 8 8 12 12 12 12 const size_t mask_size = new_levels_mask.size(); std::vector next_inner_levels(mask_size); @@ -475,7 +476,7 @@ Nft mata::nft::insert_levels(const Nft& nft, const BoolVector& new_levels_mask, for (auto it = new_levels_mask.rbegin(); it != new_levels_mask.rend(); ++it, --i) { next_inner_levels[i] = next_level; if (!*it) { - if (jump_mode == JumpMode::RepeatSymbol) { + if (jump_mode != JumpMode::AppendDontCares) { next_inner_levels[i] = static_cast(i+1); } next_level = static_cast(i); @@ -489,7 +490,7 @@ Nft mata::nft::insert_levels(const Nft& nft, const BoolVector& new_levels_mask, // The transition symbol is determined based on the parameters: // it could be a specific symb, DONT_CARE, or a default symbol. auto create_transition = [&](const State src, const Symbol symb, const State tgt, const bool is_inserted_level, const bool is_old_level_processed) { - if (!is_inserted_level && (jump_mode == JumpMode::RepeatSymbol || !is_old_level_processed)) { + if (!is_inserted_level && (jump_mode != JumpMode::AppendDontCares || !is_old_level_processed)) { result.delta.add(src, symb, tgt); } else { result.delta.add(src, DONT_CARE, tgt); From 0c19a66d94bb380a0a7463360a6d4520442e94a5 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Tue, 2 Sep 2025 13:17:35 +0200 Subject: [PATCH 40/65] add semantic of NFA EPSILON transitions --- src/nft/composition.cc | 119 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 3 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index df1e55186..0b6fe8d85 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -111,6 +111,12 @@ namespace { current_sync_type = SynchronizationType::UNDEFINED; for (const SymbolPost& symbol_post : state_post) { for (const State target : symbol_post.targets) { + // Don't use NFA-like EPSILON transitions between zero-level states. + if (symbol_post.symbol == EPSILON && nft.levels[target] == 0) { + assert(state_level == 0); + continue; + } + assert(nft.levels[target] > nft.levels[state]); current_sync_type |= sync_types_v[target]; } } @@ -120,8 +126,23 @@ namespace { // If we are on the sync level, we can determine the synchronization type. if (state_level == sync_level) { - if (state_post.find(EPSILON) != state_post.end()) { - current_sync_type = SynchronizationType::ONLY_ON_EPSILON; + const auto epsilon_post_it = state_post.find(EPSILON); + if (epsilon_post_it != state_post.end()) { + // We don't want to count NFA-like EPSILON transitions. + // (i.e., transitions between zero-level states). + const bool has_nft_like_epsilon_transition = ( + state_level != 0 || + std::any_of( + epsilon_post_it->targets.cbegin(), + epsilon_post_it->targets.cend(), + [this](State target) { + return nft.levels[target] != 0; + } + ) + ); + if (has_nft_like_epsilon_transition) { + current_sync_type = SynchronizationType::ONLY_ON_EPSILON; + } if (state_post.size() > 1) { current_sync_type |= SynchronizationType::ONLY_ON_SYMBOL; } @@ -137,6 +158,11 @@ namespace { stack.push(state); // Push it back to the stack to compute it later. for (const SymbolPost& symbol_post : state_post) { for (const State target : symbol_post.targets) { + // Don't use NFA-like EPSILON transitions between zero-level states. + if (symbol_post.symbol == EPSILON && nft.levels[target] == 0) { + assert(state_level == 0); + continue; + } assert(nft.levels[target] > nft.levels[state]); assert(sync_types_v[target] != SynchronizationType::UNDER_COMPUTATION); if (sync_types_v[target] != SynchronizationType::UNDEFINED) { @@ -286,7 +312,31 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(same_symbol_posts.size() == 2); // One move per state in the pair. const Symbol symbol = reconnect ? reconnection_symbol : same_symbol_posts[0]->symbol; - combine_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); + + if (same_symbol_posts[0]->symbol == EPSILON) { + // We need to be careful not to use NFA-like EPSILON transitions between zero-level states. + OrdVector filtered_lhs_targets; + if (lhs.levels[lhs_state] == 0) { + std::copy_if( + same_symbol_posts[0]->targets.cbegin(), + same_symbol_posts[0]->targets.cend(), + std::back_inserter(filtered_lhs_targets), + [&](State s) { return lhs.levels[s] != 0; } + ); + } + OrdVector filtered_rhs_targets; + if (rhs.levels[rhs_state] == 0) { + std::copy_if( + same_symbol_posts[1]->targets.cbegin(), + same_symbol_posts[1]->targets.cend(), + std::back_inserter(filtered_rhs_targets), + [&](State s) { return rhs.levels[s] != 0; } + ); + } + combine_targets(filtered_lhs_targets, filtered_rhs_targets, EPSILON); + } else { + combine_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); + } } // Synchronization on DONT_CARE in the LHS. @@ -363,6 +413,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le for (const SymbolPost& copy_symbol_post : copy_nft.delta[copy_state]) { for (const State copy_target : copy_symbol_post.targets) { + if (copy_symbol_post.symbol == EPSILON && copy_state_level == 0 && copy_nft.levels[copy_target] == 0) { + // We do not want to use NFA-like EPSILON transitions between zero-level states. + continue; + } + const SynchronizationType copy_target_sync_type = copy_sync_types[copy_target]; // It makes sense to continue only if we believe that we will not get stuck // later due to an inability to synchronize. When this function is called @@ -547,6 +602,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // There should be an EPSILON transition. It has been told by the SynchronizationType. assert(running_epsilon_post_it != running_delta[running_state].end()); for (const State running_epsilon_target : running_epsilon_post_it->targets) { + if (running_state_level == 0 && running_levels[running_epsilon_target]) { + // We do not want to use NFA-like EPSILON transitions between zero-level states. + continue; + } assert(running_levels[running_epsilon_target] == 0 || running_levels[running_epsilon_target] > running_state_level); if (num_of_epsilons_to_replace_sync == 0) { // This synchronization level is being projected out (it has vanished). @@ -610,6 +669,57 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } }; + /** + * @brief Process the EPSILON transitions that have the NFA-like behavior + * (transitions between zero-level states). + * + * @param composition_state The state in the resulting composition NFT. + * @param lhs_src The source state of a potential EPSILON transition in the LHS NFT. + * @param rhs_src The source state of a potential EPSILON transition in the RHS NFT. + */ + auto process_nft_like_epsilon_transitions = [&](const State composition_state, const State lhs_src, const State rhs_src) { + assert(lhs.levels[lhs_src] == 0 && rhs.levels[rhs_src] == 0); + const auto lhs_eps_post_it = lhs.delta[lhs_src].find(EPSILON); + const auto rhs_eps_post_it = rhs.delta[rhs_src].find(EPSILON); + const bool lhs_eps_exists = lhs_eps_post_it != lhs.delta[lhs_src].end(); + const bool rhs_eps_exists = rhs_eps_post_it != rhs.delta[rhs_src].end(); + + // Create combinations of rhs_src with all zero-level targets of lhs_src. + if (lhs_eps_exists) { + for (const State lhs_target : lhs_eps_post_it->targets) { + if (lhs.levels[lhs_target] != 0) { + continue; + } + create_composition_state(lhs_target, rhs_src, 0, true, composition_state); + } + } + + // Create combinations of lhs_src with all zero-level targets of rhs_src. + if (rhs_eps_exists) { + for (const State rhs_target : rhs_eps_post_it->targets) { + if (rhs.levels[rhs_target] != 0) { + continue; + } + create_composition_state(lhs_src, rhs_target, 0, true, composition_state); + } + } + + // Create combinations of all zero-level targets of lhs_src with all zero-level targets of rhs_src. + if (lhs_eps_exists && rhs_eps_exists) { + for (const State lhs_target : lhs_eps_post_it->targets) { + if (lhs.levels[lhs_target] != 0) { + continue; + } + for (const State rhs_target : rhs_eps_post_it->targets) { + if (rhs.levels[rhs_target] != 0) { + continue; + } + create_composition_state(lhs_target, rhs_target, 0, true, composition_state); + } + } + } + }; + // Initialization of the main worklist. for (const State lhs_root: lhs.initial) { for (const State rhs_root: rhs.initial) { @@ -638,6 +748,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // is impossible due to the EPSILON, we will simulate waiting in the zero-level // state of the transducer that does not have that EPSILON. + // Process potential NFA-like EPISLON transitions (transitions between zero-level states). + process_nft_like_epsilon_transitions(composition_state, lhs_state, rhs_state); + // You can see the table defining operations for pair of synchronization types // in the documentation of the SynchronizationType enum class. const bool perform_wait_on_lhs = lhs_sync_type != SynchronizationType::ONLY_ON_EPSILON && From b150e0fca2c97a0532c092900b7d87f81b3b77f4 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Tue, 2 Sep 2025 16:39:12 +0200 Subject: [PATCH 41/65] compose only for JumpMode::NoJump with fast EPSILON --- include/mata/nft/nft.hh | 4 +++ src/nft/composition.cc | 72 ++++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index d38aee67e..a69fa0454 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -804,6 +804,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, * the transitions of `rhs` followed by next synchronization level (if exists). By default, synchronization * levels are projected out from the resulting NFT. * + * NOTE: Synchronization transitions have to be explicitly presented in the NFTs and have the length of 1. + * The only exception is an EPSILON transition between two zero-level states. Such a transition is + * called a "fast EPSILON transition". + * * @param[in] lhs First transducer to compose. * @param[in] rhs Second transducer to compose. * @param[in] lhs_sync_level The synchronization level of the @p lhs. diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 0b6fe8d85..0c4f219ec 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -111,8 +111,8 @@ namespace { current_sync_type = SynchronizationType::UNDEFINED; for (const SymbolPost& symbol_post : state_post) { for (const State target : symbol_post.targets) { - // Don't use NFA-like EPSILON transitions between zero-level states. - if (symbol_post.symbol == EPSILON && nft.levels[target] == 0) { + // Skip fast EPSILON transitions. + if (symbol_post.symbol == EPSILON && nft.num_of_levels != 1 && nft.levels[target] == 0) { assert(state_level == 0); continue; } @@ -128,10 +128,9 @@ namespace { if (state_level == sync_level) { const auto epsilon_post_it = state_post.find(EPSILON); if (epsilon_post_it != state_post.end()) { - // We don't want to count NFA-like EPSILON transitions. - // (i.e., transitions between zero-level states). - const bool has_nft_like_epsilon_transition = ( - state_level != 0 || + // We don't want to count fast EPSILON transitions. + const bool has_not_only_fast_epsilon = ( + state_level != 0 || nft.num_of_levels == 1 || std::any_of( epsilon_post_it->targets.cbegin(), epsilon_post_it->targets.cend(), @@ -140,7 +139,7 @@ namespace { } ) ); - if (has_nft_like_epsilon_transition) { + if (has_not_only_fast_epsilon) { current_sync_type = SynchronizationType::ONLY_ON_EPSILON; } if (state_post.size() > 1) { @@ -158,8 +157,8 @@ namespace { stack.push(state); // Push it back to the stack to compute it later. for (const SymbolPost& symbol_post : state_post) { for (const State target : symbol_post.targets) { - // Don't use NFA-like EPSILON transitions between zero-level states. - if (symbol_post.symbol == EPSILON && nft.levels[target] == 0) { + // Skip fast EPSILON transitions. + if (symbol_post.symbol == EPSILON && nft.num_of_levels != 1 && nft.levels[target] == 0) { assert(state_level == 0); continue; } @@ -188,9 +187,36 @@ namespace mata::nft Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, const bool project_out_sync_levels, const JumpMode jump_mode) { assert(lhs_sync_level < lhs.num_of_levels && rhs_sync_level < rhs.num_of_levels); - // Just for testing. - return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels); - throw std::invalid_argument("Synchronization levels must be less than the number of levels in the NFTs."); + if (jump_mode != JumpMode::NoJump) { + return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels); + } + + // Check that there are only explicit synchronization transitions of length 1 with exception for fast EPSILON transitions. + assert( + std::all_of( + lhs.delta.transitions().begin(), + lhs.delta.transitions().end(), + [&](const Transition& transition) { + return (lhs.levels[transition.source] < lhs_sync_level && lhs.levels[transition.target] <= lhs_sync_level && lhs.levels[transition.target] != 0) || + (lhs.levels[transition.source] == lhs_sync_level && lhs.levels[transition.target] == static_cast((lhs_sync_level + 1) % lhs.num_of_levels)) || + (lhs.levels[transition.source] > lhs_sync_level) || + (lhs.levels[transition.source] == 0 && lhs.levels[transition.target] == 0 && transition.symbol == EPSILON); + } + ) + ); + // Check that there are only explicit synchronization transitions of length 1 with exception for fast EPSILON transitions. + assert( + std::all_of( + rhs.delta.transitions().begin(), + rhs.delta.transitions().end(), + [&](const Transition& transition) { + return (rhs.levels[transition.source] < rhs_sync_level && rhs.levels[transition.target] <= rhs_sync_level && rhs.levels[transition.target] != 0) || + (rhs.levels[transition.source] == rhs_sync_level && rhs.levels[transition.target] == static_cast((rhs_sync_level + 1) % rhs.num_of_levels)) || + (rhs.levels[transition.source] > rhs_sync_level) || + (rhs.levels[transition.source] == 0 && rhs.levels[transition.target] == 0 && transition.symbol == EPSILON); + } + ) + ); // Number of Levels and States const size_t lhs_num_of_states = lhs.num_of_states(); @@ -314,9 +340,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le : same_symbol_posts[0]->symbol; if (same_symbol_posts[0]->symbol == EPSILON) { - // We need to be careful not to use NFA-like EPSILON transitions between zero-level states. + // We need to be careful not to use fast EPSILON transitions. OrdVector filtered_lhs_targets; - if (lhs.levels[lhs_state] == 0) { + if (lhs.levels[lhs_state] == 0 && lhs.num_of_levels != 1) { std::copy_if( same_symbol_posts[0]->targets.cbegin(), same_symbol_posts[0]->targets.cend(), @@ -325,7 +351,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le ); } OrdVector filtered_rhs_targets; - if (rhs.levels[rhs_state] == 0) { + if (rhs.levels[rhs_state] == 0 && rhs.num_of_levels != 1) { std::copy_if( same_symbol_posts[1]->targets.cbegin(), same_symbol_posts[1]->targets.cend(), @@ -413,8 +439,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le for (const SymbolPost& copy_symbol_post : copy_nft.delta[copy_state]) { for (const State copy_target : copy_symbol_post.targets) { - if (copy_symbol_post.symbol == EPSILON && copy_state_level == 0 && copy_nft.levels[copy_target] == 0) { - // We do not want to use NFA-like EPSILON transitions between zero-level states. + if (copy_symbol_post.symbol == EPSILON && copy_state_level == 0 && copy_nft.num_of_levels != 1 && copy_nft.levels[copy_target] == 0) { + // Skip fast EPSILON transitions. continue; } @@ -542,6 +568,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Level sync_level = running_sync_props.sync_level; const Levels& running_levels = running_sync_props.nft.levels; const Delta& running_delta = running_sync_props.nft.delta; + const size_t running_num_of_levels = running_sync_props.nft.num_of_levels; const size_t waiting_trans_before_sync = waiting_sync_props.num_of_levels_before_sync; const size_t waiting_trans_after_sync = waiting_sync_props.num_of_levels_after_sync; // EPSILON synchronization is handled differently here than in the main loop. @@ -602,8 +629,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // There should be an EPSILON transition. It has been told by the SynchronizationType. assert(running_epsilon_post_it != running_delta[running_state].end()); for (const State running_epsilon_target : running_epsilon_post_it->targets) { - if (running_state_level == 0 && running_levels[running_epsilon_target]) { - // We do not want to use NFA-like EPSILON transitions between zero-level states. + if (running_state_level == 0 && running_num_of_levels != 1 && running_levels[running_epsilon_target]) { + // Skip fast EPSILON transitions. continue; } assert(running_levels[running_epsilon_target] == 0 || running_levels[running_epsilon_target] > running_state_level); @@ -670,8 +697,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le }; /** - * @brief Process the EPSILON transitions that have the NFA-like behavior - * (transitions between zero-level states). + * @brief Process potential fast EPSILON transitions (transitions over EPSILON, + * between two zero-level states). * * @param composition_state The state in the resulting composition NFT. * @param lhs_src The source state of a potential EPSILON transition in the LHS NFT. @@ -748,7 +775,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // is impossible due to the EPSILON, we will simulate waiting in the zero-level // state of the transducer that does not have that EPSILON. - // Process potential NFA-like EPISLON transitions (transitions between zero-level states). + // Process potential fast EPSILON transitions. process_nft_like_epsilon_transitions(composition_state, lhs_state, rhs_state); // You can see the table defining operations for pair of synchronization types @@ -830,7 +857,6 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { assert(!lhs_sync_levels.empty()); assert(lhs_sync_levels.size() == rhs_sync_levels.size()); - assert(jump_mode != JumpMode::NoJump); // if (lhs_sync_levels.size() == 1 && rhs_sync_levels.size() == 1) { // // If we have only one synchronization level we can do it faster. From ed5e2d8bfec2eb9b6e5d186bfd06e7c42842c026 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 3 Sep 2025 15:54:04 +0200 Subject: [PATCH 42/65] correct synchronization type table --- src/nft/composition.cc | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 0c4f219ec..a8ca447b3 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -32,12 +32,12 @@ namespace { * || | | * ----------------------||----------------|-----------------|---------------------- * || | synchronize | synchornize - * ONLY_ON_EPSILON || | | - * || wait on RHS | | wait on RHS + * ONLY_ON_EPSILON || | lait on LHS | lait on LHS + * || wait on RHS | wait on RHS | wait on RHS * ----------------------||----------------|-----------------|---------------------- * || synchronize | synchronize | synchornize - * ON_EPSILON_AND_SYMBOL || | lait on LHS | wait on LHS - * || wait on RHS | | wait on RHS + * ON_EPSILON_AND_SYMBOL || | wait on LHS | wait on LHS + * || wait on RHS | wait on RHS | wait on RHS * ================================================================================= */ enum class SynchronizationType : uint8_t { @@ -54,6 +54,9 @@ namespace { lhs = lhs | rhs; return lhs; } + inline bool exist_intersection_of_sync_types(SynchronizationType lhs, SynchronizationType rhs) { + return (static_cast(lhs) & static_cast(rhs)) != 0; + } /** * @brief A class to hold properties related to synchronization during the composition of NFTs. @@ -780,13 +783,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // You can see the table defining operations for pair of synchronization types // in the documentation of the SynchronizationType enum class. - const bool perform_wait_on_lhs = lhs_sync_type != SynchronizationType::ONLY_ON_EPSILON && - rhs_sync_type != SynchronizationType::ONLY_ON_SYMBOL; - const bool perform_wait_on_rhs = lhs_sync_type != SynchronizationType::ONLY_ON_SYMBOL && - rhs_sync_type != SynchronizationType::ONLY_ON_EPSILON; - const bool can_synchronize_in_the_future = lhs_sync_type == rhs_sync_type || - lhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || - rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL; + const bool perform_wait_on_lhs = exist_intersection_of_sync_types(rhs_sync_type, SynchronizationType::ONLY_ON_EPSILON); + const bool perform_wait_on_rhs = exist_intersection_of_sync_types(lhs_sync_type, SynchronizationType::ONLY_ON_EPSILON); + const bool can_synchronize_in_the_future = exist_intersection_of_sync_types(lhs_sync_type, rhs_sync_type); if (perform_wait_on_lhs) { // LHS is waiting (i.e., there is an EPSILON in the RHS that LHS can not synchronize on). model_waiting(composition_state, lhs_state, rhs_state, true); @@ -805,9 +804,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // It makes sense to continue only if we believe that synchronization is possible, // or if we have already passed the synchronization level (i.e., lhs_sync_type == // rhs_sync_type == SynchronizationType::UNDEFINED). - assert(lhs_sync_type == rhs_sync_type || - lhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || - rhs_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL); + assert(exist_intersection_of_sync_types(lhs_sync_type, rhs_sync_type)); // Both LHS and RHS can take a step if they are not at the synchronization level. // The LHS always moves before the RHS (i.e., lhs_level < rhs_level). From 09aefb65f0a3b5aff3803aa0aea3f39087a67af6 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 4 Sep 2025 13:45:12 +0200 Subject: [PATCH 43/65] composition tests in the progress --- src/nft/composition.cc | 44 ++++++++++++------- tests/nft/nft-composition.cc | 85 ++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index a8ca447b3..965d4b08a 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -117,7 +117,7 @@ namespace { // Skip fast EPSILON transitions. if (symbol_post.symbol == EPSILON && nft.num_of_levels != 1 && nft.levels[target] == 0) { assert(state_level == 0); - continue; + continue; } assert(nft.levels[target] > nft.levels[state]); current_sync_type |= sync_types_v[target]; @@ -163,7 +163,7 @@ namespace { // Skip fast EPSILON transitions. if (symbol_post.symbol == EPSILON && nft.num_of_levels != 1 && nft.levels[target] == 0) { assert(state_level == 0); - continue; + continue; } assert(nft.levels[target] > nft.levels[state]); assert(sync_types_v[target] != SynchronizationType::UNDER_COMPUTATION); @@ -190,6 +190,7 @@ namespace mata::nft Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, const bool project_out_sync_levels, const JumpMode jump_mode) { assert(lhs_sync_level < lhs.num_of_levels && rhs_sync_level < rhs.num_of_levels); + // TODO: Modify it to work even with other jump modes. if (jump_mode != JumpMode::NoJump) { return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels); } @@ -199,7 +200,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le std::all_of( lhs.delta.transitions().begin(), lhs.delta.transitions().end(), - [&](const Transition& transition) { + [&](const Transition& transition) { return (lhs.levels[transition.source] < lhs_sync_level && lhs.levels[transition.target] <= lhs_sync_level && lhs.levels[transition.target] != 0) || (lhs.levels[transition.source] == lhs_sync_level && lhs.levels[transition.target] == static_cast((lhs_sync_level + 1) % lhs.num_of_levels)) || (lhs.levels[transition.source] > lhs_sync_level) || @@ -212,7 +213,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le std::all_of( rhs.delta.transitions().begin(), rhs.delta.transitions().end(), - [&](const Transition& transition) { + [&](const Transition& transition) { return (rhs.levels[transition.source] < rhs_sync_level && rhs.levels[transition.target] <= rhs_sync_level && rhs.levels[transition.target] != 0) || (rhs.levels[transition.source] == rhs_sync_level && rhs.levels[transition.target] == static_cast((rhs_sync_level + 1) % rhs.num_of_levels)) || (rhs.levels[transition.source] > rhs_sync_level) || @@ -264,10 +265,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Try to find the entry in the state map. const State found_state = composition_storage.get(key.first, key.second); - assert(found_state == composition_state_to_add); if (found_state != Limits::max_state) { + assert(result.levels[found_state] == level); return found_state; } + assert(composition_state_to_add == Limits::max_state || result.levels[composition_state_to_add] == level); // If not found, add a new state to the result NFT. // Since the key pair was not found in the map, we can be certain that the state is not yet in the worklist. @@ -305,7 +307,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const bool reconnect = false, const Symbol reconnection_symbol = Limits::max_symbol) { - const Level composition_target_level = static_cast((result.levels[composition_state] + 1) % result.num_of_levels); + const Level composition_state_level = result.levels[composition_state]; + const Level composition_target_level = static_cast((composition_state_level + 1) % result.num_of_levels); // When projecting out the synchronization levels, and we do not want to connect their // predecessors to their successors, we simply perform the synchronization, note the reached targets, // and remove (vanish) the synchronization level and its transition from the result NFT. @@ -316,7 +319,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le for (const State lhs_sync_target : lhs_sync_targets) { for (const State rhs_sync_target : rhs_sync_targets) { if (vanish_sync_level) { - worklist.push({ lhs_sync_target, rhs_sync_target }); + create_composition_state(lhs_sync_target, + rhs_sync_target, + composition_state_level, + true, + composition_state); } else { result.add_transition_with_target( composition_state, @@ -497,11 +504,15 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } else { // We encountered a standard synchronization and need to synchronize the LHS and RHS. // Call the synchronization function with the reconnection parameters set. - // The only way to reach this synchronization point in both states, while waiting - // in one of them, is when the waiting state was the LHS state. - // This is because each transition in the LHS occurs before the transitions in the RHS. - assert(!is_copy_state_lhs); - synchronize(composition_state, stationar_state, copy_target, true, copy_symbol_post.symbol); + // This point can be reached only if there is not transition in the LHS and we are + // copying transitions from the RHS, or if the synchronization in the RHS occurs + // on the 0-level, and we are copying transitions from the LHS. + assert(!is_copy_state_lhs || rhs_sync_level == 0); + if (!is_copy_state_lhs) { + synchronize(composition_state, stationar_state, copy_target, true, copy_symbol_post.symbol); + } else { + synchronize(composition_state, copy_target, stationar_state, true, copy_symbol_post.symbol); + } } } else { // We are not at a synchronization point. @@ -707,7 +718,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le * @param lhs_src The source state of a potential EPSILON transition in the LHS NFT. * @param rhs_src The source state of a potential EPSILON transition in the RHS NFT. */ - auto process_nft_like_epsilon_transitions = [&](const State composition_state, const State lhs_src, const State rhs_src) { + auto process_fast_epsilon_transitions = [&](const State composition_state, const State lhs_src, const State rhs_src) { assert(lhs.levels[lhs_src] == 0 && rhs.levels[rhs_src] == 0); const auto lhs_eps_post_it = lhs.delta[lhs_src].find(EPSILON); const auto rhs_eps_post_it = rhs.delta[rhs_src].find(EPSILON); @@ -779,7 +790,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // state of the transducer that does not have that EPSILON. // Process potential fast EPSILON transitions. - process_nft_like_epsilon_transitions(composition_state, lhs_state, rhs_state); + process_fast_epsilon_transitions(composition_state, lhs_state, rhs_state); // You can see the table defining operations for pair of synchronization types // in the documentation of the SynchronizationType enum class. @@ -804,7 +815,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // It makes sense to continue only if we believe that synchronization is possible, // or if we have already passed the synchronization level (i.e., lhs_sync_type == // rhs_sync_type == SynchronizationType::UNDEFINED). - assert(exist_intersection_of_sync_types(lhs_sync_type, rhs_sync_type)); + assert(exist_intersection_of_sync_types(lhs_sync_type, rhs_sync_type) || + (lhs_level == 0 && rhs_level > 0 && rhs_sync_type == SynchronizationType::UNDEFINED) || + (rhs_level == 0 && lhs_level > 0 && lhs_sync_type == SynchronizationType::UNDEFINED) + ); // Both LHS and RHS can take a step if they are not at the synchronization level. // The LHS always moves before the RHS (i.e., lhs_level < rhs_level). diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 0d98db929..d992c39b1 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -563,11 +563,88 @@ TEST_CASE("Mata::nft::compose()") { } } +#define SKIP_TESTS +TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { +#ifndef SKIP_TESTS + SECTION("2 levels x 2 levels") { + Nft expected_full(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'e', 3); + expected_full.delta.add(3, 'c', 4); + expected_full.delta.add(4, 'd', 5); + expected_full.delta.add(5, 'f', 6); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft lhs(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + + Nft rhs(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); + rhs.delta.add(0, 'b', 1); + rhs.delta.add(1, 'e', 2); + rhs.delta.add(2, 'd', 3); + rhs.delta.add(3, 'f', 4); + + SECTION("LHS | RHS") { + Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + SECTION("2 levels x 1 level") { + Nft expected_full(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'c', 3); + expected_full.delta.add(3, 'd', 4); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft lhs(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + + Nft rhs(3, { 0 }, { 2 }, { 0, 0, 0 }, 1); + rhs.delta.add(0, 'b', 1); + rhs.delta.add(1, 'd', 2); + + SECTION("LHS | RHS") { + Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); + CHECK(are_equivalent(result_full, expected_full)); + Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + +#endif +} + // TEST_CASE("nft::compose_fast()") { -// SECTION("project_out == true") { -// SECTION("two levels easy match") { -// Nft lhs; -// lhs.num_of_levels = 2; + // SECTION("project_out == true") { + // SECTION("two levels easy match") { + // Nft lhs; + // lhs.num_of_levels = 2; // lhs.levels = { 0, 1, 0, 1, 0 }; // lhs.initial.insert(0); // lhs.final.insert(4); From 358ceed103441066078e2b39c539261e40da0096 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 4 Sep 2025 17:48:43 +0200 Subject: [PATCH 44/65] composition tests - sliding levels no epsilon --- src/nft/composition.cc | 14 +- tests/nft/nft-composition.cc | 772 ++++++++++++++++++++++++++++++++++- 2 files changed, 779 insertions(+), 7 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 965d4b08a..0b8d33c74 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -460,9 +460,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // from the waiting simulation, we are interested in onyl synchronizations on EPSILON. const bool can_synchronize_in_the_future = ( waiting_worklist != nullptr ? copy_target_sync_type != SynchronizationType::ONLY_ON_SYMBOL - : stationar_state_sync_type == copy_target_sync_type || - stationar_state_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || - copy_target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL + : exist_intersection_of_sync_types(stationar_state_sync_type, copy_target_sync_type) || + stationar_state_sync_type == SynchronizationType::UNDEFINED || + copy_target_sync_type == SynchronizationType::UNDEFINED + // : stationar_state_sync_type == copy_target_sync_type || + // stationar_state_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || + // copy_target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL ); if (!can_synchronize_in_the_future) { // There is no way we would be able to synchronize in the future. @@ -814,10 +817,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // It makes sense to continue only if we believe that synchronization is possible, // or if we have already passed the synchronization level (i.e., lhs_sync_type == - // rhs_sync_type == SynchronizationType::UNDEFINED). + // SynchronizationType::UNDEFINED or rhs_sync_type == SynchronizationType::UNDEFINED). assert(exist_intersection_of_sync_types(lhs_sync_type, rhs_sync_type) || (lhs_level == 0 && rhs_level > 0 && rhs_sync_type == SynchronizationType::UNDEFINED) || - (rhs_level == 0 && lhs_level > 0 && lhs_sync_type == SynchronizationType::UNDEFINED) + (rhs_level == 0 && lhs_level > 0 && lhs_sync_type == SynchronizationType::UNDEFINED) || + (lhs_level > lhs_sync_level && rhs_level > rhs_sync_level &&lhs_sync_type == SynchronizationType::UNDEFINED && rhs_sync_type == SynchronizationType::UNDEFINED) ); // Both LHS and RHS can take a step if they are not at the synchronization level. diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index d992c39b1..820e3ae03 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -563,9 +563,9 @@ TEST_CASE("Mata::nft::compose()") { } } -#define SKIP_TESTS -TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { +// #define SKIP_TESTS #ifndef SKIP_TESTS +TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { SECTION("2 levels x 2 levels") { Nft expected_full(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); expected_full.delta.add(0, 'a', 1); @@ -590,17 +590,30 @@ TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); CHECK(are_equivalent(result_proj, expected_proj)); + } } SECTION("2 levels x 1 level") { @@ -623,23 +636,778 @@ TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); CHECK(are_equivalent(result_full, expected_full)); + Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); CHECK(are_equivalent(result_proj, expected_proj)); } } + SECTION("1 level x 1 level") { + Nft expected_full(4, { 0 }, { 3 }, { 0, 0, 0, 0 }, 1); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'c', 3); + + Nft lhs(4, { 0 }, { 3 }, { 0, 0, 0, 0 }, 1); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + Nft rhs(4, { 0 }, { 3 }, { 0, 0, 0, 0 }, 1); + rhs.delta.add(0, 'a', 1); + rhs.delta.add(1, 'b', 2); + rhs.delta.add(2, 'c', 3); + Nft result_full = compose(lhs, rhs, 0, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + } +} +#endif + +TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - sliding sync levels") { +#ifndef SKIP_TESTS + SECTION("sync level 0 x sync level 0") { + Nft lhs(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'd', 3); + lhs.delta.add(3, 'e', 4); + + Nft rhs(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); + rhs.delta.add(0, 'a', 1); + rhs.delta.add(1, 'c', 2); + rhs.delta.add(2, 'd', 3); + rhs.delta.add(3, 'f', 4); + + SECTION("LHS | RHS") { + Nft expected_full(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'c', 3); + expected_full.delta.add(3, 'd', 4); + expected_full.delta.add(4, 'e', 5); + expected_full.delta.add(5, 'f', 6); + Nft expected_proj = project_out(expected_full, { 0 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 0, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 0, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'c', 2); + expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(3, 'd', 4); + expected_full.delta.add(4, 'f', 5); + expected_full.delta.add(5, 'e', 6); + Nft expected_proj = project_out(expected_full, { 0 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 0, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 0 x sync level 1") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'x', 0); + lhs.delta.add(2, 'z', 6); + + Nft rhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'a', 2); + rhs.delta.add(2, 'h', 3); + rhs.delta.add(3, 'i', 4); + rhs.delta.add(4, 'd', 5); + rhs.delta.add(5, 'j', 6); + rhs.delta.add(2, 'w', 0); + rhs.delta.add(2, 'y', 6); + + SECTION("LHS | RHS") { + Nft expected_full(13, { 0 }, { 12 }, { 0, 1, 2, 3, 4, 4, 0, 1, 2, 3, 4, 4, 0 }, 5); + expected_full.delta.add(0, 'g', 1); + expected_full.delta.add(1, 'a', 2); + expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(3, 'x', 4); + expected_full.delta.add(3, 'c', 5); + expected_full.delta.add(3, 'z', 11); + expected_full.delta.add(4, 'w', 0); + expected_full.delta.add(5, 'h', 6); + expected_full.delta.add(6, 'i', 7); + expected_full.delta.add(7, 'd', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(10, 'j', 12); + expected_full.delta.add(11, 'y', 12); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 0, 1, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 0, 1, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(15, { 0 }, { 12 }, { 0, 4, 3, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 3, 4 }, 5); + expected_full.delta.add(0, 'g', 3); + expected_full.delta.add(3, 'a', 4); + expected_full.delta.add(4, 'w', 2); + expected_full.delta.add(2, 'b', 1); + expected_full.delta.add(1, 'x', 0); + expected_full.delta.add(4, 'h', 5); + expected_full.delta.add(5, 'b', 6); + expected_full.delta.add(6, 'c', 7); + expected_full.delta.add(7, 'i', 8); + expected_full.delta.add(8, 'd', 9); + expected_full.delta.add(9, 'j', 10); + expected_full.delta.add(10, 'e', 11); + expected_full.delta.add(11, 'f', 12); + expected_full.delta.add(4, 'y', 13); + expected_full.delta.add(13, 'b', 14); + expected_full.delta.add(14, 'z', 12); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 1, 0, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 1, 0, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 0 x sync level 2") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'x', 0); + lhs.delta.add(2, 'z', 6); + + Nft rhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'h', 2); + rhs.delta.add(2, 'a', 3); + rhs.delta.add(3, 'i', 4); + rhs.delta.add(4, 'j', 5); + rhs.delta.add(5, 'd', 6); + rhs.delta.add(2, 'a', 0); + rhs.delta.add(2, 'a', 6); + + Nft expected_full(15, { 0 }, { 12 }, { 0, 1, 2, 3, 4, 3, 4, 0, 1, 2, 3, 4, 0, 4, 3 }, 5); + expected_full.delta.add(0, 'g', 1); + expected_full.delta.add(1, 'h', 2); + expected_full.delta.add(2, 'a', 3); + expected_full.delta.add(3, 'b', 4); + expected_full.delta.add(4, 'x', 0); + expected_full.delta.add(2, 'a', 5); + expected_full.delta.add(5, 'b', 6); + expected_full.delta.add(6, 'c', 7); + expected_full.delta.add(7, 'i', 8); + expected_full.delta.add(8, 'j', 9); + expected_full.delta.add(9, 'd', 10); + expected_full.delta.add(10, 'e', 11); + expected_full.delta.add(11, 'f', 12); + expected_full.delta.add(2, 'a', 14); + expected_full.delta.add(14, 'b', 13); + expected_full.delta.add(13, 'z', 12); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + SECTION("LHS | RHS") { + Nft result_full = compose(lhs, rhs, 0, 2, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 0, 2, true, JumpMode::RepeatSymbol).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft result_full = compose(rhs, lhs, 2, 0, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 2, 0, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sycn level 1 x sync level 0") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'w', 0); + lhs.delta.add(2, 'z', 6); + + Nft rhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'b', 1); + rhs.delta.add(1, 'g', 2); + rhs.delta.add(2, 'h', 3); + rhs.delta.add(3, 'e', 4); + rhs.delta.add(4, 'i', 5); + rhs.delta.add(5, 'j', 6); + rhs.delta.add(2, 'x', 0); + rhs.delta.add(2, 'y', 6); + + SECTION("LHS | RHS") { + Nft expected_full(15, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 3, 3, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'c', 3); + expected_full.delta.add(3, 'g', 4); + expected_full.delta.add(4, 'h', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'f', 8); + expected_full.delta.add(8, 'i', 9); + expected_full.delta.add(9, 'j', 10); + expected_full.delta.add(11, 'y', 10); + expected_full.delta.add(12, 'g', 11); + expected_full.delta.add(2, 'z', 12); + expected_full.delta.add(2, 'w', 13); + expected_full.delta.add(13, 'g', 14); + expected_full.delta.add(14, 'x', 0); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + + SECTION("RHS | LHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'g', 3); + expected_full.delta.add(3, 'h', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'i', 8); + expected_full.delta.add(8, 'j', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(11, 'z', 10); + expected_full.delta.add(3, 'y', 11); + expected_full.delta.add(3, 'x', 12); + expected_full.delta.add(12, 'w', 0); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 1 x sync level 1") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'x', 0); + lhs.delta.add(2, 'z', 6); + + Nft rhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'b', 2); + rhs.delta.add(2, 'h', 3); + rhs.delta.add(3, 'i', 4); + rhs.delta.add(4, 'e', 5); + rhs.delta.add(5, 'j', 6); + rhs.delta.add(2, 'w', 0); + rhs.delta.add(2, 'y', 6); + + SECTION("LHS | RHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'g', 2); + expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(3, 'c', 4); + expected_full.delta.add(4, 'h', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'i', 7); + expected_full.delta.add(7, 'e', 8); + expected_full.delta.add(8, 'f', 9); + expected_full.delta.add(9, 'j', 10); + expected_full.delta.add(11, 'y', 10); + expected_full.delta.add(3, 'x', 12); + expected_full.delta.add(12, 'w', 0); + expected_full.delta.add(3, 'z', 11); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 1, 1, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 1, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 4 }, 5); + expected_full.delta.add(0, 'g', 1); + expected_full.delta.add(1, 'a', 2); + expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(3, 'h', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'i', 6); + expected_full.delta.add(6, 'd', 7); + expected_full.delta.add(7, 'e', 8); + expected_full.delta.add(8, 'j', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(11, 'z', 10); + expected_full.delta.add(3, 'y', 11); + expected_full.delta.add(3, 'w', 12); + expected_full.delta.add(12, 'x', 0); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 1, 1, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 1, 1, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 1 x sync level 2") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'x', 0); + lhs.delta.add(2, 'z', 6); + + Nft rhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'h', 2); + rhs.delta.add(2, 'b', 3); + rhs.delta.add(3, 'i', 4); + rhs.delta.add(4, 'j', 5); + rhs.delta.add(5, 'e', 6); + rhs.delta.add(2, 'b', 0); + rhs.delta.add(2, 'b', 6); + + SECTION("LHS | RHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'g', 2); + expected_full.delta.add(2, 'h', 3); + expected_full.delta.add(3, 'b', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'i', 7); + expected_full.delta.add(7, 'j', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(3, 'b', 12); + expected_full.delta.add(12, 'x', 0); + expected_full.delta.add(3, 'b', 11); + expected_full.delta.add(11, 'z', 10); + Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 1, 2, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 2, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 4 }, 5); + expected_full.delta.add(0, 'g', 1); + expected_full.delta.add(1, 'h', 2); + expected_full.delta.add(2, 'a', 3); + expected_full.delta.add(3, 'b', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'i', 6); + expected_full.delta.add(6, 'j', 7); + expected_full.delta.add(7, 'd', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(3, 'b', 12); + expected_full.delta.add(12, 'x', 0); + expected_full.delta.add(3, 'b', 11); + expected_full.delta.add(11, 'z', 10); + Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 2, 1, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 2, 1, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 2 x sync level 0") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + lhs.delta.add(2, 'c', 6); + + Nft rhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'c', 1); + rhs.delta.add(1, 'g', 2); + rhs.delta.add(2, 'h', 3); + rhs.delta.add(3, 'f', 4); + rhs.delta.add(4, 'i', 5); + rhs.delta.add(5, 'j', 6); + rhs.delta.add(2, 'x', 0); + rhs.delta.add(2, 'y', 6); + + Nft expected_full(15, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 3, 3, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'c', 3); + expected_full.delta.add(3, 'g', 4); + expected_full.delta.add(4, 'h', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'f', 8); + expected_full.delta.add(8, 'i', 9); + expected_full.delta.add(9, 'j', 10); + expected_full.delta.add(2, 'c', 13); + expected_full.delta.add(13, 'g', 14); + expected_full.delta.add(14, 'x', 0); + expected_full.delta.add(2, 'c', 12); + expected_full.delta.add(12, 'g', 11); + expected_full.delta.add(11, 'y', 10); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + SECTION("LHS | RHS") { + Nft result_full = compose(lhs, rhs, 2, 0, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 2, 0, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft result_full = compose(rhs, lhs, 0, 2, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 2, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 2 x sync level 1") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + lhs.delta.add(2, 'c', 6); + + Nft rhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'c', 2); + rhs.delta.add(2, 'h', 3); + rhs.delta.add(3, 'i', 4); + rhs.delta.add(4, 'f', 5); + rhs.delta.add(5, 'j', 6); + rhs.delta.add(2, 'x', 0); + rhs.delta.add(2, 'y', 6); + + SECTION("LHS | RHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'g', 3); + expected_full.delta.add(3, 'c', 4); + expected_full.delta.add(4, 'h', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'i', 8); + expected_full.delta.add(8, 'f', 9); + expected_full.delta.add(9, 'j', 10); + expected_full.delta.add(3, 'c', 12); + expected_full.delta.add(12, 'x', 0); + expected_full.delta.add(3, 'c', 11); + expected_full.delta.add(11, 'y', 10); + Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 2, 1, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 2, 1, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4, 4}, 5); + expected_full.delta.add(0, 'g', 1); + expected_full.delta.add(1, 'a', 2); + expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(3, 'c', 4); + expected_full.delta.add(4, 'h', 5); + expected_full.delta.add(5, 'i', 6); + expected_full.delta.add(6, 'd', 7); + expected_full.delta.add(7, 'e', 8); + expected_full.delta.add(8, 'f', 9); + expected_full.delta.add(9, 'j', 10); + expected_full.delta.add(3, 'c', 12); + expected_full.delta.add(12, 'x', 0); + expected_full.delta.add(3, 'c', 11); + expected_full.delta.add(11, 'y', 10); + Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 1, 2, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 1, 2, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 2 x sync level 2") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + lhs.delta.add(2, 'c', 6); + + Nft rhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'h', 2); + rhs.delta.add(2, 'c', 3); + rhs.delta.add(3, 'i', 4); + rhs.delta.add(4, 'j', 5); + rhs.delta.add(5, 'f', 6); + rhs.delta.add(2, 'c', 0); + rhs.delta.add(2, 'c', 6); + + SECTION("LHS | RHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'g', 3); + expected_full.delta.add(3, 'h', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'i', 8); + expected_full.delta.add(8, 'j', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + expected_full.delta.add(4, 'c', 10); + + Nft expected_proj(9, { 0 }, { 8 }, { 0, 1, 2, 3, 0, 1, 2, 3, 0 }, 4); + expected_proj.delta.add(0, 'a', 1); + expected_proj.delta.add(1, 'b', 2); + expected_proj.delta.add(2, 'g', 3); + expected_proj.delta.add(3, 'h', 4); + expected_proj.delta.add(4, 'd', 5); + expected_proj.delta.add(5, 'e', 6); + expected_proj.delta.add(6, 'i', 7); + expected_proj.delta.add(7, 'j', 8); + expected_proj.delta.add(3, 'h', 8); + expected_proj.delta.add(3, 'h', 0); + + Nft result_full = compose(lhs, rhs, 2, 2, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 2, 2, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, 'g', 1); + expected_full.delta.add(1, 'h', 2); + expected_full.delta.add(2, 'a', 3); + expected_full.delta.add(3, 'b', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'i', 6); + expected_full.delta.add(6, 'j', 7); + expected_full.delta.add(7, 'd', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + expected_full.delta.add(4, 'c', 10); + + Nft expected_proj(9, { 0 }, { 8 }, { 0, 1, 2, 3, 0, 1, 2, 3, 0 }, 4); + expected_proj.delta.add(0, 'g', 1); + expected_proj.delta.add(1, 'h', 2); + expected_proj.delta.add(2, 'a', 3); + expected_proj.delta.add(3, 'b', 4); + expected_proj.delta.add(4, 'i', 5); + expected_proj.delta.add(5, 'j', 6); + expected_proj.delta.add(6, 'd', 7); + expected_proj.delta.add(7, 'e', 8); + expected_proj.delta.add(3, 'b', 8); + expected_proj.delta.add(3, 'b', 0); + + Nft result_full = compose(rhs, lhs, 2, 2, false, JumpMode::NoJump).trim(); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 2, 2, true, JumpMode::NoJump).trim(); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } #endif } + + // TEST_CASE("nft::compose_fast()") { // SECTION("project_out == true") { // SECTION("two levels easy match") { From 6a21b50bf8559058c59a8d3b14f68ba108940838 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 5 Sep 2025 14:16:37 +0200 Subject: [PATCH 45/65] first tests with EPSILON + first major fixes --- src/nft/composition.cc | 72 ++++++++++++++++++++---------- tests/nft/nft-composition.cc | 85 +++++++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 26 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 0b8d33c74..5a9727084 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -459,13 +459,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // later due to an inability to synchronize. When this function is called // from the waiting simulation, we are interested in onyl synchronizations on EPSILON. const bool can_synchronize_in_the_future = ( - waiting_worklist != nullptr ? copy_target_sync_type != SynchronizationType::ONLY_ON_SYMBOL + waiting_worklist != nullptr ? copy_target_sync_type != SynchronizationType::ONLY_ON_SYMBOL || + copy_nft.levels[copy_target] == 0 // End of the waiting loop. : exist_intersection_of_sync_types(stationar_state_sync_type, copy_target_sync_type) || - stationar_state_sync_type == SynchronizationType::UNDEFINED || - copy_target_sync_type == SynchronizationType::UNDEFINED - // : stationar_state_sync_type == copy_target_sync_type || - // stationar_state_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL || - // copy_target_sync_type == SynchronizationType::ON_EPSILON_AND_SYMBOL + stationar_state_sync_type == SynchronizationType::UNDEFINED || + copy_target_sync_type == SynchronizationType::UNDEFINED ); if (!can_synchronize_in_the_future) { // There is no way we would be able to synchronize in the future. @@ -529,11 +527,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // but rather in virtual (nonexistent) states of the waiting loop over it. const State new_composition_state = result.add_state_with_level(new_composition_state_level); result.add_transition_with_target(composition_state, copy_symbol_post.symbol, new_composition_state, jump_mode); - // Because we are waiting, use the waiting_worklist. - waiting_worklist->push({ new_composition_state, stationar_state }); + // Sometimes, the running root state gets pushed to the worklist again. We need to handle it during the pop. + waiting_worklist->push({ new_composition_state, copy_target }); } else { // Just copy this transition. - assert(waiting_worklist == nullptr); result.add_transition_with_target( composition_state, copy_symbol_post.symbol, @@ -601,28 +598,56 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le (project_out_sync_levels ? 0 : 1) ); + // A special case we need to handle here. + // In some cases, the running NFT has only one level. + // This can happen when applying NFA to NFT. In that case, + // we can and need to simply add EPSILON transitions to the targets + // of the EPSILON transitions from the running root state. + if (running_num_of_levels == 1) { + assert(num_of_epsilons_to_replace_sync > 0); + const auto epsilon_target_it = running_delta[running_root_state].find(EPSILON); + // There has to be some EPSILON transition. Otherwise, there would be no reason to wait. + assert(epsilon_target_it != running_delta[running_root_state].end()); + StateSet new_composition_targets; + for (const State epsilon_target : epsilon_target_it->targets) { + assert(running_levels[epsilon_target] == 0); + new_composition_targets.insert(create_composition_state(waiting_root_state, + epsilon_target, + 0, + is_lhs_waiting)); + } + // This function ensures that the transitions are added in the most optimal way. + // It creates one common path for all targets and then use only the last transitions of + // that path to connect to all targets. + result.add_transition_with_same_level_targets(composition_root_state, EPSILON, new_composition_targets, jump_mode); + return; + } + // Initialization of the local worklist. // I chose to use a queue instead of a stack to help the branch predictor // with conditions inside the loop. With the queue, the algorithm hits the // same branch more ofthen, because all states being explored advance "together". std::queue> worklist; + State first_composition_state = composition_root_state; if (is_lhs_waiting && waiting_trans_before_sync > 0) { // Since LHS is waiting and LHS transitions have to be put before // RHS transitions, we now add waiting_trans_before_sync EPSILON // transitions. Doing this here simplifies the conditions in the loop below. - const State new_composition_state = result.add_transition_with_lenght(composition_root_state, EPSILON, waiting_trans_before_sync, jump_mode); - worklist.push({ std::move(new_composition_state), running_root_state }); - } else { - worklist.push({ composition_root_state, running_root_state }); + first_composition_state = result.add_transition_with_lenght(composition_root_state, EPSILON, waiting_trans_before_sync, jump_mode); } + worklist.push({ first_composition_state, running_root_state }); // The main loop of the waiting simulation. while (!worklist.empty()) { auto [composition_state, running_state] = worklist.front(); worklist.pop(); const Level running_state_level = running_levels[running_state]; - - if (running_state_level == sync_level) { + const Level composition_state_level = result.levels[composition_state]; + // If the synchronization level is on 0. Than we can be careful to not use it + // again when encountering next zero-level states in the running NFT. + const bool first_running_transition = (running_state == running_root_state && composition_state == first_composition_state); + const bool is_sync_level = running_state_level == sync_level && (running_state_level != 0 || first_running_transition); + if (is_sync_level) { // We are at the synchronization level. const bool is_last_running_transition = (running_state_level == running_sync_props.nft.num_of_levels - 1); @@ -646,13 +671,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // There should be an EPSILON transition. It has been told by the SynchronizationType. assert(running_epsilon_post_it != running_delta[running_state].end()); for (const State running_epsilon_target : running_epsilon_post_it->targets) { - if (running_state_level == 0 && running_num_of_levels != 1 && running_levels[running_epsilon_target]) { + if (running_state_level == 0 && running_num_of_levels != 1 && running_levels[running_epsilon_target] == 0) { // Skip fast EPSILON transitions. continue; } assert(running_levels[running_epsilon_target] == 0 || running_levels[running_epsilon_target] > running_state_level); if (num_of_epsilons_to_replace_sync == 0) { - // This synchronization level is being projected out (it has vanished). + // This synchronization level is being projected out (it will vanish). // There are no EPSILON transitions to add before or after the synchronization level. // However, we are sure, that there will be a least one "running" transition after this. assert(!is_last_running_transition); @@ -674,12 +699,12 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } else { // We are connecting to the next state in the waiting loop, // so we simply add a transition to the next state and - // continue the waiting + // continue the waiting. const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, 1, jump_mode); worklist.push({ new_composition_state, running_epsilon_target }); } } - } else if (running_state_level == 0 && running_state != running_root_state) { + } else if (running_state_level == 0 && !first_running_transition) { // We are at a leaf zero-level state in the running NFT. // We will create a connection to the zero-level state in the composition NFT // and continue exploring that state in the main composition loop @@ -691,17 +716,16 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(!is_lhs_waiting); assert(sync_level != running_sync_props.nft.num_of_levels - 1); assert(waiting_trans_after_sync > 0); - assert((running_state_level + waiting_trans_after_sync) % result.num_of_levels == 0); + assert((composition_state_level + waiting_trans_after_sync) == result.num_of_levels); result.add_transition_with_target( composition_state, EPSILON, create_composition_state(running_state, - running_root_state, - 0, - is_lhs_waiting), + waiting_root_state, + 0), jump_mode ); - } else { + }else { // We are at an internal (non-sync) level in the running NFT. // We can simply copy the transitions to the next states. // We do not need to worry that the target state might be at the diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 820e3ae03..aae975bb8 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -8,6 +8,8 @@ #include "mata/utils/ord-vector.hh" +// #define SKIP_TESTS + using namespace mata::nft; using namespace mata::utils; @@ -563,7 +565,6 @@ TEST_CASE("Mata::nft::compose()") { } } -// #define SKIP_TESTS #ifndef SKIP_TESTS TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { SECTION("2 levels x 2 levels") { @@ -686,7 +687,10 @@ TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { } #endif -TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - sliding sync levels") { + + + +TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { #ifndef SKIP_TESTS SECTION("sync level 0 x sync level 0") { Nft lhs(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); @@ -1406,6 +1410,83 @@ TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - sliding sync levels") { #endif } +TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { +#ifndef SKIP_TESTS + SECTION("sync level 0 x sync level 0") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, EPSILON, 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'd', 1); + rhs.delta.add(1, 'g', 2); + rhs.delta.add(2, 'h', 3); + + SECTION("LHS | RHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 3, 4 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, 'c', 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'f', 8); + expected_full.delta.add(8, 'g', 9); + expected_full.delta.add(9, 'h', 10); + expected_full.delta.add(2, 'c', 11); + expected_full.delta.add(11, EPSILON, 12); + expected_full.delta.add(12, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 0 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 0, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 0, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, 'b', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'g', 7); + expected_full.delta.add(7, 'h', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + Nft expected_proj = project_out(expected_full, { 0 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 0, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } +#endif +} + // TEST_CASE("nft::compose_fast()") { From 6bbc920cb4775485e79e870ee0810c0b6fb9943e Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 5 Sep 2025 21:55:09 +0200 Subject: [PATCH 46/65] wip --- src/nft/composition.cc | 5 +- tests/nft/nft-composition.cc | 468 ++++++++++++++++++++++++++++++++++- 2 files changed, 469 insertions(+), 4 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 5a9727084..a7ce32db9 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -676,17 +676,16 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le continue; } assert(running_levels[running_epsilon_target] == 0 || running_levels[running_epsilon_target] > running_state_level); - if (num_of_epsilons_to_replace_sync == 0) { + if (num_of_epsilons_to_replace_sync == 0 && !is_last_running_transition) { // This synchronization level is being projected out (it will vanish). // There are no EPSILON transitions to add before or after the synchronization level. // However, we are sure, that there will be a least one "running" transition after this. - assert(!is_last_running_transition); worklist.push({ composition_state, running_epsilon_target }); } else if (is_last_running_transition) { // We are connecting to the zero-level state, therefore we have to // create a new composition state that will be put into the main worklist // (side effect of the create_composition_state). - assert(result.levels[composition_state] + 1 == result.num_of_levels); + assert(result.levels[composition_state] + (num_of_epsilons_to_replace_sync >= 1 ? 1 : 0) + (is_lhs_waiting ? 0 : waiting_trans_after_sync) == result.num_of_levels); result.add_transition_with_target( composition_state, EPSILON, diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index aae975bb8..e6fa58fc1 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -8,7 +8,7 @@ #include "mata/utils/ord-vector.hh" -// #define SKIP_TESTS +#define SKIP_TESTS using namespace mata::nft; using namespace mata::utils; @@ -1484,7 +1484,473 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { CHECK(are_equivalent(result_proj, expected_proj)); } } + + SECTION("sync level 0 x sync level 1") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, EPSILON, 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'd', 2); + rhs.delta.add(2, 'h', 3); + + SECTION("LHS | RHS") { + Nft expected_full(12, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(3, 'c', 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'g', 6); + expected_full.delta.add(6, 'd', 7); + expected_full.delta.add(7, 'e', 8); + expected_full.delta.add(8, 'f', 9); + expected_full.delta.add(9, 'h', 10); + expected_full.delta.add(3, 'c', 11); + expected_full.delta.add(11, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 0, 1, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 0, 1, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, 'b', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'g', 6); + expected_full.delta.add(6, 'd', 7); + expected_full.delta.add(7, 'h', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 1, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 1, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 0 x sync level 2") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, EPSILON, 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'h', 2); + rhs.delta.add(2, 'd', 3); + + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, 'b', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'g', 6); + expected_full.delta.add(6, 'h', 7); + expected_full.delta.add(7, 'd', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + SECTION("LHS | RHS") { + Nft result_full = compose(lhs, rhs, 0, 2, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 0, 2, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft result_full = compose(rhs, lhs, 2, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 2, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 1 x sync level 0") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, EPSILON, 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'e', 1); + rhs.delta.add(1, 'g', 2); + rhs.delta.add(2, 'h', 3); + + SECTION("LHS | RHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 3, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, 'c', 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'f', 8); + expected_full.delta.add(8, 'g', 9); + expected_full.delta.add(9, 'h', 10); + expected_full.delta.add(2, 'c', 11); + expected_full.delta.add(11, EPSILON, 12); + expected_full.delta.add(12, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'g', 8); + expected_full.delta.add(8, 'h', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 1 x sync level 1") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, EPSILON, 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'e', 2); + rhs.delta.add(2, 'h', 3); + + SECTION("LHS | RHS") { + Nft expected_full(12, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, 'c', 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'g', 7); + expected_full.delta.add(7, 'e', 8); + expected_full.delta.add(8, 'f', 9); + expected_full.delta.add(9, 'h', 10); + expected_full.delta.add(3, 'c', 11); + expected_full.delta.add(11, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 1, 1, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 1, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, 'a', 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'g', 6); + expected_full.delta.add(6, 'd', 7); + expected_full.delta.add(7, 'e', 8); + expected_full.delta.add(8, 'h', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 1, 1, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 1, 1, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 1 x sync level 2") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, EPSILON, 2); + lhs.delta.add(2, 'c', 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, 'c', 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'h', 2); + rhs.delta.add(2, 'e', 3); + + SECTION("LHS | RHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'g', 7); + expected_full.delta.add(7, 'h', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 1, 2, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 2, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, 'a', 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'g', 6); + expected_full.delta.add(6, 'h', 7); + expected_full.delta.add(7, 'd', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, 'c', 0); + Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 2, 1, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 2, 1, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + + SECTION("sync level 2 x sync level 0") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, EPSILON, 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, EPSILON, 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'f', 1); + rhs.delta.add(1, 'g', 2); + rhs.delta.add(2, 'h', 3); + + SECTION("LHS | RHS") { + Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 3, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'f', 8); + expected_full.delta.add(8, 'g', 9); + expected_full.delta.add(9, 'h', 10); + expected_full.delta.add(2, EPSILON, 11); + expected_full.delta.add(11, EPSILON, 12); + expected_full.delta.add(12, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 2, 0, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 2, 0, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + + SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'f', 8); + expected_full.delta.add(8, 'g', 9); + expected_full.delta.add(9, 'h', 10); + expected_full.delta.add(4, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 0, 2, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 2, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } #endif + SECTION("sync level 2 x sync level 1") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, EPSILON, 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, EPSILON, 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'f', 2); + rhs.delta.add(2, 'h', 3); + + SECTION("LHS | RHS") { + Nft expected_full(12, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'g', 8); + expected_full.delta.add(8, 'f', 9); + expected_full.delta.add(9, 'h', 10); + expected_full.delta.add(3, EPSILON, 11); + expected_full.delta.add(11, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 2, 1, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + // HEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEERE + Nft result_proj = compose(lhs, rhs, 2, 1, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + } + } } From 9a85e6c394d04ea6e6c063046d7d6cf937807e75 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sun, 7 Sep 2025 16:32:37 +0200 Subject: [PATCH 47/65] sliding sync with EPSILON + optimizations of the number of transitions --- src/nft/composition.cc | 35 +++++++---- tests/nft/nft-composition.cc | 113 +++++++++++++++++++++++++++++++---- 2 files changed, 127 insertions(+), 21 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index a7ce32db9..801fa027c 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -443,6 +443,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const bool handle_synchronization_in_place = project_out_sync_levels && stationar_sync_props.num_of_levels_after_sync == 0 && copy_sync_props.num_of_levels_after_sync == 0 && ( + !is_copy_state_lhs || + stationar_sync_props.num_of_levels_before_sync == 0 + ) && ( waiting_worklist != nullptr || stationar_state_level == stationar_sync_props.sync_level ); @@ -583,19 +586,30 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const Levels& running_levels = running_sync_props.nft.levels; const Delta& running_delta = running_sync_props.nft.delta; const size_t running_num_of_levels = running_sync_props.nft.num_of_levels; + const size_t running_trans_after_sync = running_sync_props.num_of_levels_after_sync; const size_t waiting_trans_before_sync = waiting_sync_props.num_of_levels_before_sync; const size_t waiting_trans_after_sync = waiting_sync_props.num_of_levels_after_sync; // EPSILON synchronization is handled differently here than in the main loop. - // To avoid duplicate transitions and speed up the algorithm, we replace each + // To avoid redundant transitions caused by early branching, we replace each // EPSILON synchronization with N EPSILON transitions, where: - // - Add 1 to N if we are NOT projecting out synchronization levels. - // - Add waiting_trans_before_sync to N if waiting in RHS + // - Add waiting_trans_before_sync to N if NOT waiting in LHS // (RHS transitions go after LHS, just before the synchronization). - // - Add waiting_trans_after_sync to N if waiting in LHS - // (LHS transitions go before RHS, just after the synchronization). + // - Add waiting_trans_after_sync to N if waiting in LHS, or if there are no + // running transitions after the synchronization. + // - Add 1 to N if synchronization levels are NOT being projected out. + // The first N-1 EPSILON transitions are common for all targets, + // and only the last EPSILON transition is used to connect to the appropriate targets. + const size_t num_of_epsilons_to_replace_sync = ( - (is_lhs_waiting ? waiting_trans_after_sync : waiting_trans_before_sync) + - (project_out_sync_levels ? 0 : 1) + (is_lhs_waiting + ? 0 + : waiting_trans_before_sync) + + ((is_lhs_waiting || running_trans_after_sync == 0) + ? waiting_trans_after_sync + : 0) + + (project_out_sync_levels + ? 0 + : 1) ); // A special case we need to handle here. @@ -652,9 +666,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const bool is_last_running_transition = (running_state_level == running_sync_props.nft.num_of_levels - 1); // It cannot happen that the synchronization level is projected out - // and there are no other transitions to addbefore reaching the next zero-level state. + // and there are no other transitions to add before reaching the next zero-level state. // This is because such a scenario would already be handled by the copy_transition function. - assert(!project_out_sync_levels || !is_last_running_transition || waiting_trans_after_sync > 0); + assert(!project_out_sync_levels || !is_last_running_transition || waiting_trans_after_sync > 0 || + (!is_lhs_waiting && waiting_sync_props.num_of_levels_before_sync > 0)); if (num_of_epsilons_to_replace_sync > 1) { // The synchronization will be replaced by an EPSILON transition anyway. @@ -685,7 +700,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We are connecting to the zero-level state, therefore we have to // create a new composition state that will be put into the main worklist // (side effect of the create_composition_state). - assert(result.levels[composition_state] + (num_of_epsilons_to_replace_sync >= 1 ? 1 : 0) + (is_lhs_waiting ? 0 : waiting_trans_after_sync) == result.num_of_levels); + assert(result.levels[composition_state] + (num_of_epsilons_to_replace_sync >= 1 ? 1 : 0) == result.num_of_levels); result.add_transition_with_target( composition_state, EPSILON, diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index e6fa58fc1..a67f93f85 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -8,7 +8,7 @@ #include "mata/utils/ord-vector.hh" -#define SKIP_TESTS +// #define SKIP_TESTS using namespace mata::nft; using namespace mata::utils; @@ -1846,7 +1846,7 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { rhs.delta.add(2, 'h', 3); SECTION("LHS | RHS") { - Nft expected_full(13, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 3, 4 }, 5); + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); expected_full.delta.add(0, 'a', 1); expected_full.delta.add(1, 'b', 2); expected_full.delta.add(2, EPSILON, 3); @@ -1857,9 +1857,7 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { expected_full.delta.add(7, 'f', 8); expected_full.delta.add(8, 'g', 9); expected_full.delta.add(9, 'h', 10); - expected_full.delta.add(2, EPSILON, 11); - expected_full.delta.add(11, EPSILON, 12); - expected_full.delta.add(12, EPSILON, 0); + expected_full.delta.add(4, EPSILON, 0); Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 2, 0, false, JumpMode::NoJump); @@ -1903,7 +1901,7 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { CHECK(are_equivalent(result_proj, expected_proj)); } } -#endif + SECTION("sync level 2 x sync level 1") { Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); lhs.delta.add(0, 'a', 1); @@ -1920,7 +1918,7 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { rhs.delta.add(2, 'h', 3); SECTION("LHS | RHS") { - Nft expected_full(12, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 4 }, 5); + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); expected_full.delta.add(0, 'a', 1); expected_full.delta.add(1, 'b', 2); expected_full.delta.add(2, EPSILON, 3); @@ -1931,8 +1929,7 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { expected_full.delta.add(7, 'g', 8); expected_full.delta.add(8, 'f', 9); expected_full.delta.add(9, 'h', 10); - expected_full.delta.add(3, EPSILON, 11); - expected_full.delta.add(11, EPSILON, 0); + expected_full.delta.add(4, EPSILON, 0); Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 2, 1, false, JumpMode::NoJump); @@ -1941,7 +1938,6 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); CHECK(are_equivalent(result_full, expected_full)); - // HEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEERE Nft result_proj = compose(lhs, rhs, 2, 1, true, JumpMode::NoJump); CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); @@ -1949,11 +1945,106 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, 'a', 2); + expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'g', 6); + expected_full.delta.add(6, 'd', 7); + expected_full.delta.add(7, 'e', 8); + expected_full.delta.add(8, 'f', 9); + expected_full.delta.add(9, 'h', 10); + expected_full.delta.add(4, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 1, 2, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 1, 2, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); } } -} +#endif + SECTION("sync level 2 x sync level 2") { + Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(2, EPSILON, 3); + lhs.delta.add(3, 'd', 4); + lhs.delta.add(4, 'e', 5); + lhs.delta.add(5, 'f', 6); + lhs.delta.add(2, EPSILON, 0); + + Nft rhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'g', 1); + rhs.delta.add(1, 'h', 2); + rhs.delta.add(2, 'f', 3); + SECTION("LHS | RHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'b', 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(6, 'e', 7); + expected_full.delta.add(7, 'g', 8); + expected_full.delta.add(8, 'h', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 4 }, JumpMode::NoJump); + Nft result_full = compose(lhs, rhs, 2, 2, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 2, 2, true, JumpMode::NoJump); + result_proj.print_to_dot(std::string("result.dot"), true); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(11, { 0 }, { 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, 'a', 3); + expected_full.delta.add(3, 'b', 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'g', 6); + expected_full.delta.add(6, 'h', 7); + expected_full.delta.add(7, 'd', 8); + expected_full.delta.add(8, 'e', 9); + expected_full.delta.add(9, 'f', 10); + expected_full.delta.add(4, EPSILON, 0); + Nft expected_proj = project_out(expected_full, { 4 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 2, 2, false, JumpMode::NoJump); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 2, 2, true, JumpMode::NoJump); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } +} // TEST_CASE("nft::compose_fast()") { // SECTION("project_out == true") { From bff6d751d82ada6a6f61f746461a0fd031114cc4 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sun, 7 Sep 2025 23:29:17 +0200 Subject: [PATCH 48/65] wip --- src/nft/composition.cc | 60 ++++---- tests/nft/nft-composition.cc | 284 ++++++++++++++++++++++++++++++++++- 2 files changed, 309 insertions(+), 35 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 801fa027c..bef5d19c3 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -351,7 +351,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le if (same_symbol_posts[0]->symbol == EPSILON) { // We need to be careful not to use fast EPSILON transitions. - OrdVector filtered_lhs_targets; + StateSet filtered_lhs_targets; if (lhs.levels[lhs_state] == 0 && lhs.num_of_levels != 1) { std::copy_if( same_symbol_posts[0]->targets.cbegin(), @@ -359,8 +359,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le std::back_inserter(filtered_lhs_targets), [&](State s) { return lhs.levels[s] != 0; } ); + } else { + filtered_lhs_targets = same_symbol_posts[0]->targets; } - OrdVector filtered_rhs_targets; + StateSet filtered_rhs_targets; if (rhs.levels[rhs_state] == 0 && rhs.num_of_levels != 1) { std::copy_if( same_symbol_posts[1]->targets.cbegin(), @@ -368,6 +370,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le std::back_inserter(filtered_rhs_targets), [&](State s) { return rhs.levels[s] != 0; } ); + } else { + filtered_rhs_targets = same_symbol_posts[1]->targets; } combine_targets(filtered_lhs_targets, filtered_rhs_targets, EPSILON); } else { @@ -766,38 +770,37 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const bool lhs_eps_exists = lhs_eps_post_it != lhs.delta[lhs_src].end(); const bool rhs_eps_exists = rhs_eps_post_it != rhs.delta[rhs_src].end(); - // Create combinations of rhs_src with all zero-level targets of lhs_src. + StateSet filtered_lhs_fast_eps_targets{ lhs_src }; + StateSet filtered_rhs_fast_eps_targets{ rhs_src }; if (lhs_eps_exists) { - for (const State lhs_target : lhs_eps_post_it->targets) { - if (lhs.levels[lhs_target] != 0) { - continue; - } - create_composition_state(lhs_target, rhs_src, 0, true, composition_state); - } + std::copy_if( + lhs_eps_post_it->targets.cbegin(), + lhs_eps_post_it->targets.cend(), + std::back_inserter(filtered_lhs_fast_eps_targets), + [&](State s) { return lhs.levels[s] == 0; } + ); } - - // Create combinations of lhs_src with all zero-level targets of rhs_src. if (rhs_eps_exists) { - for (const State rhs_target : rhs_eps_post_it->targets) { - if (rhs.levels[rhs_target] != 0) { - continue; - } - create_composition_state(lhs_src, rhs_target, 0, true, composition_state); - } + std::copy_if( + rhs_eps_post_it->targets.cbegin(), + rhs_eps_post_it->targets.cend(), + std::back_inserter(filtered_rhs_fast_eps_targets), + [&](State s) { return rhs.levels[s] == 0; } + ); } - - // Create combinations of all zero-level targets of lhs_src with all zero-level targets of rhs_src. - if (lhs_eps_exists && rhs_eps_exists) { - for (const State lhs_target : lhs_eps_post_it->targets) { - if (lhs.levels[lhs_target] != 0) { + // Create combinations of all non-zero-level targets of lhs_src with all non-zero-level targets of rhs_src. + for (const State lhs_target : filtered_lhs_fast_eps_targets) { + for (const State rhs_target : filtered_rhs_fast_eps_targets) { + if (lhs_target == lhs_src && rhs_target == rhs_src) { + // This is the original state pair. continue; } - for (const State rhs_target : rhs_eps_post_it->targets) { - if (rhs.levels[rhs_target] != 0) { - continue; - } - create_composition_state(lhs_target, rhs_target, 0, true, composition_state); - } + result.add_transition_with_target( + composition_state, + EPSILON, + create_composition_state(lhs_target, rhs_target, 0, true), + jump_mode + ); } } }; @@ -833,6 +836,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Process potential fast EPSILON transitions. process_fast_epsilon_transitions(composition_state, lhs_state, rhs_state); + // TODO: Optimize/reduce the number of similar combination of waiting simulations. // You can see the table defining operations for pair of synchronization types // in the documentation of the SynchronizationType enum class. const bool perform_wait_on_lhs = exist_intersection_of_sync_types(rhs_sync_type, SynchronizationType::ONLY_ON_EPSILON); diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index a67f93f85..3762cda8c 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -8,7 +8,7 @@ #include "mata/utils/ord-vector.hh" -// #define SKIP_TESTS +#define SKIP_TESTS using namespace mata::nft; using namespace mata::utils; @@ -1972,7 +1972,7 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { CHECK(are_equivalent(result_proj, expected_proj)); } } -#endif + SECTION("sync level 2 x sync level 2") { Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); lhs.delta.add(0, 'a', 1); @@ -2010,7 +2010,6 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 2, 2, true, JumpMode::NoJump); - result_proj.print_to_dot(std::string("result.dot"), true); CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); @@ -2044,8 +2043,283 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { CHECK(are_equivalent(result_proj, expected_proj)); } } + #endif +} + +TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { +#ifndef SKIP_TESTS + SECTION("3 levels x 3 levels + fast EPSILON on LHS") { + Nft lhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + lhs.delta.add(0, EPSILON, 1); + lhs.delta.add(0, EPSILON, 3); + lhs.delta.add(1, 'a', 2); + lhs.delta.add(2, 'b', 3); + + Nft rhs(4, { 0 }, { 0, 3 }, { 0, 1, 2, 0 }, 3); + rhs.delta.add(0, 'c', 1); + rhs.delta.add(1, EPSILON, 2); + rhs.delta.add(2, 'd', 3); + + SECTION("LHS | RHS") { + Nft expected_full(32, { 0 }, { 5, 14 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(0, EPSILON, 6); + expected_full.delta.add(6, EPSILON, 7); + expected_full.delta.add(7, 'a', 8); + expected_full.delta.add(8, 'b', 9); + expected_full.delta.add(9, EPSILON, 5); + expected_full.delta.add(0, 'c', 10); + expected_full.delta.add(10, EPSILON, 11); + expected_full.delta.add(11, 'a', 12); + expected_full.delta.add(12, 'b', 13); + expected_full.delta.add(13, 'd', 14); + expected_full.delta.add(0, 'c', 15); + expected_full.delta.add(15, EPSILON, 16); + expected_full.delta.add(16, EPSILON, 17); + expected_full.delta.add(17, EPSILON, 18); + expected_full.delta.add(18, 'd', 19); + expected_full.delta.add(19, EPSILON, 20); + expected_full.delta.add(20, EPSILON, 21); + expected_full.delta.add(21, EPSILON, 22); + expected_full.delta.add(22, EPSILON, 23); + expected_full.delta.add(23, EPSILON, 14); + expected_full.delta.add(5, 'c', 24); + expected_full.delta.add(24, EPSILON, 25); + expected_full.delta.add(25, EPSILON, 26); + expected_full.delta.add(26, EPSILON, 27); + expected_full.delta.add(27, 'd', 14); + expected_full.delta.add(19, EPSILON, 28); + expected_full.delta.add(28, EPSILON, 29); + expected_full.delta.add(29, 'a', 30); + expected_full.delta.add(30, 'b', 31); + expected_full.delta.add(31, EPSILON, 14); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::RepeatSymbol); + + Nft result_full = compose(lhs, rhs, 0, 1, false, JumpMode::NoJump); + CHECK(result_full.final.size() == expected_full.final.size()); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 0, 1, true, JumpMode::NoJump); + CHECK(result_proj.final.size() == expected_proj.final.size()); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + SECTION("RHS | LHS") { + Nft expected_full(32, { 0 }, { 5, 14 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 }, 5); + expected_full.delta.add(0, EPSILON, 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, EPSILON, 3); + expected_full.delta.add(3, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(0, EPSILON, 6); + expected_full.delta.add(6, EPSILON, 7); + expected_full.delta.add(7, EPSILON, 8); + expected_full.delta.add(8, 'a', 9); + expected_full.delta.add(9, 'b', 5); + expected_full.delta.add(0, 'c', 10); + expected_full.delta.add(10, EPSILON, 11); + expected_full.delta.add(11, 'd', 12); + expected_full.delta.add(12, 'a', 13); + expected_full.delta.add(13, 'b', 14); + expected_full.delta.add(0, 'c', 15); + expected_full.delta.add(15, EPSILON, 16); + expected_full.delta.add(16, 'd', 17); + expected_full.delta.add(17, EPSILON, 18); + expected_full.delta.add(18, EPSILON, 19); + expected_full.delta.add(19, EPSILON, 20); + expected_full.delta.add(20, EPSILON, 21); + expected_full.delta.add(21, EPSILON, 22); + expected_full.delta.add(22, EPSILON, 23); + expected_full.delta.add(23, EPSILON, 14); + expected_full.delta.add(5, 'c', 24); + expected_full.delta.add(24, EPSILON, 25); + expected_full.delta.add(25, 'd', 26); + expected_full.delta.add(26, EPSILON, 27); + expected_full.delta.add(27, EPSILON, 14); + expected_full.delta.add(19, EPSILON, 28); + expected_full.delta.add(28, EPSILON, 29); + expected_full.delta.add(29, EPSILON, 30); + expected_full.delta.add(30, 'a', 31); + expected_full.delta.add(31, 'b', 14); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 1, 0, false, JumpMode::NoJump); + CHECK(result_full.final.size() == expected_full.final.size()); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 1, 0, true, JumpMode::NoJump); + CHECK(result_proj.final.size() == expected_proj.final.size()); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } +#endif + SECTION("3 levels x 1 level") { + Nft lhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, EPSILON, 2); + lhs.delta.add(2, 'b', 3); + + Nft rhs(2, { 0 }, { 1 }, { 0, 0 }, 1); + rhs.delta.add(0, EPSILON, 1); + + Nft expected_full(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, 'b', 3); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + SECTION("LHS | RHS") { + Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); + expected_full.print_to_dot(std::string("expected.dot"), true); + result_full.print_to_dot(std::string("result.dot"), true); + CHECK(result_full.final.size() == expected_full.final.size()); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); + CHECK(result_proj.final.size() == expected_proj.final.size()); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } + } +// TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { +// Nft lhs(12, { 0 }, { 6, 7, 11 }, { 0, 1, 2, 2, 3, 3, 0, 0, 1, 2, 3, 0 }, 4); +// lhs.delta.add(0, 'a', 1); +// lhs.delta.add(1, 'b', 2); +// lhs.delta.add(1, 'c', 3); +// lhs.delta.add(2, 'y', 4); +// lhs.delta.add(3, EPSILON, 5); +// lhs.delta.add(4, 'd', 6); +// lhs.delta.add(5, 'e', 7); +// lhs.delta.add(2, 'f', 8); +// lhs.delta.add(3, 'g', 9); +// lhs.delta.add(8, 'z', 10); +// lhs.delta.add(9, 'h', 11); + +// Nft rhs(12, { 0 }, { 6, 7, 11 }, { 0, 1, 2, 2, 3, 3, 0, 0, 1, 2, 3, 0 }, 4); +// rhs.delta.add(0, 'j', 1); +// rhs.delta.add(1, 'k', 2); +// rhs.delta.add(1, 'l', 3); +// rhs.delta.add(2, 'z', 4); +// rhs.delta.add(3, EPSILON, 5); +// rhs.delta.add(4, 'm', 6); +// rhs.delta.add(5, 'n', 7); +// rhs.delta.add(7, 'o', 8); +// rhs.delta.add(8, 'p', 9); +// rhs.delta.add(9, 'y', 10); +// rhs.delta.add(10, 'q', 11); + +// SECTION("LHS | RHS") { +// Nft expected_full(41, { 0 }, { 22, 36 }, { 0, 1, 2, 3, 4, 2, 3, 4, 5, 6, 0, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4 }, 7); +// expected_full.delta.add(0, 'a', 1); +// expected_full.delta.add(1, 'b', 2); +// expected_full.delta.add(1, 'c', 5); +// expected_full.delta.add(2, 'j', 3); +// expected_full.delta.add(3, 'k', 4); +// expected_full.delta.add(5, 'j', 6); +// expected_full.delta.add(6, 'l', 7); +// expected_full.delta.add(7, EPSILON, 8); +// expected_full.delta.add(8, 'e', 9); +// expected_full.delta.add(9, 'n', 10); +// expected_full.delta.add(10, 'f', 37); +// expected_full.delta.add(37, 'g', 38); +// expected_full.delta.add(38, 'o', 39); +// expected_full.delta.add(39, 'p', 40); +// expected_full.delta.add(5, EPSILON, 11); +// expected_full.delta.add(11, EPSILON, 12); +// expected_full.delta.add(12, EPSILON, 13); +// expected_full.delta.add(13, 'e', 14); +// expected_full.delta.add(14, EPSILON, 15); +// expected_full.delta.add(15, 'f', 16); +// expected_full.delta.add(16, 'g', 17); +// expected_full.delta.add(17, 'j', 18); +// expected_full.delta.add(18, 'k', 19); +// expected_full.delta.add(19, 'z', 20); +// expected_full.delta.add(20, 'h', 21); +// expected_full.delta.add(21, 'm', 22); +// expected_full.delta.add(0, EPSILON, 23); +// expected_full.delta.add(23, EPSILON, 24); +// expected_full.delta.add(24, 'j', 25); +// expected_full.delta.add(25, 'l', 26); +// expected_full.delta.add(26, EPSILON, 27); +// expected_full.delta.add(27, EPSILON, 28); +// expected_full.delta.add(28, 'h', 29); +// expected_full.delta.add(29, 'a', 30); +// expected_full.delta.add(30, 'b', 31); +// expected_full.delta.add(31, 'o', 32); +// expected_full.delta.add(32, 'p', 33); +// expected_full.delta.add(33, 'y', 34); +// expected_full.delta.add(34, 'h', 36); +// expected_full.delta.add(36, 'q', 22); +// Nft expected_proj = project_out(expected_full, { 4 }, JumpMode::NoJump); +// } +// SECTION("RHS | LHS") { +// Nft expected_full(41, { 0 }, { 26, 40 }, { 0, 1, 2, 3, 4, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0 }, 7); +// expected_full.delta.add(0, 'j', 1); +// expected_full.delta.add(1, 'k', 2); +// expected_full.delta.add(2, 'a', 3); +// expected_full.delta.add(3, 'b', 4); +// expected_full.delta.add(1, 'l', 5); +// expected_full.delta.add(5, 'a', 6); +// expected_full.delta.add(6, 'c', 7); +// expected_full.delta.add(7, EPSILON, 8); +// expected_full.delta.add(8, 'n', 9); +// expected_full.delta.add(9, 'e', 10); +// expected_full.delta.add(10, 'o', 11); +// expected_full.delta.add(11, 'p', 12); +// expected_full.delta.add(5, 'f', 13); +// expected_full.delta.add(13, 'g', 14); +// expected_full.delta.add(5, EPSILON, 15); +// expected_full.delta.add(15, EPSILON, 16); +// expected_full.delta.add(16, EPSILON, 17); +// expected_full.delta.add(17, 'n', 18); +// expected_full.delta.add(18, EPSILON, 19); +// expected_full.delta.add(19, 'o', 20); +// expected_full.delta.add(20, 'p', 21); +// expected_full.delta.add(21, 'a', 22); +// expected_full.delta.add(22, 'b', 23); +// expected_full.delta.add(23, 'y', 24); +// expected_full.delta.add(24, 'q', 25); +// expected_full.delta.add(25, 'd', 26); +// expected_full.delta.add(0, EPSILON, 27); +// expected_full.delta.add(27, EPSILON, 28); +// expected_full.delta.add(28, 'a', 29); +// expected_full.delta.add(29, 'c', 30); +// expected_full.delta.add(30, EPSILON, 31); +// expected_full.delta.add(31, EPSILON, 32); +// expected_full.delta.add(32, 'e', 33); +// expected_full.delta.add(33, 'j', 34); +// expected_full.delta.add(34, 'k', 35); +// expected_full.delta.add(35, 'f', 36); +// expected_full.delta.add(36, 'g', 37); +// expected_full.delta.add(37, 'z', 38); +// expected_full.delta.add(38, 'm', 39); +// expected_full.delta.add(39, 'h', 40); +// } +// } + // TEST_CASE("nft::compose_fast()") { // SECTION("project_out == true") { // SECTION("two levels easy match") { @@ -2318,7 +2592,6 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { // lhs.delta.add(3, 'd', 4); // lhs.delta.add(3, 'e', 0); // lhs.delta.add(3, 'f', 0); -// lhs.print_to_dot(std::string("lhs.dot"), true, true); // Nft rhs; // rhs.num_of_levels = 4; @@ -2332,10 +2605,8 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { // rhs.delta.add(2, 'j', 3); // rhs.delta.add(3, 'y', 4); // rhs.delta.add(3, 'y', 0); -// rhs.print_to_dot(std::string("rhs.dot"), true, true); // Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, false, true, JumpMode::NoJump); -// result.print_to_dot(std::string("result.dot"), true, true); // Nft expected; // expected.num_of_levels = 6; @@ -2354,7 +2625,6 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { // expected.delta.add(6, 'd', 4); // expected.delta.add(6, 'e', 0); // expected.delta.add(6, 'f', 0); -// expected.print_to_dot(std::string("expected.dot"), true, true); // CHECK(result.num_of_levels == 6); // CHECK(are_equivalent(result, expected)); From ac21b101a15854ea3df50deb89629d0b7617f511 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 11 Sep 2025 11:28:25 +0200 Subject: [PATCH 49/65] new tests done --- src/nft/composition.cc | 4 +- tests/nft/nft-composition.cc | 459 +++++++++++++++++++++++++---------- 2 files changed, 329 insertions(+), 134 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index bef5d19c3..058021302 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -772,7 +772,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le StateSet filtered_lhs_fast_eps_targets{ lhs_src }; StateSet filtered_rhs_fast_eps_targets{ rhs_src }; - if (lhs_eps_exists) { + if (lhs_eps_exists && lhs.num_of_levels != 1) { std::copy_if( lhs_eps_post_it->targets.cbegin(), lhs_eps_post_it->targets.cend(), @@ -780,7 +780,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le [&](State s) { return lhs.levels[s] == 0; } ); } - if (rhs_eps_exists) { + if (rhs_eps_exists && rhs.num_of_levels != 1) { std::copy_if( rhs_eps_post_it->targets.cbegin(), rhs_eps_post_it->targets.cend(), diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 3762cda8c..259f82d78 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -8,8 +8,6 @@ #include "mata/utils/ord-vector.hh" -#define SKIP_TESTS - using namespace mata::nft; using namespace mata::utils; @@ -565,7 +563,6 @@ TEST_CASE("Mata::nft::compose()") { } } -#ifndef SKIP_TESTS TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { SECTION("2 levels x 2 levels") { Nft expected_full(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); @@ -685,13 +682,8 @@ TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { CHECK(are_equivalent(result_full, expected_full)); } } -#endif - - - TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { -#ifndef SKIP_TESTS SECTION("sync level 0 x sync level 0") { Nft lhs(5, { 0 }, { 4 }, { 0, 1, 0, 1, 0 }, 2); lhs.delta.add(0, 'a', 1); @@ -1407,11 +1399,9 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { CHECK(are_equivalent(result_proj, expected_proj)); } } -#endif } TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { -#ifndef SKIP_TESTS SECTION("sync level 0 x sync level 0") { Nft lhs(7, { 0 }, { 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); lhs.delta.add(0, EPSILON, 1); @@ -2043,11 +2033,9 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { CHECK(are_equivalent(result_proj, expected_proj)); } } - #endif } TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { -#ifndef SKIP_TESTS SECTION("3 levels x 3 levels + fast EPSILON on LHS") { Nft lhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); lhs.delta.add(0, EPSILON, 1); @@ -2167,7 +2155,7 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { CHECK(are_equivalent(result_proj, expected_proj)); } } -#endif + SECTION("3 levels x 1 level") { Nft lhs(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); lhs.delta.add(0, 'a', 1); @@ -2177,16 +2165,26 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { Nft rhs(2, { 0 }, { 1 }, { 0, 0 }, 1); rhs.delta.add(0, EPSILON, 1); - Nft expected_full(4, { 0 }, { 3 }, { 0, 1, 2, 0 }, 3); + Nft expected_full(14, { 0 }, { 3 }, { 0, 1, 2, 0, 1, 2, 0, 1, 2, 1, 2, 0, 1, 2 }, 3); expected_full.delta.add(0, 'a', 1); expected_full.delta.add(1, EPSILON, 2); expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(0, EPSILON, 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, EPSILON, 6); + expected_full.delta.add(6, 'a', 7); + expected_full.delta.add(7, EPSILON, 8); + expected_full.delta.add(8, 'b', 3); + expected_full.delta.add(0, 'a', 9); + expected_full.delta.add(9, EPSILON, 10); + expected_full.delta.add(10, 'b', 11); + expected_full.delta.add(11, EPSILON, 12); + expected_full.delta.add(12, EPSILON, 13); + expected_full.delta.add(13, EPSILON, 3); Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); - expected_full.print_to_dot(std::string("expected.dot"), true); - result_full.print_to_dot(std::string("result.dot"), true); CHECK(result_full.final.size() == expected_full.final.size()); CHECK(result_full.num_of_states() == expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); @@ -2200,125 +2198,322 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); CHECK(are_equivalent(result_proj, expected_proj)); } + SECTION("RHS | LHS") { + Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); + CHECK(result_full.final.size() == expected_full.final.size()); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); + CHECK(result_proj.final.size() == expected_proj.final.size()); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } } + SECTION("3 levels x 1 level (long)") { + Nft lhs(7, { 0 }, { 3, 6 }, { 0, 1, 2, 0, 1, 2, 0 }, 3); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, EPSILON, 2); + lhs.delta.add(2, 'b', 3); + lhs.delta.add(3, 'c', 4); + lhs.delta.add(4, EPSILON, 5); + lhs.delta.add(5, 'd', 6); + + Nft rhs(2, { 0 }, { 1 }, { 0, 0 }, 1); + rhs.delta.add(0, EPSILON, 1); + + Nft expected_full(24, { 0 }, { 3, 6 }, { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 1, 2, 0, 1, 2, 1, 2, 1, 2, 0, 1, 2 }, 3); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, EPSILON, 2); + expected_full.delta.add(2, 'b', 3); + expected_full.delta.add(3, 'c', 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(0, EPSILON, 7); + expected_full.delta.add(7, EPSILON, 8); + expected_full.delta.add(8, EPSILON, 9); + expected_full.delta.add(9, 'a', 10); + expected_full.delta.add(10, EPSILON, 11); + expected_full.delta.add(11, 'b', 3); + expected_full.delta.add(3, 'c', 4); + expected_full.delta.add(4, EPSILON, 5); + expected_full.delta.add(5, 'd', 6); + expected_full.delta.add(0, 'a', 12); + expected_full.delta.add(12, EPSILON, 13); + expected_full.delta.add(13, 'b', 14); + expected_full.delta.add(14, EPSILON, 15); + expected_full.delta.add(15, EPSILON, 16); + expected_full.delta.add(16, EPSILON, 3); + expected_full.delta.add(14, 'c', 17); + expected_full.delta.add(17, EPSILON, 18); + expected_full.delta.add(18, 'd', 6); + expected_full.delta.add(14, 'c', 19); + expected_full.delta.add(19, EPSILON, 20); + expected_full.delta.add(20, 'd', 21); + expected_full.delta.add(21, EPSILON, 22); + expected_full.delta.add(22, EPSILON, 23); + expected_full.delta.add(23, EPSILON, 6); + Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); + + SECTION("LHS | RHS") { + Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); + CHECK(result_full.final.size() == expected_full.final.size()); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); + CHECK(result_proj.final.size() == expected_proj.final.size()); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + + SECTION("RHS | LHS") { + Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); + CHECK(result_full.final.size() == expected_full.final.size()); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); + CHECK(result_proj.final.size() == expected_proj.final.size()); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + } } -// TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { -// Nft lhs(12, { 0 }, { 6, 7, 11 }, { 0, 1, 2, 2, 3, 3, 0, 0, 1, 2, 3, 0 }, 4); -// lhs.delta.add(0, 'a', 1); -// lhs.delta.add(1, 'b', 2); -// lhs.delta.add(1, 'c', 3); -// lhs.delta.add(2, 'y', 4); -// lhs.delta.add(3, EPSILON, 5); -// lhs.delta.add(4, 'd', 6); -// lhs.delta.add(5, 'e', 7); -// lhs.delta.add(2, 'f', 8); -// lhs.delta.add(3, 'g', 9); -// lhs.delta.add(8, 'z', 10); -// lhs.delta.add(9, 'h', 11); - -// Nft rhs(12, { 0 }, { 6, 7, 11 }, { 0, 1, 2, 2, 3, 3, 0, 0, 1, 2, 3, 0 }, 4); -// rhs.delta.add(0, 'j', 1); -// rhs.delta.add(1, 'k', 2); -// rhs.delta.add(1, 'l', 3); -// rhs.delta.add(2, 'z', 4); -// rhs.delta.add(3, EPSILON, 5); -// rhs.delta.add(4, 'm', 6); -// rhs.delta.add(5, 'n', 7); -// rhs.delta.add(7, 'o', 8); -// rhs.delta.add(8, 'p', 9); -// rhs.delta.add(9, 'y', 10); -// rhs.delta.add(10, 'q', 11); - -// SECTION("LHS | RHS") { -// Nft expected_full(41, { 0 }, { 22, 36 }, { 0, 1, 2, 3, 4, 2, 3, 4, 5, 6, 0, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4 }, 7); -// expected_full.delta.add(0, 'a', 1); -// expected_full.delta.add(1, 'b', 2); -// expected_full.delta.add(1, 'c', 5); -// expected_full.delta.add(2, 'j', 3); -// expected_full.delta.add(3, 'k', 4); -// expected_full.delta.add(5, 'j', 6); -// expected_full.delta.add(6, 'l', 7); -// expected_full.delta.add(7, EPSILON, 8); -// expected_full.delta.add(8, 'e', 9); -// expected_full.delta.add(9, 'n', 10); -// expected_full.delta.add(10, 'f', 37); -// expected_full.delta.add(37, 'g', 38); -// expected_full.delta.add(38, 'o', 39); -// expected_full.delta.add(39, 'p', 40); -// expected_full.delta.add(5, EPSILON, 11); -// expected_full.delta.add(11, EPSILON, 12); -// expected_full.delta.add(12, EPSILON, 13); -// expected_full.delta.add(13, 'e', 14); -// expected_full.delta.add(14, EPSILON, 15); -// expected_full.delta.add(15, 'f', 16); -// expected_full.delta.add(16, 'g', 17); -// expected_full.delta.add(17, 'j', 18); -// expected_full.delta.add(18, 'k', 19); -// expected_full.delta.add(19, 'z', 20); -// expected_full.delta.add(20, 'h', 21); -// expected_full.delta.add(21, 'm', 22); -// expected_full.delta.add(0, EPSILON, 23); -// expected_full.delta.add(23, EPSILON, 24); -// expected_full.delta.add(24, 'j', 25); -// expected_full.delta.add(25, 'l', 26); -// expected_full.delta.add(26, EPSILON, 27); -// expected_full.delta.add(27, EPSILON, 28); -// expected_full.delta.add(28, 'h', 29); -// expected_full.delta.add(29, 'a', 30); -// expected_full.delta.add(30, 'b', 31); -// expected_full.delta.add(31, 'o', 32); -// expected_full.delta.add(32, 'p', 33); -// expected_full.delta.add(33, 'y', 34); -// expected_full.delta.add(34, 'h', 36); -// expected_full.delta.add(36, 'q', 22); -// Nft expected_proj = project_out(expected_full, { 4 }, JumpMode::NoJump); -// } -// SECTION("RHS | LHS") { -// Nft expected_full(41, { 0 }, { 26, 40 }, { 0, 1, 2, 3, 4, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0 }, 7); -// expected_full.delta.add(0, 'j', 1); -// expected_full.delta.add(1, 'k', 2); -// expected_full.delta.add(2, 'a', 3); -// expected_full.delta.add(3, 'b', 4); -// expected_full.delta.add(1, 'l', 5); -// expected_full.delta.add(5, 'a', 6); -// expected_full.delta.add(6, 'c', 7); -// expected_full.delta.add(7, EPSILON, 8); -// expected_full.delta.add(8, 'n', 9); -// expected_full.delta.add(9, 'e', 10); -// expected_full.delta.add(10, 'o', 11); -// expected_full.delta.add(11, 'p', 12); -// expected_full.delta.add(5, 'f', 13); -// expected_full.delta.add(13, 'g', 14); -// expected_full.delta.add(5, EPSILON, 15); -// expected_full.delta.add(15, EPSILON, 16); -// expected_full.delta.add(16, EPSILON, 17); -// expected_full.delta.add(17, 'n', 18); -// expected_full.delta.add(18, EPSILON, 19); -// expected_full.delta.add(19, 'o', 20); -// expected_full.delta.add(20, 'p', 21); -// expected_full.delta.add(21, 'a', 22); -// expected_full.delta.add(22, 'b', 23); -// expected_full.delta.add(23, 'y', 24); -// expected_full.delta.add(24, 'q', 25); -// expected_full.delta.add(25, 'd', 26); -// expected_full.delta.add(0, EPSILON, 27); -// expected_full.delta.add(27, EPSILON, 28); -// expected_full.delta.add(28, 'a', 29); -// expected_full.delta.add(29, 'c', 30); -// expected_full.delta.add(30, EPSILON, 31); -// expected_full.delta.add(31, EPSILON, 32); -// expected_full.delta.add(32, 'e', 33); -// expected_full.delta.add(33, 'j', 34); -// expected_full.delta.add(34, 'k', 35); -// expected_full.delta.add(35, 'f', 36); -// expected_full.delta.add(36, 'g', 37); -// expected_full.delta.add(37, 'z', 38); -// expected_full.delta.add(38, 'm', 39); -// expected_full.delta.add(39, 'h', 40); -// } -// } +TEST_CASE("nft::composition(..., Level, Level, ...) - complex") { + Nft lhs(25, { 0 }, { 12, 14, 19, 24 }, { 0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 0, 0, 0, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0 }, 5); + lhs.delta.add(0, 'a', 1); + lhs.delta.add(1, 'b', 2); + lhs.delta.add(1, 'c', 3); + lhs.delta.add(2, 'd', 4); + lhs.delta.add(2, 'e', 5); + lhs.delta.add(3, 'f', 6); + lhs.delta.add(4, EPSILON, 7); + lhs.delta.add(5, 'b', 8); + lhs.delta.add(5, EPSILON, 9); + lhs.delta.add(6, 'y', 10); + lhs.delta.add(7, 'g', 11); + lhs.delta.add(8, 'h', 12); + lhs.delta.add(9, 'i', 13); + lhs.delta.add(10, 'j', 14); + lhs.delta.add(11, 'k', 15); + lhs.delta.add(13, 'l', 20); + lhs.delta.add(15, 'm', 16); + lhs.delta.add(16, 'o', 17); + lhs.delta.add(17, 'x', 18); + lhs.delta.add(18, 'q', 19); + lhs.delta.add(20, 'n', 21); + lhs.delta.add(21, 'p', 22); + lhs.delta.add(22, 'y', 23); + lhs.delta.add(23, 'r', 24); + + Nft rhs(15, { 0 }, { 11, 12, 13, 14 }, { 0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 0, 0, 0, 0 }, 5); + rhs.delta.add(0, 's', 1); + rhs.delta.add(1, 't', 2); + rhs.delta.add(1, 'u', 3); + rhs.delta.add(2, 'v', 4); + rhs.delta.add(3, 'w', 5); + rhs.delta.add(3, 'x', 6); + rhs.delta.add(4, 'a', 7); + rhs.delta.add(5, 'b', 8); + rhs.delta.add(6, 'x', 9); + rhs.delta.add(6, 'y', 10); + rhs.delta.add(7, 'y', 11); + rhs.delta.add(8, 'z', 12); + rhs.delta.add(9, 'a', 13); + rhs.delta.add(10, 'b', 14); + + SECTION("LHS | RHS") { + Nft expected_full(64, { 0 }, { 12, 22, 44, 63 }, { 0, 1, 2, 3, 4, 5, 6, 5, 6, 6, 7, 8, 0, 2, 3, 4, 5, 6, 5, 6, 7, 8, 0, 6, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 5, 6, 6, 7, 8, 0, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 5, 6, 6, 7, 8, 0 }, 9); + expected_full.delta.add(0, 'a', 1); + expected_full.delta.add(1, 'c', 2); + expected_full.delta.add(1, 'b', 13); + expected_full.delta.add(2, 'f', 3); + expected_full.delta.add(3, 's', 4); + expected_full.delta.add(4, 't', 5); + expected_full.delta.add(5, 'v', 6); + expected_full.delta.add(4, 'u', 7); + expected_full.delta.add(7, 'w', 8); + expected_full.delta.add(7, 'x', 9); + expected_full.delta.add(9, 'y', 10); + expected_full.delta.add(10, 'j', 11); + expected_full.delta.add(11, 'b', 12); + expected_full.delta.add(13, 'e', 14); + expected_full.delta.add(14, 's', 15); + expected_full.delta.add(15, 't', 16); + expected_full.delta.add(16, 'v', 17); + expected_full.delta.add(15, 'u', 18); + expected_full.delta.add(18, 'w', 19); + expected_full.delta.add(19, 'b', 20); + expected_full.delta.add(20, 'h', 21); + expected_full.delta.add(21, 'z', 22); + expected_full.delta.add(18, 'x', 23); + expected_full.delta.add(0, 'a', 24); + expected_full.delta.add(24, 'b', 25); + expected_full.delta.add(25, 'd', 26); + expected_full.delta.add(26, EPSILON, 27); + expected_full.delta.add(27, EPSILON, 28); + expected_full.delta.add(28, EPSILON, 29); + expected_full.delta.add(29, EPSILON, 30); + expected_full.delta.add(30, 'g', 31); + expected_full.delta.add(31, EPSILON, 32); + expected_full.delta.add(32, 'k', 33); + expected_full.delta.add(33, 'm', 34); + expected_full.delta.add(34, 'o', 35); + expected_full.delta.add(35, 's', 36); + expected_full.delta.add(36, 't', 37); + expected_full.delta.add(37, 'v', 38); + expected_full.delta.add(36, 'u', 39); + expected_full.delta.add(39, 'w', 40); + expected_full.delta.add(39, 'x', 41); + expected_full.delta.add(41, 'x', 42); + expected_full.delta.add(42, 'q', 43); + expected_full.delta.add(43, 'a', 44); + expected_full.delta.add(25, 'e', 45); + expected_full.delta.add(45, EPSILON, 46); + expected_full.delta.add(46, EPSILON, 47); + expected_full.delta.add(47, EPSILON, 48); + expected_full.delta.add(48, EPSILON, 49); + expected_full.delta.add(49, 'i', 50); + expected_full.delta.add(50, EPSILON, 51); + expected_full.delta.add(51, 'l', 52); + expected_full.delta.add(52, 'n', 53); + expected_full.delta.add(53, 'p', 54); + expected_full.delta.add(54, 's', 55); + expected_full.delta.add(55, 't', 56); + expected_full.delta.add(56, 'v', 57); + expected_full.delta.add(55, 'u', 58); + expected_full.delta.add(58, 'w', 59); + expected_full.delta.add(58, 'x', 60); + expected_full.delta.add(60, 'y', 61); + expected_full.delta.add(61, 'r', 62); + expected_full.delta.add(62, 'b', 63); + Nft expected_proj = project_out(expected_full, { 6 }, JumpMode::NoJump); + + Nft result_full = compose(lhs, rhs, 3, 3, false, JumpMode::NoJump); + CHECK(result_full.final.size() == expected_full.final.size()); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(lhs, rhs, 3, 3, true, JumpMode::NoJump).trim(); + CHECK(result_proj.final.size() == expected_proj.final.size()); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } + + SECTION("RHS | LHS") { + Nft expected_full(77, { 0 }, { 18, 27, 54, 76 }, { 0, 1, 2, 3, 4, 5, 6, 5, 6, 2, 3, 4, 5, 6, 5, 6, 7, 8, 0, 3, 4, 5, 5, 6, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 8, 0, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 8, 0 }, 9); + expected_full.delta.add(0, 's', 1); + expected_full.delta.add(1, 't', 2); + expected_full.delta.add(2, 'v', 3); + expected_full.delta.add(3, 'a', 4); + expected_full.delta.add(4, 'c', 5); + expected_full.delta.add(5, 'f', 6); + expected_full.delta.add(4, 'b', 7); + expected_full.delta.add(7, 'e', 8); + expected_full.delta.add(1, 'u', 9); + expected_full.delta.add(9, 'w', 10); + expected_full.delta.add(10, 'a', 11); + expected_full.delta.add(11, 'c', 12); + expected_full.delta.add(12, 'f', 13); + expected_full.delta.add(11, 'b', 14); + expected_full.delta.add(14, 'e', 15); + expected_full.delta.add(15, 'b', 16); + expected_full.delta.add(16, 'z', 17); + expected_full.delta.add(17, 'h', 18); + expected_full.delta.add(9, 'x', 19); + expected_full.delta.add(19, 'a', 20); + expected_full.delta.add(20, 'b', 21); + expected_full.delta.add(21, 'e', 23); + expected_full.delta.add(20, 'c', 22); + expected_full.delta.add(22, 'f', 24); + expected_full.delta.add(24, 'y', 25); + expected_full.delta.add(25, 'b', 26); + expected_full.delta.add(26, 'j', 27); + expected_full.delta.add(0, EPSILON, 28); + expected_full.delta.add(28, EPSILON, 29); + expected_full.delta.add(29, EPSILON, 30); + expected_full.delta.add(30, 'a', 31); + expected_full.delta.add(31, 'b', 32); + expected_full.delta.add(32, 'd', 33); + expected_full.delta.add(33, EPSILON, 34); + expected_full.delta.add(34, EPSILON, 35); + expected_full.delta.add(35, 'g', 36); + expected_full.delta.add(36, 's', 37); + expected_full.delta.add(37, 't', 38); + expected_full.delta.add(38, 'v', 39); + expected_full.delta.add(39, 'k', 40); + expected_full.delta.add(40, 'm', 41); + expected_full.delta.add(41, 'o', 42); + expected_full.delta.add(37, 'u', 43); + expected_full.delta.add(43, 'w', 44); + expected_full.delta.add(44, 'k', 45); + expected_full.delta.add(45, 'm', 46); + expected_full.delta.add(46, 'o', 47); + expected_full.delta.add(43, 'x', 48); + expected_full.delta.add(48, 'k', 49); + expected_full.delta.add(49, 'm', 50); + expected_full.delta.add(50, 'o', 51); + expected_full.delta.add(51, 'x', 52); + expected_full.delta.add(52, 'a', 53); + expected_full.delta.add(53, 'q', 54); + expected_full.delta.add(32, 'e', 55); + expected_full.delta.add(55, EPSILON, 56); + expected_full.delta.add(56, EPSILON, 57); + expected_full.delta.add(57, 'i', 58); + expected_full.delta.add(58, 's', 59); + expected_full.delta.add(59, 't', 60); + expected_full.delta.add(60, 'v', 61); + expected_full.delta.add(61, 'l', 62); + expected_full.delta.add(62, 'n', 63); + expected_full.delta.add(63, 'p', 64); + expected_full.delta.add(59, 'u', 65); + expected_full.delta.add(65, 'w', 66); + expected_full.delta.add(66, 'l', 67); + expected_full.delta.add(67, 'n', 68); + expected_full.delta.add(68, 'p', 69); + expected_full.delta.add(65, 'x', 70); + expected_full.delta.add(70, 'l', 71); + expected_full.delta.add(71, 'n', 72); + expected_full.delta.add(72, 'p', 73); + expected_full.delta.add(73, 'y', 74); + expected_full.delta.add(74, 'b', 75); + expected_full.delta.add(75, 'r', 76); + Nft expected_proj = project_out(expected_full, { 6 }, JumpMode::NoJump); + + Nft result_full = compose(rhs, lhs, 3, 3, false, JumpMode::NoJump); + CHECK(result_full.final.size() == expected_full.final.size()); + CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_levels == expected_full.num_of_levels); + CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(are_equivalent(result_full, expected_full)); + + Nft result_proj = compose(rhs, lhs, 3, 3, true, JumpMode::NoJump).trim(); + CHECK(result_proj.final.size() == expected_proj.final.size()); + CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); + CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(are_equivalent(result_proj, expected_proj)); + } +} // TEST_CASE("nft::compose_fast()") { // SECTION("project_out == true") { @@ -2330,7 +2525,7 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { // lhs.final.insert(4); // lhs.delta.add(0, 'x', 1); // lhs.delta.add(1, 'a', 2); -// lhs.delta.add(1, 'b', 2); +// lhs.delta.add(1, 'b', 2); // lhs.delta.add(2, 'y', 3); // lhs.delta.add(3, 'c', 4); // lhs.delta.add(3, 'd', 4); From 9aa81c17b9db9cb8ea749b9be641c0f47025c9dc Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 11 Sep 2025 11:32:29 +0200 Subject: [PATCH 50/65] remove old tests --- tests/nft/nft-composition.cc | 431 ----------------------------------- 1 file changed, 431 deletions(-) diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index 259f82d78..bd4463cb9 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -2514,434 +2514,3 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - complex") { CHECK(are_equivalent(result_proj, expected_proj)); } } - -// TEST_CASE("nft::compose_fast()") { - // SECTION("project_out == true") { - // SECTION("two levels easy match") { - // Nft lhs; - // lhs.num_of_levels = 2; -// lhs.levels = { 0, 1, 0, 1, 0 }; -// lhs.initial.insert(0); -// lhs.final.insert(4); -// lhs.delta.add(0, 'x', 1); -// lhs.delta.add(1, 'a', 2); -// lhs.delta.add(1, 'b', 2); -// lhs.delta.add(2, 'y', 3); -// lhs.delta.add(3, 'c', 4); -// lhs.delta.add(3, 'd', 4); - -// Nft rhs; -// rhs.num_of_levels = 2; -// rhs.levels = { 0, 1, 0, 1, 0 }; -// rhs.initial.insert(0); -// rhs.final.insert(4); -// rhs.delta.add(0, 'x', 1); -// rhs.delta.add(1, 'e', 2); -// rhs.delta.add(1, 'f', 2); -// rhs.delta.add(2, 'y', 3); -// rhs.delta.add(3, 'g', 4); -// rhs.delta.add(3, 'h', 4); - -// Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, true, true, JumpMode::NoJump); - -// Nft expected; -// expected.num_of_levels = 2; -// expected.levels = { 0, 1, 0, 1, 0 }; -// expected.initial.insert(0); -// expected.final.insert(4); -// expected.delta.add(0, 'a', 1); -// expected.delta.add(0, 'b', 1); -// expected.delta.add(1, 'e', 2); -// expected.delta.add(1, 'f', 2); -// expected.delta.add(2, 'c', 3); -// expected.delta.add(2, 'd', 3); -// expected.delta.add(3, 'g', 4); -// expected.delta.add(3, 'h', 4); - -// CHECK(result.num_of_levels == 2); -// CHECK(are_equivalent(result, expected)); -// } - -// SECTION("four levels with loop") { -// Nft lhs; -// lhs.num_of_levels = 4; -// lhs.levels = { 0, 1, 2, 3, 0 }; -// lhs.initial.insert(0); -// lhs.final.insert(4); -// lhs.delta.add(0, 'x', 1); -// lhs.delta.add(1, 'a', 2); -// lhs.delta.add(1, 'b', 2); -// lhs.delta.add(2, 'y', 3); -// lhs.delta.add(3, 'c', 4); -// lhs.delta.add(3, 'd', 4); -// lhs.delta.add(3, 'e', 0); -// lhs.delta.add(3, 'f', 0); - -// Nft rhs; -// rhs.num_of_levels = 4; -// rhs.levels = { 0, 1, 2, 3, 0 }; -// rhs.initial.insert(0); -// rhs.final.insert(4); -// rhs.delta.add(0, 'g', 1); -// rhs.delta.add(0, 'h', 1); -// rhs.delta.add(1, 'x', 2); -// rhs.delta.add(2, 'i', 3); -// rhs.delta.add(2, 'j', 3); -// rhs.delta.add(3, 'y', 4); -// rhs.delta.add(3, 'y', 0); - -// Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, true, true, JumpMode::NoJump); - -// Nft expected; -// expected.num_of_levels = 4; -// expected.levels = { 0, 1, 2, 3, 0 }; -// expected.initial.insert(0); -// expected.final.insert(4); -// expected.delta.add(0, 'g', 1); -// expected.delta.add(0, 'h', 1); -// expected.delta.add(1, 'a', 2); -// expected.delta.add(1, 'b', 2); -// expected.delta.add(2, 'i', 3); -// expected.delta.add(2, 'j', 3); -// expected.delta.add(3, 'c', 4); -// expected.delta.add(3, 'd', 4); -// expected.delta.add(3, 'e', 0); -// expected.delta.add(3, 'f', 0); - -// CHECK(result.num_of_levels == 4); -// CHECK(are_equivalent(result, expected)); -// } - -// SECTION("synchronization of lst two levels with one mismatch") { -// Nft lhs; -// lhs.num_of_levels = 3; -// lhs.levels = { 0, 1, 2 }; -// lhs.initial.insert(0); -// lhs.final.insert(0); -// lhs.delta.add(0, 'u', 1); -// lhs.delta.add(0, 'v', 1); -// lhs.delta.add(1, 'x', 2); -// lhs.delta.add(2, 'y', 0); - -// Nft rhs; -// rhs.num_of_levels = 4; -// rhs.levels = { 0, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0}; -// rhs.initial.insert(0); -// rhs.final.insert(7); -// rhs.final.insert(14); -// rhs.final.insert(15); -// rhs.delta.add(0, 'a', 1); -// rhs.delta.add(0, 'b', 1); -// rhs.delta.add(1, 'c', 2); -// rhs.delta.add(1, 'd', 2); -// rhs.delta.add(1, 'c', 3); -// rhs.delta.add(1, 'd', 3); -// rhs.delta.add(2, 'x', 4); -// rhs.delta.add(4, 'y', 6); -// rhs.delta.add(6, 'e', 8); -// rhs.delta.add(8, 'f', 10); -// rhs.delta.add(10, 'x', 12); -// rhs.delta.add(12, 'y', 14); -// rhs.delta.add(3, 'x', 5); -// rhs.delta.add(5, 'z', 7); -// rhs.delta.add(7, 'g', 9); -// rhs.delta.add(9, 'h', 11); -// rhs.delta.add(11, 'x', 13); -// rhs.delta.add(13, 'y', 15); - -// Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, true, true, JumpMode::NoJump); - -// Nft expected; -// expected.num_of_levels = 3; -// expected.levels = { 0, 1, 2, 0, 1, 2, 0 }; -// expected.initial.insert(0); -// expected.final.insert(6); -// expected.delta.add(0, 'u', 1); -// expected.delta.add(0, 'v', 1); -// expected.delta.add(1, 'a', 2); -// expected.delta.add(1, 'b', 2); -// expected.delta.add(2, 'c', 3); -// expected.delta.add(2, 'd', 3); -// expected.delta.add(3, 'u', 4); -// expected.delta.add(3, 'v', 4); -// expected.delta.add(4, 'e', 5); -// expected.delta.add(5, 'f', 6); - -// CHECK(result.num_of_levels == 3); -// CHECK(are_equivalent(result, expected)); -// } - -// SECTION("epsilon on synchronization") { -// Nft lhs; -// lhs.num_of_levels = 3; -// lhs.levels = { 0, 1, 2, 0 }; -// lhs.initial.insert(0); -// lhs.final.insert(3); -// lhs.delta.add(0, 'u', 1); -// lhs.delta.add(0, 'v', 1); -// lhs.delta.add(1, 'x', 2); -// lhs.delta.add(2, 'y', 3); - -// Nft rhs; -// rhs.num_of_levels = 3; -// rhs.levels = { 0, 1, 2, 2, 0, 2, 0, 1, 1, 2, 2, 0, 0 }; -// rhs.initial.insert(0); -// rhs.final.insert(11); -// rhs.final.insert(12); -// rhs.delta.add(0, 'a', 1); -// rhs.delta.add(0, 'b', 1); -// rhs.delta.add(1, 'c', 2); -// rhs.delta.add(1, EPSILON, 3); -// rhs.delta.add(1, EPSILON, 5); -// rhs.delta.add(2, EPSILON, 4); -// rhs.delta.add(3, EPSILON, 4); -// rhs.delta.add(4, 'e', 7); -// rhs.delta.add(4, 'f', 7); -// rhs.delta.add(7, 'x', 9); -// rhs.delta.add(9, 'y', 11); -// rhs.delta.add(5, 'd', 6); -// rhs.delta.add(6, 'g', 8); -// rhs.delta.add(8, 'x', 10); -// rhs.delta.add(10, 'y', 12); - -// Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, true, true, JumpMode::NoJump); - -// Nft expected; -// expected.num_of_levels = 2; -// expected.levels = { 0, 1, 0, 1, 0 }; -// expected.initial.insert(0); -// expected.final.insert(4); -// expected.delta.add(0, EPSILON, 1); -// expected.delta.add(1, 'a', 2); -// expected.delta.add(1, 'b', 2); -// expected.delta.add(2, 'u', 3); -// expected.delta.add(2, 'v', 3); -// expected.delta.add(3, 'e', 4); -// expected.delta.add(3, 'f', 4); - -// CHECK(result.num_of_levels == 2); -// CHECK(are_equivalent(result, expected)); -// } -// } - -// SECTION("project_out == false") { -// SECTION("two levels easy match") { -// Nft lhs; -// lhs.num_of_levels = 2; -// lhs.levels = { 0, 1, 0, 1, 0 }; -// lhs.initial.insert(0); -// lhs.final.insert(4); -// lhs.delta.add(0, 'x', 1); -// lhs.delta.add(1, 'a', 2); -// lhs.delta.add(1, 'b', 2); -// lhs.delta.add(2, 'y', 3); -// lhs.delta.add(3, 'c', 4); -// lhs.delta.add(3, 'd', 4); - -// Nft rhs; -// rhs.num_of_levels = 2; -// rhs.levels = { 0, 1, 0, 1, 0 }; -// rhs.initial.insert(0); -// rhs.final.insert(4); -// rhs.delta.add(0, 'x', 1); -// rhs.delta.add(1, 'e', 2); -// rhs.delta.add(1, 'f', 2); -// rhs.delta.add(2, 'y', 3); -// rhs.delta.add(3, 'g', 4); -// rhs.delta.add(3, 'h', 4); - -// Nft result = compose_fast(lhs, rhs, { 0 }, { 0 }, false, true, JumpMode::NoJump); - -// Nft expected; -// expected.num_of_levels = 3; -// expected.levels = { 0, 2, 0, 2, 0, 1, 1 }; -// expected.initial.insert(0); -// expected.final.insert(4); -// expected.delta.add(0, 'x', 5); -// expected.delta.add(5, 'a', 1); -// expected.delta.add(5, 'b', 1); -// expected.delta.add(1, 'e', 2); -// expected.delta.add(1, 'f', 2); -// expected.delta.add(2, 'y', 6); -// expected.delta.add(6, 'c', 3); -// expected.delta.add(6, 'd', 3); -// expected.delta.add(3, 'g', 4); -// expected.delta.add(3, 'h', 4); - -// CHECK(result.num_of_levels == 3); -// CHECK(are_equivalent(result, expected)); -// } - - -// SECTION("four levels with loop") { -// Nft lhs; -// lhs.num_of_levels = 4; -// lhs.levels = { 0, 1, 2, 3, 0 }; -// lhs.initial.insert(0); -// lhs.final.insert(4); -// lhs.delta.add(0, 'x', 1); -// lhs.delta.add(1, 'a', 2); -// lhs.delta.add(1, 'b', 2); -// lhs.delta.add(2, 'y', 3); -// lhs.delta.add(3, 'c', 4); -// lhs.delta.add(3, 'd', 4); -// lhs.delta.add(3, 'e', 0); -// lhs.delta.add(3, 'f', 0); - -// Nft rhs; -// rhs.num_of_levels = 4; -// rhs.levels = { 0, 1, 2, 3, 0 }; -// rhs.initial.insert(0); -// rhs.final.insert(4); -// rhs.delta.add(0, 'g', 1); -// rhs.delta.add(0, 'h', 1); -// rhs.delta.add(1, 'x', 2); -// rhs.delta.add(2, 'i', 3); -// rhs.delta.add(2, 'j', 3); -// rhs.delta.add(3, 'y', 4); -// rhs.delta.add(3, 'y', 0); - -// Nft result = compose_fast(lhs, rhs, { 0, 2 }, { 1, 3 }, false, true, JumpMode::NoJump); - -// Nft expected; -// expected.num_of_levels = 6; -// expected.levels = { 0, 2, 3, 4, 0, 1, 5 }; -// expected.initial.insert(0); -// expected.final.insert(4); -// expected.delta.add(0, 'g', 5); -// expected.delta.add(0, 'h', 5); -// expected.delta.add(5, 'x', 1); -// expected.delta.add(1, 'a', 2); -// expected.delta.add(1, 'b', 2); -// expected.delta.add(2, 'i', 3); -// expected.delta.add(2, 'j', 3); -// expected.delta.add(3, 'y', 6); -// expected.delta.add(6, 'c', 4); -// expected.delta.add(6, 'd', 4); -// expected.delta.add(6, 'e', 0); -// expected.delta.add(6, 'f', 0); - -// CHECK(result.num_of_levels == 6); -// CHECK(are_equivalent(result, expected)); -// } - -// SECTION("synchronization of lst two levels with one mismatch") { -// Nft lhs; -// lhs.num_of_levels = 3; -// lhs.levels = { 0, 1, 2 }; -// lhs.initial.insert(0); -// lhs.final.insert(0); -// lhs.delta.add(0, 'u', 1); -// lhs.delta.add(0, 'v', 1); -// lhs.delta.add(1, 'x', 2); -// lhs.delta.add(2, 'y', 0); - -// Nft rhs; -// rhs.num_of_levels = 4; -// rhs.levels = { 0, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0}; -// rhs.initial.insert(0); -// rhs.final.insert(7); -// rhs.final.insert(14); -// rhs.final.insert(15); -// rhs.delta.add(0, 'a', 1); -// rhs.delta.add(0, 'b', 1); -// rhs.delta.add(1, 'c', 2); -// rhs.delta.add(1, 'd', 2); -// rhs.delta.add(1, 'c', 3); -// rhs.delta.add(1, 'd', 3); -// rhs.delta.add(2, 'x', 4); -// rhs.delta.add(4, 'y', 6); -// rhs.delta.add(6, 'e', 8); -// rhs.delta.add(8, 'f', 10); -// rhs.delta.add(10, 'x', 12); -// rhs.delta.add(12, 'y', 14); -// rhs.delta.add(3, 'x', 5); -// rhs.delta.add(5, 'z', 7); -// rhs.delta.add(7, 'g', 9); -// rhs.delta.add(9, 'h', 11); -// rhs.delta.add(11, 'x', 13); -// rhs.delta.add(13, 'y', 15); - -// Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 2, 3 }, false, true, JumpMode::NoJump); - -// Nft expected; -// expected.num_of_levels = 5; -// expected.levels = { 0, 1, 2, 3, 1, 2, 3, 4, 0, 4, 0 }; -// expected.initial.insert(0); -// expected.final.insert(10); -// expected.delta.add(0, 'u', 1); -// expected.delta.add(0, 'v', 1); -// expected.delta.add(1, 'a', 2); -// expected.delta.add(1, 'b', 2); -// expected.delta.add(2, 'c', 3); -// expected.delta.add(2, 'd', 3); -// expected.delta.add(3, 'x', 7); -// expected.delta.add(7, 'y', 8); -// expected.delta.add(8, 'u', 4); -// expected.delta.add(8, 'v', 4); -// expected.delta.add(4, 'e', 5); -// expected.delta.add(5, 'f', 6); -// expected.delta.add(6, 'x', 9); -// expected.delta.add(9, 'y', 10); - -// CHECK(result.num_of_levels == 5); -// CHECK(are_equivalent(result, expected)); -// } - -// SECTION("epsilon on synchronization") { -// Nft lhs; -// lhs.num_of_levels = 3; -// lhs.levels = { 0, 1, 2, 0 }; -// lhs.initial.insert(0); -// lhs.final.insert(3); -// lhs.delta.add(0, 'u', 1); -// lhs.delta.add(0, 'v', 1); -// lhs.delta.add(1, 'x', 2); -// lhs.delta.add(2, 'y', 3); - -// Nft rhs; -// rhs.num_of_levels = 3; -// rhs.levels = { 0, 1, 2, 2, 0, 2, 0, 1, 1, 2, 2, 0, 0 }; -// rhs.initial.insert(0); -// rhs.final.insert(11); -// rhs.final.insert(12); -// rhs.delta.add(0, 'a', 1); -// rhs.delta.add(0, 'b', 1); -// rhs.delta.add(1, 'c', 2); -// rhs.delta.add(1, EPSILON, 3); -// rhs.delta.add(1, EPSILON, 5); -// rhs.delta.add(2, EPSILON, 4); -// rhs.delta.add(3, EPSILON, 4); -// rhs.delta.add(4, 'e', 7); -// rhs.delta.add(4, 'f', 7); -// rhs.delta.add(7, 'x', 9); -// rhs.delta.add(9, 'y', 11); -// rhs.delta.add(5, 'd', 6); -// rhs.delta.add(6, 'g', 8); -// rhs.delta.add(8, 'x', 10); -// rhs.delta.add(10, 'y', 12); - -// Nft result = compose_fast(lhs, rhs, { 1, 2 }, { 1, 2 }, false, true, JumpMode::NoJump); - -// Nft expected; -// expected.num_of_levels = 4; -// expected.levels = { 0, 1, 2, 1, 2, 3, 0, 3, 0 }; -// expected.initial.insert(0); -// expected.final.insert(8); -// expected.delta.add(0, EPSILON, 1); -// expected.delta.add(1, 'a', 2); -// expected.delta.add(1, 'b', 2); -// expected.delta.add(2, EPSILON, 5); -// expected.delta.add(5, EPSILON, 6); -// expected.delta.add(6, 'u', 3); -// expected.delta.add(6, 'v', 3); -// expected.delta.add(3, 'e', 4); -// expected.delta.add(3, 'f', 4); -// expected.delta.add(4, 'x', 7); -// expected.delta.add(7, 'y', 8); - -// CHECK(result.num_of_levels == 4); -// CHECK(are_equivalent(result, expected)); -// } -// } -// } \ No newline at end of file From 7f53ee0eb4179d762ef95e9a9335aa7b3f1bbb6e Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 11 Sep 2025 18:39:52 +0200 Subject: [PATCH 51/65] working of string tests --- src/nft/composition.cc | 41 +++++++++++++++++++++++++++---- src/nft/strings.cc | 6 +++-- src/strings/nfa-noodlification.cc | 4 +-- tests/nft/nft.cc | 6 ++--- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 058021302..6c131b9f9 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -317,13 +317,44 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Helper function to combine LHS and RHS targets over the given symbol. auto combine_targets = [&](const StateSet& lhs_sync_targets, const StateSet& rhs_sync_targets, const Symbol symbol) { for (const State lhs_sync_target : lhs_sync_targets) { + const Level lhs_sync_target_level = lhs.levels[lhs_sync_target]; for (const State rhs_sync_target : rhs_sync_targets) { + const Level rhs_sync_target_level = rhs.levels[rhs_sync_target]; if (vanish_sync_level) { - create_composition_state(lhs_sync_target, - rhs_sync_target, - composition_state_level, - true, - composition_state); + // This transition will vanish, so we need to find the next + // transition after the synchronization levels and use it for the connection. + if (lhs_sync_target_level != 0) { + // Take transition from LHS + for (const Move& lhs_move : lhs.delta[lhs_sync_target].moves()) { + const Level lhs_move_target_level = lhs.levels[lhs_move.target]; + const size_t transition_length = (lhs_move_target_level == 0 ? lhs.num_of_levels : lhs_move_target_level) - (lhs_sync_level + 1); + const Level composition_target_level_adjusted = static_cast((composition_state_level + transition_length) % result.num_of_levels); + result.add_transition_with_target( + composition_state, + lhs_move.symbol, + create_composition_state(lhs_move.target, + rhs_sync_target, + composition_target_level_adjusted), + jump_mode + ); + } + } else { + assert(rhs_sync_target_level != 0); + // Take transition from RHS + for (const Move& rhs_move : rhs.delta[rhs_sync_target].moves()) { + const Level rhs_move_target_level = rhs.levels[rhs_move.target]; + const size_t transition_length = (rhs_move_target_level == 0 ? rhs.num_of_levels : rhs_move_target_level) - (rhs_sync_level + 1); + const Level composition_target_level_adjusted = static_cast((composition_state_level + transition_length) % result.num_of_levels); + result.add_transition_with_target( + composition_state, + rhs_move.symbol, + create_composition_state(lhs_sync_target, + rhs_move.target, + composition_target_level_adjusted), + jump_mode + ); + } + } } else { result.add_transition_with_target( composition_state, diff --git a/src/nft/strings.cc b/src/nft/strings.cc index 9eb89e9e5..2d19e376d 100644 --- a/src/nft/strings.cc +++ b/src/nft/strings.cc @@ -507,7 +507,9 @@ Nft ReluctantReplace::replace_regex(nfa::Nfa regex, const Word& replacement, Alp Nft dft_begin_marker{ reluctant_replace.begin_marker_nft(reluctant_replace.begin_marker_nfa(regex, alphabet), begin_marker) }; Nft nft_reluctant_replace{ reluctant_replace.reluctant_leftmost_nft(std::move(regex), alphabet, begin_marker, replacement, replace_mode) }; - return compose(dft_begin_marker, nft_reluctant_replace); + // dft_begin_marker.print_to_dot(std::string("dft_begin_marker.dot"), true); + // nft_reluctant_replace.print_to_dot(std::string("nft_reluctant_replace.dot"), true); + return compose(dft_begin_marker, nft_reluctant_replace, 1, 0, true, nft::JumpMode::NoJump); } Nft ReluctantReplace::replace_literal(const Word& literal, const Word& replacement, Alphabet* alphabet, @@ -541,7 +543,7 @@ Nft ReluctantReplace::replace_literal(const Word& literal, const Word& replaceme return nft_end_marker; }() }; Nft nft_literal_replace{ reluctant_replace.replace_literal_nft(literal, replacement, alphabet, end_marker, replace_mode) }; - return compose(nft_end_marker, nft_literal_replace); + return compose(nft_end_marker, nft_literal_replace, 1, 0, true, nft::JumpMode::NoJump); } Nft ReluctantReplace::replace_symbol(Symbol from_symbol, Symbol replacement, mata::Alphabet* alphabet, diff --git a/src/strings/nfa-noodlification.cc b/src/strings/nfa-noodlification.cc index 95b3551be..a21ce7cc6 100644 --- a/src/strings/nfa-noodlification.cc +++ b/src/strings/nfa-noodlification.cc @@ -449,7 +449,7 @@ std::vector seg_nfa::noodlify_for_transducer( // we intersect input nfa with nft on the input track but we need to add INPUT_DELIMITER as an "epsilon transition" of nft add_self_loop_for_every_default_state(intersection, INPUT_DELIMITER); - intersection = mata::nft::compose(concatenated_input_nft, intersection, 0, 0, false); + intersection = mata::nft::compose(concatenated_input_nft, intersection, 0, 0, false, mata::nft::JumpMode::NoJump); intersection.trim(); if(intersection.final.empty()) { @@ -460,7 +460,7 @@ std::vector seg_nfa::noodlify_for_transducer( // and, we also need to INPUT_DELIMITER as "epsilon transition" of the output nfa, so that we do not lose it add_self_loop_for_every_default_state(concatenated_output_nft, INPUT_DELIMITER); add_self_loop_for_every_default_state(intersection, OUTPUT_DELIMITER); - intersection = mata::nft::compose(concatenated_output_nft, intersection, 0, 1, false); + intersection = mata::nft::compose(concatenated_output_nft, intersection, 0, 1, false, mata::nft::JumpMode::NoJump); intersection.trim(); if(intersection.final.empty()) { diff --git a/tests/nft/nft.cc b/tests/nft/nft.cc index 5dde9ca0b..73f2065ff 100644 --- a/tests/nft/nft.cc +++ b/tests/nft/nft.cc @@ -5596,7 +5596,7 @@ TEST_CASE("mata::nft::Nft::apply()") { Nfa nfa = nfa::builder::create_from_regex("dabce"); mata::EnumAlphabet alphabet{ 'a', 'b', 'c', 'd', 'e', 'f' }; Nft nft{ nft::strings::replace_reluctant_literal({'a', 'b', 'c' }, { 'f' }, &alphabet) }; - Nft nft_applied_nfa{ nft.apply(nfa, 0) }; + Nft nft_applied_nfa{ nft.apply(nfa, 0, true, JumpMode::NoJump) }; Nfa result{ nft_applied_nfa.to_nfa_move() }; result.remove_epsilon(); result.trim(); @@ -5613,7 +5613,7 @@ TEST_CASE("mata::nft::Nft::apply()") { Nfa nfa = nfa::builder::create_from_regex("(a|b)*"); Nft nft{1, {0}, {0}, {{0,0}}, 5}; nft.insert_identity(0, 'a'); - CHECK(nft.apply(nfa).num_of_levels == 4); - CHECK(nft.apply(nfa, 3, false).num_of_levels == 5); + CHECK(nft.apply(nfa, 0, true, JumpMode::NoJump).num_of_levels == 4); + CHECK(nft.apply(nfa, 3, false, JumpMode::NoJump).num_of_levels == 5); } } From 591bed3a20b59ec936d308b3969202f2ad994808 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Sun, 14 Sep 2025 13:28:45 +0200 Subject: [PATCH 52/65] weaker can_synchronize_in_the_future cindition in copy --- src/nft/composition.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 6c131b9f9..9a3ddd24b 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -501,7 +501,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le copy_nft.levels[copy_target] == 0 // End of the waiting loop. : exist_intersection_of_sync_types(stationar_state_sync_type, copy_target_sync_type) || stationar_state_sync_type == SynchronizationType::UNDEFINED || - copy_target_sync_type == SynchronizationType::UNDEFINED + copy_target_sync_type == SynchronizationType::UNDEFINED || + copy_nft.levels[copy_target] == 0 ); if (!can_synchronize_in_the_future) { // There is no way we would be able to synchronize in the future. From f72dcda35e35e64931469297017391827be74fa0 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 17 Sep 2025 17:50:52 +0200 Subject: [PATCH 53/65] synchronization optimized --- src/nft/composition.cc | 231 +++++----- src/nft/intersection.cc | 8 +- src/nft/nft.cc | 12 +- tests/nft/nft-composition.cc | 847 +++++++++++++++++++++++++---------- 4 files changed, 748 insertions(+), 350 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 9a3ddd24b..da20ecbce 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -24,6 +24,10 @@ namespace { * for lhs and rhs states, and whether they can synchronize or if one of them will wait * because there is an EPSILON on the synchronization in the other that it * could not synchronize with. + * + * You can nottice that we do not want to synchronize on EPSILONs. + * This is a part of the optimization to avoid creating redundant EPSILON transitions. + * For more defails, see https://cs.nyu.edu/~mohri/pub/nway.pdf Figure 3. * || RHS sync type * LHS sync type || ONLY_ON_SYMBOL | ONLY_ON_EPSILON | ON_EPSILON_AND_SYMBOL * ======================||================|=================|====================== @@ -31,11 +35,11 @@ namespace { * ONLY_ON_SYMBOL || | wait on LHS | wait on LHS * || | | * ----------------------||----------------|-----------------|---------------------- - * || | synchronize | synchornize + * || | | * ONLY_ON_EPSILON || | lait on LHS | lait on LHS * || wait on RHS | wait on RHS | wait on RHS * ----------------------||----------------|-----------------|---------------------- - * || synchronize | synchronize | synchornize + * || synchronize | | synchornize * ON_EPSILON_AND_SYMBOL || | wait on LHS | wait on LHS * || wait on RHS | wait on RHS | wait on RHS * ================================================================================= @@ -54,9 +58,15 @@ namespace { lhs = lhs | rhs; return lhs; } - inline bool exist_intersection_of_sync_types(SynchronizationType lhs, SynchronizationType rhs) { + inline bool exist_intersection(SynchronizationType lhs, SynchronizationType rhs) { return (static_cast(lhs) & static_cast(rhs)) != 0; } + inline bool exist_synchronization(SynchronizationType lhs, SynchronizationType rhs) { + return lhs != SynchronizationType::ONLY_ON_EPSILON && rhs != SynchronizationType::ONLY_ON_EPSILON; + } + inline bool exist_epsilon(SynchronizationType s) { + return s != SynchronizationType::ONLY_ON_SYMBOL; + } /** * @brief A class to hold properties related to synchronization during the composition of NFTs. @@ -242,6 +252,31 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // are likely to be at the same level and thus enter the same branch. std::queue> worklist; + /** + * @brief Efficiently inserts a symbol post into the delta of the result NFT. + * + * @param source The source state of the transition. + * @param symbol_post The symbol post to insert. + */ + auto insert_symbol_post_to_delta = [&](const State source, const SymbolPost& symbol_post) { + assert(!symbol_post.targets.empty()); + auto &mutable_state_post = result.delta.mutable_state_post(source); + auto symbol_post_it = mutable_state_post.find(symbol_post.symbol); + if (symbol_post_it != mutable_state_post.end()) { + // The symbol post already exists, just add the targets. + symbol_post_it->targets.insert(symbol_post.targets); + return; + } + // The symbol post does not exist. + if (mutable_state_post.empty() || mutable_state_post.back().symbol < symbol_post.symbol) { + // It is the greatest symbol, just add it to the end. + mutable_state_post.push_back(std::move(symbol_post)); + return; + } + // Use insert method to insert at the correct position. + mutable_state_post.insert(std::move(symbol_post)); + }; + /** * @brief Creates a new composition state for the given pair of states, if it does not already exist. * @@ -316,127 +351,99 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Helper function to combine LHS and RHS targets over the given symbol. auto combine_targets = [&](const StateSet& lhs_sync_targets, const StateSet& rhs_sync_targets, const Symbol symbol) { - for (const State lhs_sync_target : lhs_sync_targets) { - const Level lhs_sync_target_level = lhs.levels[lhs_sync_target]; - for (const State rhs_sync_target : rhs_sync_targets) { - const Level rhs_sync_target_level = rhs.levels[rhs_sync_target]; - if (vanish_sync_level) { - // This transition will vanish, so we need to find the next - // transition after the synchronization levels and use it for the connection. - if (lhs_sync_target_level != 0) { - // Take transition from LHS - for (const Move& lhs_move : lhs.delta[lhs_sync_target].moves()) { - const Level lhs_move_target_level = lhs.levels[lhs_move.target]; - const size_t transition_length = (lhs_move_target_level == 0 ? lhs.num_of_levels : lhs_move_target_level) - (lhs_sync_level + 1); - const Level composition_target_level_adjusted = static_cast((composition_state_level + transition_length) % result.num_of_levels); - result.add_transition_with_target( - composition_state, - lhs_move.symbol, - create_composition_state(lhs_move.target, - rhs_sync_target, - composition_target_level_adjusted), - jump_mode - ); - } - } else { - assert(rhs_sync_target_level != 0); - // Take transition from RHS - for (const Move& rhs_move : rhs.delta[rhs_sync_target].moves()) { - const Level rhs_move_target_level = rhs.levels[rhs_move.target]; - const size_t transition_length = (rhs_move_target_level == 0 ? rhs.num_of_levels : rhs_move_target_level) - (rhs_sync_level + 1); - const Level composition_target_level_adjusted = static_cast((composition_state_level + transition_length) % result.num_of_levels); - result.add_transition_with_target( - composition_state, - rhs_move.symbol, - create_composition_state(lhs_sync_target, - rhs_move.target, - composition_target_level_adjusted), - jump_mode - ); - } + if (!vanish_sync_level) { + // Do a standard combination of targets. + SymbolPost symbol_post{ symbol }; + for (const State lhs_sync_target : lhs_sync_targets) { + for (const State rhs_sync_target : rhs_sync_targets) { + symbol_post.insert(create_composition_state(lhs_sync_target, + rhs_sync_target, + composition_target_level)); + } + } + insert_symbol_post_to_delta(composition_state, symbol_post); + return; + } + + // The synchronization level will vanish. + // We know that there is at least one transition in LHS or RHS after the synchronization. + // We are going to use those transitions to connect composition_state to their targets. + const bool is_remainging_transition_in_lhs = lhs_sync_level + 1 < lhs.num_of_levels; + const bool is_remainging_transition_in_rhs = rhs_sync_level + 1 < rhs.num_of_levels; + assert(is_remainging_transition_in_lhs || is_remainging_transition_in_rhs); + + + const StateSet& moving_sync_targets = is_remainging_transition_in_lhs ? lhs_sync_targets : rhs_sync_targets; + const StateSet& static_sync_targets = is_remainging_transition_in_lhs ? rhs_sync_targets : lhs_sync_targets; + const Nft& moving_nft = is_remainging_transition_in_lhs ? lhs : rhs; + + mata::utils::SynchronizedExistentialIterator::const_iterator> moving_sync_iterator(moving_sync_targets.size()); + for (const State moving_sync_target : moving_sync_targets) { + mata::utils::push_back(moving_sync_iterator, moving_nft.delta[moving_sync_target]); + } + while (moving_sync_iterator.advance()) { + const std::vector& same_symbol_posts{ moving_sync_iterator.get_current() }; + assert(!same_symbol_posts.empty()); + + const Symbol moving_symbol = same_symbol_posts[0]->symbol; + SymbolPost symbol_post{ moving_symbol }; + for (const auto& moving_next_targets : same_symbol_posts) { + for (const State moving_next_target : moving_next_targets->targets) { + for (const State static_sync_target : static_sync_targets) { + symbol_post.insert(create_composition_state(moving_next_target, + static_sync_target, + composition_target_level, + is_remainging_transition_in_lhs)); } + } + } + insert_symbol_post_to_delta(composition_state, symbol_post); + } + }; + + // Helper function to perform synchronization on DONT_CARE transitions. + auto synchronization_on_dont_care = [&](const State dont_care_state, const State other_state, const bool is_dont_care_lhs) { + const Nft& dont_care_nft = is_dont_care_lhs ? lhs : rhs; + const Nft& other_nft = is_dont_care_lhs ? rhs : lhs; + + auto dont_care_sync_it = dont_care_nft.delta[dont_care_state].find(DONT_CARE); + if (dont_care_sync_it != dont_care_nft.delta[dont_care_state].end()) { + for (const SymbolPost& symbol_post : other_nft.delta[other_state]) { + if (symbol_post.symbol == EPSILON) { + // We don't want to synchronize DONT_CARE with EPSILON. + continue; + } + const Symbol symbol = reconnect ? reconnection_symbol : symbol_post.symbol; + if (is_dont_care_lhs) { + combine_targets(dont_care_sync_it->targets, symbol_post.targets, symbol); } else { - result.add_transition_with_target( - composition_state, - symbol, - create_composition_state(lhs_sync_target, - rhs_sync_target, - composition_target_level, - true), - jump_mode - ); + combine_targets(symbol_post.targets, dont_care_sync_it->targets, symbol); } } } }; - // Synchronization using SynchronizedUniversalIterator. + // The body of the synchronization. mata::utils::SynchronizedUniversalIterator::const_iterator> sync_iterator(2); mata::utils::push_back(sync_iterator, lhs.delta[lhs_state]); mata::utils::push_back(sync_iterator, rhs.delta[rhs_state]); while (sync_iterator.advance()) { const std::vector& same_symbol_posts{ sync_iterator.get_current() }; assert(same_symbol_posts.size() == 2); // One move per state in the pair. - const Symbol symbol = reconnect ? reconnection_symbol - : same_symbol_posts[0]->symbol; + const Symbol symbol = reconnect ? reconnection_symbol : same_symbol_posts[0]->symbol; if (same_symbol_posts[0]->symbol == EPSILON) { - // We need to be careful not to use fast EPSILON transitions. - StateSet filtered_lhs_targets; - if (lhs.levels[lhs_state] == 0 && lhs.num_of_levels != 1) { - std::copy_if( - same_symbol_posts[0]->targets.cbegin(), - same_symbol_posts[0]->targets.cend(), - std::back_inserter(filtered_lhs_targets), - [&](State s) { return lhs.levels[s] != 0; } - ); - } else { - filtered_lhs_targets = same_symbol_posts[0]->targets; - } - StateSet filtered_rhs_targets; - if (rhs.levels[rhs_state] == 0 && rhs.num_of_levels != 1) { - std::copy_if( - same_symbol_posts[1]->targets.cbegin(), - same_symbol_posts[1]->targets.cend(), - std::back_inserter(filtered_rhs_targets), - [&](State s) { return rhs.levels[s] != 0; } - ); - } else { - filtered_rhs_targets = same_symbol_posts[1]->targets; - } - combine_targets(filtered_lhs_targets, filtered_rhs_targets, EPSILON); - } else { - combine_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); + // We don't want to synchronize EPSILON with EPSILON. + // This creates a redundant EPSILON transitions in this implementation. + // This is an optimization according to https://cs.nyu.edu/~mohri/pub/nway.pdf Figure 3. + continue; } - } + combine_targets(same_symbol_posts[0]->targets, same_symbol_posts[1]->targets, symbol); - // Synchronization on DONT_CARE in the LHS. - auto lhs_dont_care_sync_it = lhs.delta[lhs_state].find(DONT_CARE); - if (lhs_dont_care_sync_it != lhs.delta[lhs_state].end()) { - for (const SymbolPost& rhs_symbol_post : rhs.delta[rhs_state]) { - if (rhs_symbol_post.symbol == EPSILON) { - // We don't want to synchronize DONT_CARE with EPSILON. - continue; - } - const Symbol symbol = reconnect ? reconnection_symbol - : rhs_symbol_post.symbol; - combine_targets(lhs_dont_care_sync_it->targets, rhs_symbol_post.targets, symbol); - } } - // Do the synchronization on DONT_CARE in the RHS. - auto rhs_dont_care_sync_it = rhs.delta[rhs_state].find(DONT_CARE); - if (rhs_dont_care_sync_it != rhs.delta[rhs_state].end()) { - for (const SymbolPost& lhs_symbol_post : lhs.delta[lhs_state]) { - if (lhs_symbol_post.symbol == EPSILON) { - // We don't want to synchronize DONT_CARE with EPSILON. - continue; - } - const Symbol symbol = reconnect ? reconnection_symbol - : lhs_symbol_post.symbol; - combine_targets(lhs_symbol_post.targets, rhs_dont_care_sync_it->targets, symbol); - } - } + synchronization_on_dont_care(lhs_state, rhs_state, true); + synchronization_on_dont_care(rhs_state, lhs_state, false); }; @@ -497,9 +504,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // later due to an inability to synchronize. When this function is called // from the waiting simulation, we are interested in onyl synchronizations on EPSILON. const bool can_synchronize_in_the_future = ( - waiting_worklist != nullptr ? copy_target_sync_type != SynchronizationType::ONLY_ON_SYMBOL || + waiting_worklist != nullptr ? exist_epsilon(copy_target_sync_type) || copy_nft.levels[copy_target] == 0 // End of the waiting loop. - : exist_intersection_of_sync_types(stationar_state_sync_type, copy_target_sync_type) || + : exist_synchronization(stationar_state_sync_type, copy_target_sync_type) || stationar_state_sync_type == SynchronizationType::UNDEFINED || copy_target_sync_type == SynchronizationType::UNDEFINED || copy_nft.levels[copy_target] == 0 @@ -871,9 +878,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // TODO: Optimize/reduce the number of similar combination of waiting simulations. // You can see the table defining operations for pair of synchronization types // in the documentation of the SynchronizationType enum class. - const bool perform_wait_on_lhs = exist_intersection_of_sync_types(rhs_sync_type, SynchronizationType::ONLY_ON_EPSILON); - const bool perform_wait_on_rhs = exist_intersection_of_sync_types(lhs_sync_type, SynchronizationType::ONLY_ON_EPSILON); - const bool can_synchronize_in_the_future = exist_intersection_of_sync_types(lhs_sync_type, rhs_sync_type); + const bool perform_wait_on_lhs = exist_intersection(rhs_sync_type, SynchronizationType::ONLY_ON_EPSILON); + const bool perform_wait_on_rhs = exist_intersection(lhs_sync_type, SynchronizationType::ONLY_ON_EPSILON); + const bool can_synchronize_in_the_future = exist_synchronization(lhs_sync_type, rhs_sync_type); if (perform_wait_on_lhs) { // LHS is waiting (i.e., there is an EPSILON in the RHS that LHS can not synchronize on). model_waiting(composition_state, lhs_state, rhs_state, true); @@ -892,10 +899,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // It makes sense to continue only if we believe that synchronization is possible, // or if we have already passed the synchronization level (i.e., lhs_sync_type == // SynchronizationType::UNDEFINED or rhs_sync_type == SynchronizationType::UNDEFINED). - assert(exist_intersection_of_sync_types(lhs_sync_type, rhs_sync_type) || + assert(exist_synchronization(lhs_sync_type, rhs_sync_type) || (lhs_level == 0 && rhs_level > 0 && rhs_sync_type == SynchronizationType::UNDEFINED) || (rhs_level == 0 && lhs_level > 0 && lhs_sync_type == SynchronizationType::UNDEFINED) || - (lhs_level > lhs_sync_level && rhs_level > rhs_sync_level &&lhs_sync_type == SynchronizationType::UNDEFINED && rhs_sync_type == SynchronizationType::UNDEFINED) + (lhs_level > lhs_sync_level && rhs_level > rhs_sync_level && lhs_sync_type == SynchronizationType::UNDEFINED && rhs_sync_type == SynchronizationType::UNDEFINED) ); // Both LHS and RHS can take a step if they are not at the synchronization level. diff --git a/src/nft/intersection.cc b/src/nft/intersection.cc index fb18bfeb7..0f6cfed9b 100644 --- a/src/nft/intersection.cc +++ b/src/nft/intersection.cc @@ -127,7 +127,13 @@ Nft mata::nft::algorithms::product(const Nft& lhs, const Nft& rhs, const std::fu StatePost &product_state_post{product.delta.mutable_state_post(product_source)}; const auto product_state_post_find_it = product_state_post.find(product_symbol_post.symbol); if (product_state_post_find_it == product_state_post.end()) { - product_state_post.insert(std::move(product_symbol_post)); + if (product_state_post.empty() || product_state_post.back().symbol < product_symbol_post.symbol) { + // Optimization: If the new symbol is greater than the last one, we can simply push it back. + product_state_post.push_back(std::move(product_symbol_post)); + } else { + // Otherwise, we need to insert it in the correct position to keep the order. + product_state_post.insert(std::move(product_symbol_post)); + } } else { product_state_post_find_it->targets.insert(product_symbol_post.targets); } diff --git a/src/nft/nft.cc b/src/nft/nft.cc index 27cd7ac1a..ec7d16ccc 100644 --- a/src/nft/nft.cc +++ b/src/nft/nft.cc @@ -590,10 +590,16 @@ void Nft::add_transition_with_same_level_targets(State source, Symbol symbol, co StatePost& mutable_state_post = delta.mutable_state_post(source); auto mutable_symbol_post_it = mutable_state_post.find(symbol); if (mutable_symbol_post_it == mutable_state_post.end()) { - mutable_state_post.insert(std::move(SymbolPost(symbol, targets))); - return; + if (mutable_state_post.empty() || mutable_state_post.back().symbol < symbol) { + // Optimization: If the new symbol is greater than the last one, we can simply push it back. + mutable_state_post.push_back({ symbol, targets }); + } else { + // Otherwise, we need to insert it in the correct position to keep the order. + mutable_state_post.insert({ symbol, targets }); + } + } else { + mutable_symbol_post_it->targets.insert(targets); } - mutable_symbol_post_it->targets.insert(targets); return; } diff --git a/tests/nft/nft-composition.cc b/tests/nft/nft-composition.cc index bd4463cb9..f6854440d 100644 --- a/tests/nft/nft-composition.cc +++ b/tests/nft/nft-composition.cc @@ -588,28 +588,44 @@ TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } @@ -634,28 +650,44 @@ TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -676,10 +708,14 @@ TEST_CASE("nft::compose(Nft&, Nft&, Level, Level, ...) - easy cases") { rhs.delta.add(2, 'c', 3); Nft result_full = compose(lhs, rhs, 0, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); - CHECK(are_equivalent(result_full, expected_full)); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); + CHECK(are_equivalent(result_full, expected_full)); } } @@ -708,15 +744,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 0 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 0, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 0, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -730,15 +774,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 0 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 0, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -783,15 +835,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 0, 1, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 0, 1, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -815,15 +875,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 1, 0, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 1, 0, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -870,28 +938,44 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 0, 2, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 0, 2, true, JumpMode::RepeatSymbol).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 2, 0, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 2, 0, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -938,15 +1022,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } @@ -969,15 +1061,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1022,15 +1122,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 1, 1, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 1, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1052,15 +1160,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 1, 1, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 1, 1, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1105,15 +1221,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 1, 2, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 2, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1135,15 +1259,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 2, 1, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 2, 1, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1190,28 +1322,44 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 2, 0, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 2, 0, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 0, 2, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 2, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1256,15 +1404,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 2, 1, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 2, 1, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1286,15 +1442,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 1, 2, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 1, 2, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1348,15 +1512,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { expected_proj.delta.add(3, 'h', 0); Nft result_full = compose(lhs, rhs, 2, 2, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 2, 2, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1387,15 +1559,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding without epsilon") { expected_proj.delta.add(3, 'b', 0); Nft result_full = compose(rhs, lhs, 2, 2, false, JumpMode::NoJump).trim(); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 2, 2, true, JumpMode::NoJump).trim(); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1435,15 +1615,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 0 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 0, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 0, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1462,15 +1650,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 0 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 0, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1507,15 +1703,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 0, 1, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 0, 1, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1534,15 +1738,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 1, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 1, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1578,28 +1790,44 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 0, 2, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 0, 2, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 2, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 2, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1637,15 +1865,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1664,15 +1900,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1709,15 +1953,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 1, 1, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 1, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1736,15 +1988,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 1, 1, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 1, 1, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1780,15 +2040,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 1, 2, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 2, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1807,15 +2075,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 2, 1, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 2, 1, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1851,15 +2127,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 2, 0, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 2, 0, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } @@ -1879,15 +2163,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 2 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 0, 2, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 2, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1923,15 +2215,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 2, 1, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 2, 1, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -1950,15 +2250,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 3 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 1, 2, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 1, 2, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -1994,15 +2302,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 4 }, JumpMode::NoJump); Nft result_full = compose(lhs, rhs, 2, 2, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 2, 2, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { @@ -2021,15 +2337,23 @@ TEST_CASE("nft::compose(..., Level, Level, ...) - sliding with epsilon") { Nft expected_proj = project_out(expected_full, { 4 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 2, 2, false, JumpMode::NoJump); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 2, 2, true, JumpMode::NoJump); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -2049,7 +2373,7 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { rhs.delta.add(2, 'd', 3); SECTION("LHS | RHS") { - Nft expected_full(32, { 0 }, { 5, 14 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 }, 5); + Nft expected_full(28, { 0 }, { 5, 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 }, 5); expected_full.delta.add(0, EPSILON, 1); expected_full.delta.add(1, EPSILON, 2); expected_full.delta.add(2, EPSILON, 3); @@ -2060,49 +2384,53 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { expected_full.delta.add(7, 'a', 8); expected_full.delta.add(8, 'b', 9); expected_full.delta.add(9, EPSILON, 5); - expected_full.delta.add(0, 'c', 10); - expected_full.delta.add(10, EPSILON, 11); - expected_full.delta.add(11, 'a', 12); - expected_full.delta.add(12, 'b', 13); - expected_full.delta.add(13, 'd', 14); - expected_full.delta.add(0, 'c', 15); + expected_full.delta.add(0, 'c', 11); + expected_full.delta.add(11, EPSILON, 12); + expected_full.delta.add(12, EPSILON, 13); + expected_full.delta.add(13, EPSILON, 14); + expected_full.delta.add(14, 'd', 15); expected_full.delta.add(15, EPSILON, 16); expected_full.delta.add(16, EPSILON, 17); expected_full.delta.add(17, EPSILON, 18); - expected_full.delta.add(18, 'd', 19); - expected_full.delta.add(19, EPSILON, 20); + expected_full.delta.add(18, EPSILON, 19); + expected_full.delta.add(19, EPSILON, 10); + expected_full.delta.add(5, 'c', 20); expected_full.delta.add(20, EPSILON, 21); expected_full.delta.add(21, EPSILON, 22); expected_full.delta.add(22, EPSILON, 23); - expected_full.delta.add(23, EPSILON, 14); - expected_full.delta.add(5, 'c', 24); + expected_full.delta.add(23, 'd', 10); + expected_full.delta.add(15, EPSILON, 24); expected_full.delta.add(24, EPSILON, 25); - expected_full.delta.add(25, EPSILON, 26); - expected_full.delta.add(26, EPSILON, 27); - expected_full.delta.add(27, 'd', 14); - expected_full.delta.add(19, EPSILON, 28); - expected_full.delta.add(28, EPSILON, 29); - expected_full.delta.add(29, 'a', 30); - expected_full.delta.add(30, 'b', 31); - expected_full.delta.add(31, EPSILON, 14); + expected_full.delta.add(25, 'a', 26); + expected_full.delta.add(26, 'b', 27); + expected_full.delta.add(27, EPSILON, 10); Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::RepeatSymbol); Nft result_full = compose(lhs, rhs, 0, 1, false, JumpMode::NoJump); + CHECK(result_full.final.size() == expected_full.final.size()); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 0, 1, true, JumpMode::NoJump); CHECK(result_proj.final.size() == expected_proj.final.size()); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { - Nft expected_full(32, { 0 }, { 5, 14 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 }, 5); + Nft expected_full(28, { 0 }, { 5, 10 }, { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 }, 5); expected_full.delta.add(0, EPSILON, 1); expected_full.delta.add(1, EPSILON, 2); expected_full.delta.add(2, EPSILON, 3); @@ -2113,45 +2441,48 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { expected_full.delta.add(7, EPSILON, 8); expected_full.delta.add(8, 'a', 9); expected_full.delta.add(9, 'b', 5); - expected_full.delta.add(0, 'c', 10); - expected_full.delta.add(10, EPSILON, 11); - expected_full.delta.add(11, 'd', 12); - expected_full.delta.add(12, 'a', 13); - expected_full.delta.add(13, 'b', 14); - expected_full.delta.add(0, 'c', 15); + expected_full.delta.add(0, 'c', 11); + expected_full.delta.add(11, EPSILON, 12); + expected_full.delta.add(12, 'd', 13); + expected_full.delta.add(13, EPSILON, 14); + expected_full.delta.add(14, EPSILON, 15); expected_full.delta.add(15, EPSILON, 16); - expected_full.delta.add(16, 'd', 17); + expected_full.delta.add(16, EPSILON, 17); expected_full.delta.add(17, EPSILON, 18); expected_full.delta.add(18, EPSILON, 19); - expected_full.delta.add(19, EPSILON, 20); + expected_full.delta.add(19, EPSILON, 10); + expected_full.delta.add(5, 'c', 20); expected_full.delta.add(20, EPSILON, 21); - expected_full.delta.add(21, EPSILON, 22); + expected_full.delta.add(21, 'd', 22); expected_full.delta.add(22, EPSILON, 23); - expected_full.delta.add(23, EPSILON, 14); - expected_full.delta.add(5, 'c', 24); + expected_full.delta.add(23, EPSILON, 10); + expected_full.delta.add(15, EPSILON, 24); expected_full.delta.add(24, EPSILON, 25); - expected_full.delta.add(25, 'd', 26); - expected_full.delta.add(26, EPSILON, 27); - expected_full.delta.add(27, EPSILON, 14); - expected_full.delta.add(19, EPSILON, 28); - expected_full.delta.add(28, EPSILON, 29); - expected_full.delta.add(29, EPSILON, 30); - expected_full.delta.add(30, 'a', 31); - expected_full.delta.add(31, 'b', 14); + expected_full.delta.add(25, EPSILON, 26); + expected_full.delta.add(26, 'a', 27); + expected_full.delta.add(27, 'b', 10); Nft expected_proj = project_out(expected_full, { 1 }, JumpMode::NoJump); Nft result_full = compose(rhs, lhs, 1, 0, false, JumpMode::NoJump); CHECK(result_full.final.size() == expected_full.final.size()); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 1, 0, true, JumpMode::NoJump); CHECK(result_proj.final.size() == expected_proj.final.size()); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -2186,31 +2517,47 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); CHECK(result_full.final.size() == expected_full.final.size()); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); CHECK(result_proj.final.size() == expected_proj.final.size()); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); CHECK(result_full.final.size() == expected_full.final.size()); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); CHECK(result_proj.final.size() == expected_proj.final.size()); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -2263,32 +2610,48 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - epsilon on both sides") { SECTION("LHS | RHS") { Nft result_full = compose(lhs, rhs, 1, 0, false, JumpMode::NoJump); CHECK(result_full.final.size() == expected_full.final.size()); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 1, 0, true, JumpMode::NoJump); CHECK(result_proj.final.size() == expected_proj.final.size()); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } SECTION("RHS | LHS") { Nft result_full = compose(rhs, lhs, 0, 1, false, JumpMode::NoJump); CHECK(result_full.final.size() == expected_full.final.size()); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 0, 1, true, JumpMode::NoJump); CHECK(result_proj.final.size() == expected_proj.final.size()); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } @@ -2406,16 +2769,24 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - complex") { Nft result_full = compose(lhs, rhs, 3, 3, false, JumpMode::NoJump); CHECK(result_full.final.size() == expected_full.final.size()); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(lhs, rhs, 3, 3, true, JumpMode::NoJump).trim(); CHECK(result_proj.final.size() == expected_proj.final.size()); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } @@ -2501,16 +2872,24 @@ TEST_CASE("nft::composition(..., Level, Level, ...) - complex") { Nft result_full = compose(rhs, lhs, 3, 3, false, JumpMode::NoJump); CHECK(result_full.final.size() == expected_full.final.size()); - CHECK(result_full.num_of_states() == expected_full.num_of_states()); + CHECK(result_full.num_of_states() <= expected_full.num_of_states()); CHECK(result_full.num_of_levels == expected_full.num_of_levels); - CHECK(result_full.delta.num_of_transitions() == expected_full.delta.num_of_transitions()); + CHECK(result_full.delta.num_of_transitions() <= expected_full.delta.num_of_transitions()); + result_full.trim(); + result_full.remove_epsilon(); + expected_full.trim(); + expected_full.remove_epsilon(); CHECK(are_equivalent(result_full, expected_full)); Nft result_proj = compose(rhs, lhs, 3, 3, true, JumpMode::NoJump).trim(); CHECK(result_proj.final.size() == expected_proj.final.size()); - CHECK(result_proj.num_of_states() == expected_proj.num_of_states()); + CHECK(result_proj.num_of_states() <= expected_proj.num_of_states()); CHECK(result_proj.num_of_levels == expected_proj.num_of_levels); - CHECK(result_proj.delta.num_of_transitions() == expected_proj.delta.num_of_transitions()); + CHECK(result_proj.delta.num_of_transitions() <= expected_proj.delta.num_of_transitions()); + result_proj.trim(); + result_proj.remove_epsilon(); + expected_proj.trim(); + expected_proj.remove_epsilon(); CHECK(are_equivalent(result_proj, expected_proj)); } } From c49f58598dd7298b35e52a9031cb53265be1a33f Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 18 Sep 2025 11:09:02 +0200 Subject: [PATCH 54/65] optimize copy --- src/nft/composition.cc | 63 +++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index da20ecbce..dc79c6283 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -200,7 +200,6 @@ namespace mata::nft Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, const bool project_out_sync_levels, const JumpMode jump_mode) { assert(lhs_sync_level < lhs.num_of_levels && rhs_sync_level < rhs.num_of_levels); - // TODO: Modify it to work even with other jump modes. if (jump_mode != JumpMode::NoJump) { return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels); } @@ -211,22 +210,19 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le lhs.delta.transitions().begin(), lhs.delta.transitions().end(), [&](const Transition& transition) { - return (lhs.levels[transition.source] < lhs_sync_level && lhs.levels[transition.target] <= lhs_sync_level && lhs.levels[transition.target] != 0) || - (lhs.levels[transition.source] == lhs_sync_level && lhs.levels[transition.target] == static_cast((lhs_sync_level + 1) % lhs.num_of_levels)) || - (lhs.levels[transition.source] > lhs_sync_level) || + return static_cast((lhs.levels[transition.source] + 1) % lhs.num_of_levels) == lhs.levels[transition.target] || (lhs.levels[transition.source] == 0 && lhs.levels[transition.target] == 0 && transition.symbol == EPSILON); } ) ); + // Check that there are only explicit synchronization transitions of length 1 with exception for fast EPSILON transitions. assert( std::all_of( rhs.delta.transitions().begin(), rhs.delta.transitions().end(), [&](const Transition& transition) { - return (rhs.levels[transition.source] < rhs_sync_level && rhs.levels[transition.target] <= rhs_sync_level && rhs.levels[transition.target] != 0) || - (rhs.levels[transition.source] == rhs_sync_level && rhs.levels[transition.target] == static_cast((rhs_sync_level + 1) % rhs.num_of_levels)) || - (rhs.levels[transition.source] > rhs_sync_level) || + return static_cast((rhs.levels[transition.source] + 1) % rhs.num_of_levels) == rhs.levels[transition.target] || (rhs.levels[transition.source] == 0 && rhs.levels[transition.target] == 0 && transition.symbol == EPSILON); } ) @@ -477,22 +473,20 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const SynchronizationType stationar_state_sync_type = stationar_sync_props.sync_types_v[stationar_state]; - // It may happen that we encounter a transition whose target is at the synchronization level, - // the state in the stationary NFT is also at the synchronization level (or we came from the waiting simulation), - // project_out_sync_levels is true, and there is no other transition that would be added - // before the next zero-level state. In this case, we need to handle this synchronization - // here in place and make the connection directly to the next zero-level state. + // It may happen that + // 1) we want to project out synchronization levels, + // 2) there are no transitions after the synchronization level in both NFTs, and + // 3) targets of the copy_state are at the synchronization level. + // In this case, we are going to need to perform synchronization here and use + // the copied transitions to connect directly to the next zero-level states. const bool handle_synchronization_in_place = project_out_sync_levels && stationar_sync_props.num_of_levels_after_sync == 0 && - copy_sync_props.num_of_levels_after_sync == 0 && ( - !is_copy_state_lhs || - stationar_sync_props.num_of_levels_before_sync == 0 - ) && ( - waiting_worklist != nullptr || - stationar_state_level == stationar_sync_props.sync_level - ); + copy_sync_props.num_of_levels_after_sync == 0 && + (!is_copy_state_lhs || stationar_sync_props.num_of_levels_before_sync == 0) && + (waiting_worklist != nullptr || stationar_state_level == stationar_sync_props.sync_level); for (const SymbolPost& copy_symbol_post : copy_nft.delta[copy_state]) { + SymbolPost symbol_post{ copy_symbol_post.symbol }; for (const State copy_target : copy_symbol_post.targets) { if (copy_symbol_post.symbol == EPSILON && copy_state_level == 0 && copy_nft.num_of_levels != 1 && copy_nft.levels[copy_target] == 0) { // Skip fast EPSILON transitions. @@ -535,15 +529,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(copy_eps_symbol_post != copy_nft.delta[copy_target].end()); for (const State sync_target : copy_eps_symbol_post->targets) { assert(copy_nft.levels[sync_target] == 0); - result.add_transition_with_target( - composition_state, - copy_symbol_post.symbol, - create_composition_state(sync_target, - stationar_state, - 0, - is_copy_state_lhs), - jump_mode - ); + symbol_post.insert(create_composition_state(sync_target, + stationar_state, + 0, + is_copy_state_lhs)); } // We do not need to add new pairs to the waiting_worklist, // because, if necessary, they have already been added to the main @@ -572,23 +561,21 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // because, while waiting in the stationary state, we are not exactly at that state, // but rather in virtual (nonexistent) states of the waiting loop over it. const State new_composition_state = result.add_state_with_level(new_composition_state_level); - result.add_transition_with_target(composition_state, copy_symbol_post.symbol, new_composition_state, jump_mode); + symbol_post.push_back(new_composition_state); // Sometimes, the running root state gets pushed to the worklist again. We need to handle it during the pop. waiting_worklist->push({ new_composition_state, copy_target }); } else { // Just copy this transition. - result.add_transition_with_target( - composition_state, - copy_symbol_post.symbol, - create_composition_state(copy_target, - stationar_state, - new_composition_state_level, - is_copy_state_lhs), - jump_mode - ); + symbol_post.insert(create_composition_state(copy_target, + stationar_state, + new_composition_state_level, + is_copy_state_lhs)); } } } + if (!symbol_post.targets.empty()) { + insert_symbol_post_to_delta(composition_state, symbol_post); + } } }; From 849be976897ef1472941dc255a3e65e35849a8ed Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 18 Sep 2025 11:26:05 +0200 Subject: [PATCH 55/65] process_fast_epsilon_transitions optimized --- src/nft/composition.cc | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index dc79c6283..8433400cb 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -814,21 +814,37 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le [&](State s) { return rhs.levels[s] == 0; } ); } + + if (filtered_lhs_fast_eps_targets.size() == 1 && filtered_rhs_fast_eps_targets.size() == 1) { + // There are no fast EPSILON transitions to process. + return; + } + // Create combinations of all non-zero-level targets of lhs_src with all non-zero-level targets of rhs_src. + // Create a common path of EPSILON transitions to connect to all those combinations. + // Branch onyl at the last level of that common path. + assert(result.num_of_levels > 0); + const size_t common_path_len = result_num_of_levels - 1; + State source = composition_state; + for (size_t i = 0; i < common_path_len; ++i) { + SymbolPost symbol_post{ EPSILON }; + State new_source = result.add_state_with_level(static_cast(i + 1)); + symbol_post.push_back(new_source); + insert_symbol_post_to_delta(source, symbol_post); + source = new_source; + } + + SymbolPost symbol_post{ EPSILON }; for (const State lhs_target : filtered_lhs_fast_eps_targets) { for (const State rhs_target : filtered_rhs_fast_eps_targets) { if (lhs_target == lhs_src && rhs_target == rhs_src) { // This is the original state pair. continue; } - result.add_transition_with_target( - composition_state, - EPSILON, - create_composition_state(lhs_target, rhs_target, 0, true), - jump_mode - ); + symbol_post.insert(create_composition_state(lhs_target, rhs_target, 0)); } } + insert_symbol_post_to_delta(source, symbol_post); }; // Initialization of the main worklist. From 2244881d958ca68887d1e02b6ad62f1221aeb9d9 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 18 Sep 2025 14:11:11 +0200 Subject: [PATCH 56/65] add wait optimized --- src/nft/composition.cc | 105 ++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 8433400cb..f83a433a3 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -273,6 +273,44 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le mutable_state_post.insert(std::move(symbol_post)); }; + /** + * @brief Creates a chain of EPSILON transitions of the given length starting from the source state. + * + * @param source The source state from which the EPSILON transitions will start. + * @param common_path_length The length of the EPSILON transition chain to create. + * @return The last state in the created EPSILON transition chain. + */ + auto create_epsilon_transition_with_common_path = [&](const State source, const size_t common_path_length) { + assert(source < result.num_of_states()); + State current_source = source; + Level current_level = result.levels[current_source]; + for (size_t i = 0; i < common_path_length; ++i) { + SymbolPost symbol_post{ EPSILON }; + const State new_composition_state = result.add_state_with_level(++current_level); + assert(current_level < result.num_of_levels); + symbol_post.push_back(new_composition_state); + insert_symbol_post_to_delta(current_source, symbol_post); + current_source = new_composition_state; + } + return current_source; + }; + + /** + * @brief Creates an EPSILON transition from source to target, possibly with a common path in between. + * @param source The source state of the EPSILON transition. + * @param target The target state of the EPSILON transition. + */ + auto create_epsilon_transition_with_target = [&](const State source, const State target) { + assert(source < result.num_of_states() && target < result.num_of_states()); + const Level source_level = result.levels[source]; + const Level target_level = result.levels[target]; + const size_t common_path_length = (target_level == 0 ? result.num_of_levels : target_level) - source_level - 1; + State internal_state = create_epsilon_transition_with_common_path(source, common_path_length); + SymbolPost symbol_post{ EPSILON }; + symbol_post.insert(target); + insert_symbol_post_to_delta(internal_state, symbol_post); + }; + /** * @brief Creates a new composition state for the given pair of states, if it does not already exist. * @@ -365,8 +403,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We know that there is at least one transition in LHS or RHS after the synchronization. // We are going to use those transitions to connect composition_state to their targets. const bool is_remainging_transition_in_lhs = lhs_sync_level + 1 < lhs.num_of_levels; - const bool is_remainging_transition_in_rhs = rhs_sync_level + 1 < rhs.num_of_levels; - assert(is_remainging_transition_in_lhs || is_remainging_transition_in_rhs); + assert(is_remainging_transition_in_lhs || rhs_sync_level + 1 < rhs.num_of_levels); const StateSet& moving_sync_targets = is_remainging_transition_in_lhs ? lhs_sync_targets : rhs_sync_targets; @@ -653,17 +690,19 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // There has to be some EPSILON transition. Otherwise, there would be no reason to wait. assert(epsilon_target_it != running_delta[running_root_state].end()); StateSet new_composition_targets; + State new_composition_state = create_epsilon_transition_with_common_path(composition_root_state, result.num_of_levels - 1); + SymbolPost symbol_post{ EPSILON }; for (const State epsilon_target : epsilon_target_it->targets) { assert(running_levels[epsilon_target] == 0); - new_composition_targets.insert(create_composition_state(waiting_root_state, - epsilon_target, - 0, - is_lhs_waiting)); + symbol_post.insert(create_composition_state(waiting_root_state, + epsilon_target, + 0, + is_lhs_waiting)); } // This function ensures that the transitions are added in the most optimal way. // It creates one common path for all targets and then use only the last transitions of // that path to connect to all targets. - result.add_transition_with_same_level_targets(composition_root_state, EPSILON, new_composition_targets, jump_mode); + insert_symbol_post_to_delta(new_composition_state, symbol_post); return; } @@ -677,7 +716,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Since LHS is waiting and LHS transitions have to be put before // RHS transitions, we now add waiting_trans_before_sync EPSILON // transitions. Doing this here simplifies the conditions in the loop below. - first_composition_state = result.add_transition_with_lenght(composition_root_state, EPSILON, waiting_trans_before_sync, jump_mode); + first_composition_state = create_epsilon_transition_with_common_path(composition_root_state, waiting_trans_before_sync); } worklist.push({ first_composition_state, running_root_state }); @@ -686,7 +725,6 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le auto [composition_state, running_state] = worklist.front(); worklist.pop(); const Level running_state_level = running_levels[running_state]; - const Level composition_state_level = result.levels[composition_state]; // If the synchronization level is on 0. Than we can be careful to not use it // again when encountering next zero-level states in the running NFT. const bool first_running_transition = (running_state == running_root_state && composition_state == first_composition_state); @@ -706,7 +744,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // To reduce the number of redundant EPSILON transitions in the resulting NFT, // we can add N-1 EPSILON transitions now, and then use the last one to connect // to the adequate target state, where N is num_of_epsilons_to_replace_sync. - composition_state = result.add_transition_with_lenght(composition_state, EPSILON, num_of_epsilons_to_replace_sync - 1, jump_mode); + composition_state = create_epsilon_transition_with_common_path(composition_state, num_of_epsilons_to_replace_sync - 1); } // Do the EPSILON synchronization. @@ -715,6 +753,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const auto& running_epsilon_post_it = running_delta[running_state].find(EPSILON); // There should be an EPSILON transition. It has been told by the SynchronizationType. assert(running_epsilon_post_it != running_delta[running_state].end()); + SymbolPost epsilon_symbol_post{ EPSILON }; for (const State running_epsilon_target : running_epsilon_post_it->targets) { if (running_state_level == 0 && running_num_of_levels != 1 && running_levels[running_epsilon_target] == 0) { // Skip fast EPSILON transitions. @@ -730,24 +769,25 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // We are connecting to the zero-level state, therefore we have to // create a new composition state that will be put into the main worklist // (side effect of the create_composition_state). + assert(result.levels[composition_state] + 1 == result.num_of_levels); assert(result.levels[composition_state] + (num_of_epsilons_to_replace_sync >= 1 ? 1 : 0) == result.num_of_levels); - result.add_transition_with_target( - composition_state, - EPSILON, - create_composition_state(waiting_root_state, - running_epsilon_target, - 0, - is_lhs_waiting), - jump_mode - ); + epsilon_symbol_post.insert(create_composition_state(waiting_root_state, + running_epsilon_target, + 0, + is_lhs_waiting)); } else { // We are connecting to the next state in the waiting loop, // so we simply add a transition to the next state and // continue the waiting. - const State new_composition_state = result.add_transition_with_lenght(composition_state, EPSILON, 1, jump_mode); + assert(result.levels[composition_state] + 1 < result.num_of_levels); + const State new_composition_state = result.add_state_with_level(result.levels[composition_state] + 1); + epsilon_symbol_post.push_back(new_composition_state); worklist.push({ new_composition_state, running_epsilon_target }); } } + if (!epsilon_symbol_post.targets.empty()) { + insert_symbol_post_to_delta(composition_state, epsilon_symbol_post); + } } else if (running_state_level == 0 && !first_running_transition) { // We are at a leaf zero-level state in the running NFT. // We will create a connection to the zero-level state in the composition NFT @@ -760,15 +800,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(!is_lhs_waiting); assert(sync_level != running_sync_props.nft.num_of_levels - 1); assert(waiting_trans_after_sync > 0); - assert((composition_state_level + waiting_trans_after_sync) == result.num_of_levels); - result.add_transition_with_target( - composition_state, - EPSILON, - create_composition_state(running_state, - waiting_root_state, - 0), - jump_mode - ); + assert((result.levels[composition_state] + waiting_trans_after_sync) == result.num_of_levels); + create_epsilon_transition_with_target(composition_state, + create_composition_state(running_state, + waiting_root_state, + 0)); }else { // We are at an internal (non-sync) level in the running NFT. // We can simply copy the transitions to the next states. @@ -825,15 +861,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Branch onyl at the last level of that common path. assert(result.num_of_levels > 0); const size_t common_path_len = result_num_of_levels - 1; - State source = composition_state; - for (size_t i = 0; i < common_path_len; ++i) { - SymbolPost symbol_post{ EPSILON }; - State new_source = result.add_state_with_level(static_cast(i + 1)); - symbol_post.push_back(new_source); - insert_symbol_post_to_delta(source, symbol_post); - source = new_source; - } - + State source = create_epsilon_transition_with_common_path(composition_state, common_path_len); + SymbolPost symbol_post{ EPSILON }; for (const State lhs_target : filtered_lhs_fast_eps_targets) { for (const State rhs_target : filtered_rhs_fast_eps_targets) { From 734063a2769b76d220ffbd763d7f1c8d31640966 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Thu, 25 Sep 2025 17:07:04 +0200 Subject: [PATCH 57/65] optimize wait - add state --- src/nft/composition.cc | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index f83a433a3..f69591691 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -243,6 +243,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // Use composition storage without tracking inverted indices, // because waiting in virtual states would spoil the inverse mapping. TwoDimensionalMap composition_storage(lhs_num_of_states, rhs_num_of_states); + std::unordered_map waiting_state_storage; // I use a queue for the worklist to process states in a breadth-first manner. // This helps the branch prediction in the CPU because all processed states // are likely to be at the same level and thus enter the same branch. @@ -274,8 +275,8 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le }; /** - * @brief Creates a chain of EPSILON transitions of the given length starting from the source state. - * + * @brief Creates a chain of EPSILON transitions of the given length starting from the source state. + * * @param source The source state from which the EPSILON transitions will start. * @param common_path_length The length of the EPSILON transition chain to create. * @return The last state in the created EPSILON transition chain. @@ -597,7 +598,13 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // that will not be tracked in the composition_storage. We cannot use composition_storage // because, while waiting in the stationary state, we are not exactly at that state, // but rather in virtual (nonexistent) states of the waiting loop over it. - const State new_composition_state = result.add_state_with_level(new_composition_state_level); + State new_composition_state; + if (waiting_state_storage.contains(copy_target)) { + new_composition_state = waiting_state_storage[copy_target]; + } else { + new_composition_state = result.add_state_with_level(new_composition_state_level); + waiting_state_storage[copy_target] = new_composition_state; + } symbol_post.push_back(new_composition_state); // Sometimes, the running root state gets pushed to the worklist again. We need to handle it during the pop. waiting_worklist->push({ new_composition_state, copy_target }); @@ -645,6 +652,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le const State running_root_state, const bool is_lhs_waiting) { + waiting_state_storage.clear(); const SynchronizationProperties& running_sync_props = is_lhs_waiting ? rhs_sync_props : lhs_sync_props; const SynchronizationProperties& waiting_sync_props = is_lhs_waiting ? lhs_sync_props @@ -780,7 +788,14 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le // so we simply add a transition to the next state and // continue the waiting. assert(result.levels[composition_state] + 1 < result.num_of_levels); - const State new_composition_state = result.add_state_with_level(result.levels[composition_state] + 1); + State new_composition_state; + if (waiting_state_storage.contains(running_epsilon_target)) { + new_composition_state = waiting_state_storage[running_epsilon_target]; + } else { + new_composition_state = result.add_state_with_level(result.levels[composition_state] + 1); + waiting_state_storage[running_epsilon_target] = new_composition_state; + } + // const State new_composition_state = result.add_state_with_level(result.levels[composition_state] + 1); epsilon_symbol_post.push_back(new_composition_state); worklist.push({ new_composition_state, running_epsilon_target }); } @@ -862,7 +877,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(result.num_of_levels > 0); const size_t common_path_len = result_num_of_levels - 1; State source = create_epsilon_transition_with_common_path(composition_state, common_path_len); - + SymbolPost symbol_post{ EPSILON }; for (const State lhs_target : filtered_lhs_fast_eps_targets) { for (const State rhs_target : filtered_rhs_fast_eps_targets) { @@ -977,8 +992,11 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le } } - // Cannot do the trim to remove dead ends, - // because some algorithms work with useless states. + // TODO: Make trim on demand. + if (project_out_sync_levels) { + result.trim(); + } + return result; } From 7a11b0f30afd6933e3b0936e9c7131b51de5a4d0 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 9 Jan 2026 14:32:17 +0100 Subject: [PATCH 58/65] add jump_mode parameter to the redirection to the general compose --- src/nft/composition.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 335443f21..0da9746b3 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -202,7 +202,7 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le assert(lhs_sync_level < lhs.levels.num_of_levels && rhs_sync_level < rhs.levels.num_of_levels); if (jump_mode != JumpMode::NoJump) { - return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels); + return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels, jump_mode); } // Check that there are only explicit synchronization transitions of length 1 with exception for fast EPSILON transitions. From fef7919edc82aa9d6637076cc70749275e2291fa Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 9 Jan 2026 15:50:04 +0100 Subject: [PATCH 59/65] dispatch function for composition --- include/mata/nft/algorithms.hh | 41 +++++++++++++++++++++++++++++++ include/mata/nft/nft.hh | 44 ++++++++++++++++++---------------- include/mata/nft/types.hh | 6 +++++ src/nft/composition.cc | 42 +++++++++++++++++++++++--------- 4 files changed, 102 insertions(+), 31 deletions(-) diff --git a/include/mata/nft/algorithms.hh b/include/mata/nft/algorithms.hh index b30659158..13ec7a213 100644 --- a/include/mata/nft/algorithms.hh +++ b/include/mata/nft/algorithms.hh @@ -132,6 +132,47 @@ Nft product(const Nft& lhs, const Nft& rhs, const std::function& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, + bool project_out_sync_levels = true, + JumpMode jump_mode = JumpMode::RepeatSymbol); + +/** + * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs") with a single synchronization level and no jumps. + * + * @param[in] lhs First transducer to compose. + * @param[in] rhs Second transducer to compose. + * @param[in] lhs_sync_level The synchronization level of the @p lhs. + * @param[in] rhs_sync_level The synchronization level of the @p rhs. + * @param[in] project_out_sync_levels Whether we wont to project out the synchronization levels. + * + * @return A new NFT after the composition. + */ +Nft compose_fast_no_jump(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true); + } #endif diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index 66b271871..e1f77bb5f 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -952,16 +952,6 @@ Nft intersection(const Nft& lhs, const Nft& rhs, /** * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs"). * - * This function computes the composition of two NFTs, `lhs` and `rhs`, by aligning their synchronization levels. - * Transitions between two synchronization levels are ordered as follows: first the transitions of `lhs`, then - * the transitions of `rhs` followed by next synchronization level (if exists). By default, synchronization - * levels are projected out from the resulting NFT. - * - * Vectors of synchronization levels have to be non-empty and of the same size. - * - * NOTE: If you have only a single synchronization level per NFT and don't use jump transitions, consider using - * more efficient overloaded version of this function which takes single synchronization levels instead of vectors. - * * @param[in] lhs First transducer to compose. * @param[in] rhs Second transducer to compose. * @param[in] lhs_sync_levels Ordered vector of synchronization levels of the @p lhs. @@ -969,33 +959,47 @@ Nft intersection(const Nft& lhs, const Nft& rhs, * @param[in] project_out_sync_levels Whether we want to project out the synchronization levels. * @param[in] jump_mode Specifies if the symbol on a jump transition (a transition with a length greater than 1) * is interpreted as a sequence repeating the same symbol or as a single instance of the symbol followed by a sequence of @c DONT_CARE. + * @param[in] composition_mode Mode of composition to use (General of FastNoJump). Default is Auto, + * which selects the best mode based on the presence of jump transitions. + * * @return A new NFT after the composition. */ Nft compose(const Nft& lhs, const Nft& rhs, const utils::OrdVector& lhs_sync_levels, const utils::OrdVector& rhs_sync_levels, bool project_out_sync_levels = true, - JumpMode jump_mode = JumpMode::RepeatSymbol); + JumpMode jump_mode = JumpMode::RepeatSymbol, + CompositionMode composition_mode = CompositionMode::Auto); /** * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs"). * - * NOTE: If you don't have jump transitions, a more efficient computation will be used (without unwinding jumps, - * without creating intermediate NFTs, without aligning synchronization level). - * - * WARNING: If the NFTs use any king of jump transitions, the computation will be redirected - * to the more general and slower version of this function which is similar to the - * one that takes vectors of synchronization levels. - * * @param[in] lhs First transducer to compose. * @param[in] rhs Second transducer to compose. * @param[in] lhs_sync_level The synchronization level of the @p lhs. * @param[in] rhs_sync_level The synchronization level of the @p rhs. - * @param[in] project_out_sync_levels Whether we wont to project out the synchronization levels. + * @param[in] project_out_sync_levels Whether we want to project out the synchronization levels. * @param[in] jump_mode Specifies if the symbol on a jump transition (a transition with a length greater than 1) * is interpreted as a sequence repeating the same symbol or as a single instance of the symbol followed by a sequence of @c DONT_CARE. + * @param[in] composition_mode Mode of composition to use (General of FastNoJump). Default is Auto, + * which selects the best mode based on the presence of jump transitions. + * * @return A new NFT after the composition. */ -Nft compose(const Nft& lhs, const Nft& rhs, Level lhs_sync_level = 1, Level rhs_sync_level = 0, bool project_out_sync_levels = true, JumpMode jump_mode = JumpMode::RepeatSymbol); +inline Nft compose(const Nft& lhs, const Nft& rhs, + const Level lhs_sync_level = 1, const Level rhs_sync_level = 0, + const bool project_out_sync_levels = true, + const JumpMode jump_mode = JumpMode::RepeatSymbol, + const CompositionMode composition_mode = CompositionMode::Auto) +{ + return compose( + lhs, rhs, + utils::OrdVector{ lhs_sync_level }, + utils::OrdVector{ rhs_sync_level }, + project_out_sync_levels, + jump_mode, + composition_mode + ); +} /** * @brief Concatenate two NFTs. diff --git a/include/mata/nft/types.hh b/include/mata/nft/types.hh index 6531b374e..4febdb1b1 100644 --- a/include/mata/nft/types.hh +++ b/include/mata/nft/types.hh @@ -60,6 +60,12 @@ enum class JumpMode { NoJump ///< No jumps are allowed. }; +enum class CompositionMode { + General, ///< General composition mode for arbitrary number or synchronization levels and jump modes. + FastNoJump, ///< Fast composition mode for a single synchronization level and no jumps. + Auto ///< Automatically select the best composition mode based on the parameters. +}; + using ProductFinalStateCondition = mata::nfa::ProductFinalStateCondition; /// An epsilon symbol which is now defined as the maximal value of data type used for symbols. diff --git a/src/nft/composition.cc b/src/nft/composition.cc index 0da9746b3..cf39ffc59 100644 --- a/src/nft/composition.cc +++ b/src/nft/composition.cc @@ -6,6 +6,7 @@ #include #include #include "mata/nft/nft.hh" +#include "mata/nft/algorithms.hh" #include "mata/utils/two-dimensional-map.hh" @@ -197,14 +198,38 @@ namespace { namespace mata::nft { +// Dispatch function for composition modes. +Nft compose(const Nft& lhs, + const Nft& rhs, + const utils::OrdVector& lhs_sync_levels, + const utils::OrdVector& rhs_sync_levels, + const bool project_out_sync_levels, + const JumpMode jump_mode, + const CompositionMode composition_mode) +{ + switch (composition_mode) { + case CompositionMode::FastNoJump: + if (jump_mode != JumpMode::NoJump) { + throw std::invalid_argument("FastNoJump composition mode only supports NoJump jump mode."); + } + if (lhs_sync_levels.size() != 1 || rhs_sync_levels.size() != 1) { + throw std::invalid_argument("FastNoJump composition mode only supports a single synchronization level per NFT."); + } + return algorithms::compose_fast_no_jump(lhs, rhs, lhs_sync_levels.front(), rhs_sync_levels.front(), project_out_sync_levels); + case CompositionMode::Auto: + if (jump_mode == JumpMode::NoJump && lhs_sync_levels.size() == 1) { + return algorithms::compose_fast_no_jump(lhs, rhs, lhs_sync_levels.front(), rhs_sync_levels.front(), project_out_sync_levels); + } + [[fallthrough]]; + case CompositionMode::General: + default: + return algorithms::compose_general(lhs, rhs, lhs_sync_levels, rhs_sync_levels, project_out_sync_levels, jump_mode); + } +} -Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, const bool project_out_sync_levels, const JumpMode jump_mode) { +Nft algorithms::compose_fast_no_jump(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Level rhs_sync_level, const bool project_out_sync_levels) { assert(lhs_sync_level < lhs.levels.num_of_levels && rhs_sync_level < rhs.levels.num_of_levels); - if (jump_mode != JumpMode::NoJump) { - return compose(lhs, rhs, OrdVector{ lhs_sync_level }, OrdVector{ rhs_sync_level }, project_out_sync_levels, jump_mode); - } - // Check that there are only explicit synchronization transitions of length 1 with exception for fast EPSILON transitions. assert( std::all_of( @@ -1001,15 +1026,10 @@ Nft compose(const Nft& lhs, const Nft& rhs, const Level lhs_sync_level, const Le return result; } -Nft compose(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { +Nft algorithms::compose_general(const Nft& lhs, const Nft& rhs, const OrdVector& lhs_sync_levels, const OrdVector& rhs_sync_levels, bool project_out_sync_levels, const JumpMode jump_mode) { assert(!lhs_sync_levels.empty()); assert(lhs_sync_levels.size() == rhs_sync_levels.size()); - // if (lhs_sync_levels.size() == 1 && rhs_sync_levels.size() == 1) { - // // If we have only one synchronization level we can do it faster. - // return compose(lhs, rhs, lhs_sync_levels.front(), lhs_sync_levels.front(), project_out_sync_levels, jump_mode); - // } - // Inserts loop into the given Nft for each state with level 0. // The loop word is constructed using the EPSILON symbol for all levels, except for the levels // where is_dont_care_on_transition is true, in which case the DONT_CARE symbol is used. From 163cc2f6210585291dd6650c0864537c8ae463b9 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 9 Jan 2026 15:54:02 +0100 Subject: [PATCH 60/65] noodlify and replace_reluctant with JumpMode::NoJump --- src/applications/strings/noodlification.cc | 8 ++++---- src/applications/strings/replace.cc | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/applications/strings/noodlification.cc b/src/applications/strings/noodlification.cc index 824dbf770..ffe74776c 100644 --- a/src/applications/strings/noodlification.cc +++ b/src/applications/strings/noodlification.cc @@ -469,9 +469,9 @@ std::vector seg_nfa::noodlify_for_transducer( // we can do the following optimization. Let I1, ..., In be the input automata. Because T is homomorphic, // we can take the concatenation T(I1).T(I2)...T(In) connected with INPUT_DELIMITER instead of computing // the composition with concatenated_input_nft. - mata::nft::Nft concatenation{ mata::nft::compose(mata::nft::Nft(*input_automata[0]), *nft, 0, 0, false) }; + mata::nft::Nft concatenation{ mata::nft::compose(mata::nft::Nft(*input_automata[0]), *nft, 0, 0, false, mata::nft::JumpMode::NoJump) }; for (size_t i = 1; i < input_automata.size(); ++i) { - mata::nft::Nft composition = mata::nft::compose(mata::nft::Nft(*input_automata[i]), *nft, 0, 0, false); + mata::nft::Nft composition = mata::nft::compose(mata::nft::Nft(*input_automata[i]), *nft, 0, 0, false, mata::nft::JumpMode::NoJump); concatenation = mata::nft::algorithms::concatenate_eps(concatenation, composition, input_delimiter, true); } intersection = std::move(concatenation); @@ -480,7 +480,7 @@ std::vector seg_nfa::noodlify_for_transducer( // we intersect input nfa with nft on the input track, but we need to add INPUT_DELIMITER as an "epsilon transition" of nft add_self_loop_for_every_default_state(intersection, input_delimiter); - intersection = mata::nft::compose(concatenated_input_nft, intersection, 0, 0, false); + intersection = mata::nft::compose(concatenated_input_nft, intersection, 0, 0, false, mata::nft::JumpMode::NoJump); } intersection.trim(); @@ -491,7 +491,7 @@ std::vector seg_nfa::noodlify_for_transducer( // We also need to INPUT_DELIMITER as "epsilon transition" of the output nfa, so that we do not lose it. add_self_loop_for_every_default_state(concatenated_output_nft, input_delimiter); add_self_loop_for_every_default_state(intersection, output_delimiter); - intersection = mata::nft::compose(concatenated_output_nft, intersection, 0, 1, false); + intersection = mata::nft::compose(concatenated_output_nft, intersection, 0, 1, false, mata::nft::JumpMode::NoJump); intersection.trim(); if (intersection.final.empty()) { return {}; } diff --git a/src/applications/strings/replace.cc b/src/applications/strings/replace.cc index aed588ae6..075ee57a2 100644 --- a/src/applications/strings/replace.cc +++ b/src/applications/strings/replace.cc @@ -516,7 +516,7 @@ Nft ReluctantReplace::replace_regex(nfa::Nfa aut, const Word& replacement, Alpha const Nft dft_begin_marker{ begin_marker_nft(begin_marker_nfa(aut, alphabet), begin_marker) }; const Nft nft_reluctant_replace{ reluctant_leftmost_nft(std::move(aut), alphabet, begin_marker, replacement, replace_mode) }; - return compose(dft_begin_marker, nft_reluctant_replace); + return compose(dft_begin_marker, nft_reluctant_replace, 1, 0, true, mata::nft::JumpMode::NoJump); } Nft ReluctantReplace::replace_literal(const Word& literal, const Word& replacement, Alphabet const* const alphabet, @@ -551,7 +551,7 @@ Nft ReluctantReplace::replace_literal(const Word& literal, const Word& replaceme const Nft nft_literal_replace{ replace_literal_nft(literal, replacement, alphabet, end_marker, replace_mode) }; - return compose(nft_end_marker, nft_literal_replace); + return compose(nft_end_marker, nft_literal_replace, 1, 0, true, mata::nft::JumpMode::NoJump); } Nft ReluctantReplace::replace_symbol( From 7ea30db606eff3e776175ca9716f3154c7e36814 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 9 Jan 2026 16:04:00 +0100 Subject: [PATCH 61/65] add JumpMode::NoJump to calls of apply --- examples/nft.cc | 2 +- tests/nft/nft.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/nft.cc b/examples/nft.cc index 70f9721f3..a9ac513f1 100644 --- a/examples/nft.cc +++ b/examples/nft.cc @@ -37,7 +37,7 @@ int main() { Nfa nfa = nfa::builder::create_from_regex("ABCDEFggg"); // Compute the pre-image of an NFA (applies NFA to the image tape, i.e., tape 1) - Nft backward_applied_nft = nft.apply(nfa, 1); + Nft backward_applied_nft = nft.apply(nfa, 1, true, mata::nft::JumpMode::NoJump); // Extract a pre-image NFA. Nfa nfa_pre_image{ backward_applied_nft.to_nfa_move() }; // Minimize the result pre-image using hopcroft minimization. diff --git a/tests/nft/nft.cc b/tests/nft/nft.cc index 29ebdead2..3f2fe8eee 100644 --- a/tests/nft/nft.cc +++ b/tests/nft/nft.cc @@ -5505,7 +5505,7 @@ TEST_CASE("mata::nft::Nft::apply()") { Nfa nfa = nfa::builder::create_from_regex("da+b+ce"); mata::EnumAlphabet alphabet{ 'a', 'b', 'c', 'd', 'e', 'f' }; Nft nft{ mata::applications::strings::replace::replace_reluctant_regex("a+b+c", { 'f' }, &alphabet) }; - Nft nft_applied_nfa{ nft.apply(nfa, 0) }; + Nft nft_applied_nfa{ nft.apply(nfa, 0, true, mata::nft::JumpMode::NoJump) }; Nfa result{ nft_applied_nfa.to_nfa_move() }; result.remove_epsilon(); result.trim(); From 6f32caa260aa9219a40c2a62169e02fd71878af3 Mon Sep 17 00:00:00 2001 From: koniksedy Date: Fri, 9 Jan 2026 16:06:14 +0100 Subject: [PATCH 62/65] add comment --- include/mata/nft/nft.hh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index e1f77bb5f..e3f72ac43 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -959,8 +959,9 @@ Nft intersection(const Nft& lhs, const Nft& rhs, * @param[in] project_out_sync_levels Whether we want to project out the synchronization levels. * @param[in] jump_mode Specifies if the symbol on a jump transition (a transition with a length greater than 1) * is interpreted as a sequence repeating the same symbol or as a single instance of the symbol followed by a sequence of @c DONT_CARE. - * @param[in] composition_mode Mode of composition to use (General of FastNoJump). Default is Auto, - * which selects the best mode based on the presence of jump transitions. + * @param[in] composition_mode Mode of composition to use. Default is Auto, which selects the best mode based + * on the presence of jump transitions. General mode supports all NFTs, while FastNoJump + * mode is optimized for NFTs without jump transitions and with only one synchronization level. * * @return A new NFT after the composition. */ @@ -980,8 +981,9 @@ Nft compose(const Nft& lhs, const Nft& rhs, * @param[in] project_out_sync_levels Whether we want to project out the synchronization levels. * @param[in] jump_mode Specifies if the symbol on a jump transition (a transition with a length greater than 1) * is interpreted as a sequence repeating the same symbol or as a single instance of the symbol followed by a sequence of @c DONT_CARE. - * @param[in] composition_mode Mode of composition to use (General of FastNoJump). Default is Auto, - * which selects the best mode based on the presence of jump transitions. + * @param[in] composition_mode Mode of composition to use. Default is Auto, which selects the best mode based + * on the presence of jump transitions. General mode supports all NFTs, while FastNoJump + * mode is optimized for NFTs without jump transitions and with only one synchronization level. * * @return A new NFT after the composition. */ From 7ad9f672bc8be6514e653949b5709b9826558640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ed=C3=BD?= <55767047+koniksedy@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:31:35 +0100 Subject: [PATCH 63/65] Update include/mata/nft/nft.hh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Chocholatý --- include/mata/nft/nft.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index e3f72ac43..4500b437b 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -350,7 +350,7 @@ public: * @param length The length of the transition. * @param jump_mode Specifies the semantic of jump transitions (transitions with a length greater than 1). */ - State add_transition_with_lenght(State source, Symbol symbol, size_t length, JumpMode jump_mode = JumpMode::RepeatSymbol); + State add_transition_with_length(State source, Symbol symbol, size_t length, JumpMode jump_mode = JumpMode::RepeatSymbol); /** * @brief Add a single NFT transition from a source state @p source to a target state @p target From a89bddcd49233794a1cb6a2517e068053650b46c Mon Sep 17 00:00:00 2001 From: koniksedy Date: Wed, 14 Jan 2026 14:32:41 +0100 Subject: [PATCH 64/65] fix type in nft.cc --- src/nft/nft.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nft/nft.cc b/src/nft/nft.cc index e7a033af6..003ec72b0 100644 --- a/src/nft/nft.cc +++ b/src/nft/nft.cc @@ -667,7 +667,7 @@ State Nft::add_transition(const State source, const std::vector& symbols return insert_word(source, symbols); } -State Nft::add_transition_with_lenght(const State source, const Symbol symbol, const size_t length, const JumpMode jump_mode) { +State Nft::add_transition_with_length(const State source, const Symbol symbol, const size_t length, const JumpMode jump_mode) { assert(source < num_of_states()); if (length == 0) { return source; } From a17e19aeb40681d27289773da7b382f5667949ae Mon Sep 17 00:00:00 2001 From: jurajsic Date: Sun, 5 Apr 2026 18:46:05 +0200 Subject: [PATCH 65/65] update explanation of compose functions --- include/mata/nft/algorithms.hh | 30 +++++++++++++++++++++++------- include/mata/nft/nft.hh | 27 +++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/include/mata/nft/algorithms.hh b/include/mata/nft/algorithms.hh index 13ec7a213..3bd4bce3f 100644 --- a/include/mata/nft/algorithms.hh +++ b/include/mata/nft/algorithms.hh @@ -133,14 +133,23 @@ Nft concatenate_eps(const Nft& lhs, const Nft& rhs, const Symbol& epsilon, bool StateRenaming* lhs_state_renaming = nullptr, StateRenaming* rhs_state_renaming = nullptr); /** - * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs"). + * @brief Composes two NFTs. * * This function computes the composition of two NFTs, `lhs` and `rhs`, by aligning their synchronization levels. - * Transitions between two synchronization levels are ordered as follows: first the transitions of `lhs`, then - * the transitions of `rhs` followed by next synchronization level (if exists). By default, synchronization - * levels are projected out from the resulting NFT. - * - * Vectors of synchronization levels have to be non-empty and of the same size. + * Vectors of synchronization levels must be non-empty and of the same size. The levels of the resulting NFT + * are formed, iteratively, from the non-synchronizing levels of `lhs`, followed by the non-synchronizing levels of + * `rhs`, followed by the next synchronizing level (if not projected out by setting project_out_sync_levels to true). + * For example, given `lhs` with 7 levels, `rhs` with 8 levels, lhs_sync_levels=<0,2,5>, rhs_sync_levels=<1,4,5>, the + * resulting NFT levels will correspond to (assuming that we are not projecting out the synchronized levels) + * - `rhs` level 0 + * - synchronized level from level 0 of `lhs` and level 1 of `rhs` + * - `lhs` level 1 + * - `rhs` levels 2 and 3 + * - synchronized level from level 2 of `lhs` and level 4 of `rhs` + * - `lhs` levels 3 and 4 + * - synchronized level from level 5 of `lhs` and level 5 of `rhs` + * - `lhs` level 6 + * - `rhs` levels 6 and 7 * * NOTE: If you have only a single synchronization level per NFT and don't use jump transitions, consider using * no-jump varsion of the composition for better performance. @@ -161,7 +170,14 @@ Nft compose_general(const Nft& lhs, const Nft& rhs, JumpMode jump_mode = JumpMode::RepeatSymbol); /** - * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs") with a single synchronization level and no jumps. + * @brief Composes two NFTs with a single synchronization level and no jumps. + * + * The levels of the resulting NFT are in the following order: + * - levels of `lhs` before its synvhronization level + * - levels of `rhs` before its synvhronization level + * - the synchronizing level (possibly missing if project_out_sync_levels is true) + * - levels of `lhs` after its synvhronization level + * - levels of `rhs` after its synvhronization level * * @param[in] lhs First transducer to compose. * @param[in] rhs Second transducer to compose. diff --git a/include/mata/nft/nft.hh b/include/mata/nft/nft.hh index 4500b437b..07f220afe 100644 --- a/include/mata/nft/nft.hh +++ b/include/mata/nft/nft.hh @@ -950,7 +950,23 @@ Nft intersection(const Nft& lhs, const Nft& rhs, State lhs_first_aux_state = Limits::max_state, State rhs_first_aux_state = Limits::max_state); /** - * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs"). + * @brief Composes two NFTs. + * + * This function computes the composition of two NFTs, `lhs` and `rhs`, by aligning their synchronization levels. + * Vectors of synchronization levels must be non-empty and of the same size. The levels of the resulting NFT + * are formed, iteratively, from the non-synchronizing levels of `lhs`, followed by the non-synchronizing levels of + * `rhs`, followed by the next synchronizing level (if not projected out by setting project_out_sync_levels to true). + * For example, given `lhs` with 7 levels, `rhs` with 8 levels, lhs_sync_levels=<0,2,5>, rhs_sync_levels=<1,4,5>, the + * resulting NFT levels will correspond to (assuming that we are not projecting out the synchronized levels) + * - `rhs` level 0 + * - synchronized level from level 0 of `lhs` and level 1 of `rhs` + * - `lhs` level 1 + * - `rhs` levels 2 and 3 + * - synchronized level from level 2 of `lhs` and level 4 of `rhs` + * - `lhs` levels 3 and 4 + * - synchronized level from level 5 of `lhs` and level 5 of `rhs` + * - `lhs` level 6 + * - `rhs` levels 6 and 7 * * @param[in] lhs First transducer to compose. * @param[in] rhs Second transducer to compose. @@ -972,8 +988,15 @@ Nft compose(const Nft& lhs, const Nft& rhs, CompositionMode composition_mode = CompositionMode::Auto); /** - * @brief Composes two NFTs (lhs || rhs; read as "rhs after lhs"). + * @brief Composes two NFTs with a single synchronization level. * + * The levels of the resulting NFT are in the following order: + * - levels of `lhs` before its synvhronization level + * - levels of `rhs` before its synvhronization level + * - the synchronizing level (possibly missing if project_out_sync_levels is true) + * - levels of `lhs` after its synvhronization level + * - levels of `rhs` after its synvhronization level + * * @param[in] lhs First transducer to compose. * @param[in] rhs Second transducer to compose. * @param[in] lhs_sync_level The synchronization level of the @p lhs.