// -*- coding: utf-8 -*- // Copyright (C) 2017-2022 Laboratoire de Recherche et Developpement // de l'Epita (LRDE). // // This file is part of Spot, a model checking library. // // Spot is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // Spot is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public // License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "config.h" #include #include namespace spot { namespace { enum genem_version_t { spot28, atva19, spot29, spot210, spot211 }; static genem_version_t genem_version = spot29; } void generic_emptiness_check_select_version(const char* emversion) { if (emversion == nullptr || !strcasecmp(emversion, "spot29")) genem_version = spot29; else if (!strcasecmp(emversion, "spot211")) genem_version = spot211; else if (!strcasecmp(emversion, "spot210")) genem_version = spot210; else if (!strcasecmp(emversion, "spot28")) genem_version = spot28; else if (!strcasecmp(emversion, "atva19")) genem_version = atva19; else throw std::invalid_argument("generic_emptiness_check version should be " "one of {spot28, atva19, spot29, spot210, " "spot211}"); } namespace { template static bool is_scc_empty(const scc_info& si, unsigned scc, const acc_cond& autacc, Extra extra, acc_cond::mark_t tocut = {}); template static bool scc_split_check_filtered(const scc_info& upper_si, const acc_cond& acc, Extra extra, acc_cond::mark_t tocut) { if constexpr (EarlyStop) { const int accepting_scc = upper_si.one_accepting_scc(); if (accepting_scc >= 0) { if (extra) upper_si.get_accepting_run(accepting_scc, extra); return false; } if (!acc.uses_fin_acceptance()) return true; } unsigned nscc = upper_si.scc_count(); for (unsigned scc = 0; scc < nscc; ++scc) if (!is_scc_empty(upper_si, scc, acc, extra, tocut)) if constexpr (EarlyStop) return false; return true; } template static bool scc_split_check(const scc_info& si, unsigned scc, const acc_cond& acc, Extra extra, acc_cond::mark_t tocut) { if (genem_version == spot211 || genem_version == spot210) tocut |= acc.fin_unit(); scc_and_mark_filter filt(si, scc, tocut); filt.override_acceptance(acc); scc_info upper_si(filt, EarlyStop ? scc_info_options::STOP_ON_ACC : scc_info_options::TRACK_STATES); return scc_split_check_filtered(upper_si, acc, extra, tocut); } template static bool is_scc_empty(const scc_info& si, unsigned scc, const acc_cond& autacc, Extra extra, acc_cond::mark_t tocut) { if (si.is_rejecting_scc(scc)) return true; if constexpr (!EarlyStop) if (si.is_maximally_accepting_scc(scc)) { extra(si, scc); return false; } acc_cond::mark_t sets = si.acc_sets_of(scc); acc_cond acc = autacc.restrict_to(sets); acc = acc.remove(si.common_sets_of(scc), false); if (SPOT_LIKELY(genem_version == spot210 || !EarlyStop)) do { auto [fo, fpart] = acc.fin_one_extract(); assert(fo >= 0); // Try to accept when Fin(fo) == true acc_cond::mark_t fo_m = {(unsigned) fo}; if (!scc_split_check (si, scc, fpart, extra, fo_m)) if constexpr (EarlyStop) return false; // Try to accept when Fin(fo) == false acc = acc.force_inf(fo_m); } while (!acc.is_f()); else if (genem_version == spot211) { do { auto [fo, fpart, rest] = acc.fin_unit_one_split(); acc_cond::mark_t fo_m = {(unsigned) fo}; if (!scc_split_check (si, scc, fpart, extra, fo_m)) if constexpr (EarlyStop) return false; acc = rest; } while (!acc.is_f()); } else if (genem_version == spot29) do { acc_cond::acc_code rest = acc_cond::acc_code::f(); for (const acc_cond& disjunct: acc.top_disjuncts()) if (acc_cond::mark_t fu = disjunct.fin_unit()) { if (!scc_split_check (si, scc, disjunct.remove(fu, true), extra, fu)) if constexpr (EarlyStop) return false; } else { rest |= disjunct.get_acceptance(); } if (rest.is_f()) break; acc_cond subacc(acc.num_sets(), std::move(rest)); int fo = subacc.fin_one(); assert(fo >= 0); // Try to accept when Fin(fo) == true acc_cond::mark_t fo_m = {(unsigned) fo}; if (!scc_split_check (si, scc, subacc.remove(fo_m, true), extra, fo_m)) if constexpr (EarlyStop) return false; // Try to accept when Fin(fo) == false acc = subacc.force_inf(fo_m); } while (!acc.is_f()); else { for (const acc_cond& disjunct: acc.top_disjuncts()) if (acc_cond::mark_t fu = disjunct.fin_unit()) { if (!scc_split_check (si, scc, disjunct.remove(fu, true), extra, fu)) if constexpr (EarlyStop) return false; } else { int fo = (SPOT_UNLIKELY(genem_version == spot28) ? acc.fin_one() : disjunct.fin_one()); assert(fo >= 0); // Try to accept when Fin(fo) == true acc_cond::mark_t fo_m = {(unsigned) fo}; if (!scc_split_check (si, scc, disjunct.remove(fo_m, true), extra, fo_m)) if constexpr (EarlyStop) return false; // Try to accept when Fin(fo) == false if (!is_scc_empty (si, scc, disjunct.force_inf(fo_m), extra, tocut)) if constexpr (EarlyStop) return false; } } return true; } static bool generic_emptiness_check_main(const twa_graph_ptr& aut, twa_run_ptr run) { // We used to call cleanup_acceptance_here(aut, false), // but it turns out this is usually a waste of time. auto& aut_acc = aut->acc(); if (aut_acc.is_f()) return true; if (!aut_acc.uses_fin_acceptance()) { if (!run) return aut->is_empty(); if (auto p = aut->accepting_run()) { *run = *p; return false; } return true; } // Filter with fin_unit() right away if possible. // scc_and_mark_filter will have no effect if fin_unit() is // empty. scc_and_mark_filter filt(aut, aut_acc.fin_unit()); scc_info si(filt, scc_info_options::STOP_ON_ACC); const int accepting_scc = si.one_accepting_scc(); if (accepting_scc >= 0) { if (run) si.get_accepting_run(accepting_scc, run); return false; } unsigned nscc = si.scc_count(); for (unsigned scc = 0; scc < nscc; ++scc) if (!is_scc_empty(si, scc, aut_acc, run)) return false; return true; } } bool generic_emptiness_check(const const_twa_graph_ptr& aut) { if (SPOT_UNLIKELY(!aut->is_existential())) throw std::runtime_error("generic_emptiness_check() " "does not support alternating automata"); auto aut_ = std::const_pointer_cast(aut); acc_cond old = aut_->acc(); bool res = generic_emptiness_check_main(aut_, nullptr); aut_->set_acceptance(old); return res; } twa_run_ptr generic_accepting_run(const const_twa_graph_ptr& aut) { if (SPOT_UNLIKELY(!aut->is_existential())) throw std::runtime_error("generic_accepting_run() " "does not support alternating automata"); auto aut_ = std::const_pointer_cast(aut); acc_cond old = aut_->acc(); twa_run_ptr run = std::make_shared(aut_); bool res = generic_emptiness_check_main(aut_, run); aut_->set_acceptance(old); if (!res) return run; return nullptr; } bool generic_emptiness_check_for_scc(const scc_info& si, unsigned scc) { if (si.is_accepting_scc(scc)) return false; return is_scc_empty (si, scc, si.get_aut()->acc(), nullptr); } bool generic_emptiness_check_for_scc(const scc_info& si, unsigned scc, const acc_cond& forced_acc) { if (si.is_trivial(scc)) return true; return scc_split_check (si, scc, forced_acc, nullptr, {}); } bool maximal_accepting_loops_for_scc(const scc_info& si, unsigned scc, const acc_cond& forced_acc, const bitvect& keep, std::function callback) { if (si.is_trivial(scc)) return false; scc_and_mark_filter filt(si, scc, {}, keep); filt.override_acceptance(forced_acc); scc_info upper_si(filt, scc_info_options::TRACK_STATES); return !scc_split_check_filtered(upper_si, forced_acc, callback, {}); } }