strength: generalize is_safety_automaton to any type of automata

Reported by Samuel Judson.

* spot/twaalgos/strength.cc (is_safety_automaton): Reimplement it.
* spot/twaalgos/strength.hh (is_safety_automaton): Update
documentation.
* tests/python/safety.py: New file.
* tests/Makefile.am: Add it.
* NEWS: Mention this change.
* THANKS: Add Samuel.
This commit is contained in:
Alexandre Duret-Lutz 2023-12-16 00:09:24 +01:00
parent e8c2b27ad2
commit 983964d037
6 changed files with 122 additions and 16 deletions

6
NEWS
View file

@ -120,6 +120,12 @@ New in spot 2.11.6.dev (not yet released)
- ltsmin's interface will now point to README.ltsmin in case an - ltsmin's interface will now point to README.ltsmin in case an
error is found while running divine or spins. error is found while running divine or spins.
- spot::is_safety_automaton() was generalized to detect any
automaton for which the acceptance could be changed to "t" without
changing the language. In previous versions this function assumed
weak automata as input, but the documentation did not reflect
this.
Python: Python:
- The spot.automata() and spot.automaton() functions now accept a - The spot.automata() and spot.automaton() functions now accept a

1
THANKS
View file

@ -56,6 +56,7 @@ Raven Beutner
Reuben Rowe Reuben Rowe
Roei Nahum Roei Nahum
Rüdiger Ehlers Rüdiger Ehlers
Samuel Judson
Shachar Itzhaky Shachar Itzhaky
Shengping Shaw Shengping Shaw
Shufang Zhu Shufang Zhu

View file

@ -23,6 +23,8 @@
#include <spot/twaalgos/minimize.hh> #include <spot/twaalgos/minimize.hh>
#include <spot/twaalgos/isdet.hh> #include <spot/twaalgos/isdet.hh>
#include <spot/twaalgos/sccfilter.hh> #include <spot/twaalgos/sccfilter.hh>
#include <spot/twaalgos/contains.hh>
#include <spot/twaalgos/stripacc.hh>
using namespace std::string_literals; using namespace std::string_literals;
@ -182,23 +184,55 @@ namespace spot
{ {
if (aut->acc().is_t()) if (aut->acc().is_t())
return true; return true;
if (!aut->is_existential())
throw std::runtime_error
("is_safety_automaton() does not support alternation");
bool need_si = !si; std::unique_ptr<scc_info> localsi;
if (need_si) if (!si)
si = new scc_info(aut); {
localsi = std::make_unique<scc_info>(aut);
si = localsi.get();
}
si->determine_unknown_acceptance();
bool res = true; // a trim automaton without rejecting cycle is a safety automaton
unsigned scount = si->scc_count(); bool has_rejecting_cycle = false;
for (unsigned scc = 0; scc < scount; ++scc)
if (!si->is_trivial(scc) && si->is_rejecting_scc(scc)) // first, look for rejecting SCCs.
unsigned scccount = si->scc_count();
for (unsigned scc = 0; scc < scccount; ++scc)
if (si->is_useful_scc(scc)
&& !si->is_trivial(scc)
&& si->is_rejecting_scc(scc))
{ {
res = false; has_rejecting_cycle = true;
break; break;
} }
if (!has_rejecting_cycle && !aut->prop_inherently_weak().is_true())
{
// maybe we have rejecting cycles inside accepting SCCs?
for (unsigned scc = 0; scc < scccount; ++scc)
if (si->is_useful_scc(scc)
&& !si->is_trivial(scc)
&& si->is_accepting_scc(scc)
&& scc_has_rejecting_cycle(*si, scc))
{
has_rejecting_cycle = true;
break;
}
}
if (!has_rejecting_cycle)
return true;
if (need_si) // If the automaton has a rejecting loop and is deterministic, it
delete si; // cannot be a safety automaton.
return res; if (is_universal(aut))
return false;
twa_graph_ptr b = make_twa_graph(aut, twa::prop_set::all());
strip_acceptance_here(b);
return spot::contains(aut, b);
} }

View file

@ -104,12 +104,24 @@ namespace spot
/// \brief Check whether an automaton is a safety automaton. /// \brief Check whether an automaton is a safety automaton.
/// ///
/// A safety automaton has only accepting SCCs (or trivial /// An automaton is a safety automaton if its acceptance condition
/// SCCs). /// can be changed to "true" without changing its language.
/// ///
/// A minimized WDBA (as returned by a successful run of /// The test performed by this function differs depending on
/// minimize_obligation()) represents safety property if it is a /// the nature of the input \a aut.
/// safety automaton. ///
/// If \a aut is an automaton with `t` acceptance, it is necessarily
/// a safety automaton.
///
/// Else we check for the absence of rejecting cycle in the
/// useful part of the automaton. This absence is only a sufficient
/// condition in the non-deterministic case, because a rejecting
/// run might correspond to a word that is accepted by another run.
///
/// If the previous test could not conclude, we build the automaton
/// B that is a copy of \a aut with acceptance set to true, and we
/// check that \a aut contains all words of B. This last test
/// requires complementing \a aut.
/// ///
/// \param aut the automaton to check /// \param aut the automaton to check
/// ///

View file

@ -446,6 +446,7 @@ TESTS_python = \
python/relabel.py \ python/relabel.py \
python/remfin.py \ python/remfin.py \
python/removeap.py \ python/removeap.py \
python/safety.py \
python/satmin.py \ python/satmin.py \
python/sbacc.py \ python/sbacc.py \
python/sccfilter.py \ python/sccfilter.py \

52
tests/python/safety.py Normal file
View file

@ -0,0 +1,52 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) by the Spot authors, see the AUTHORS file for details.
#
# 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 <http://www.gnu.org/licenses/>.
import spot
from unittest import TestCase
tc = TestCase()
for f in ['Fb', 'GFa & GFb',
'(p0 W Fp0) R ((Gp1 & Fp1) | (F!p1 & G!p1)) & GFp0',
'GFp0 xor GFp1']:
aut = spot.translate(f, 'BA')
tc.assertFalse(spot.is_safety_automaton(aut))
aut = spot.translate(f, 'BA')
tc.assertFalse(spot.is_safety_automaton(aut))
aut = spot.translate(f, 'deterministic', 'complete')
tc.assertFalse(spot.is_safety_automaton(aut))
aut = spot.translate(f, 'generic', 'sbacc')
tc.assertFalse(spot.is_safety_automaton(aut))
for f in ['Gb', 'Ga|Gb|Gc', 'Fr->(!p U r)', 'p1 M F(p1 U (Gp0 U X(0)))',
'((p1 U !p0) M !FXp1) W p0', 'p0 & ((!p1 | (p1 W X!p1)) M p1)',
'(p0 W Fp0) R ((Gp1 & Fp1) | (F!p1 & G!p1))']:
aut = spot.translate(f, 'BA')
tc.assertTrue(spot.is_safety_automaton(aut))
ba = spot.translate(f, 'BA', 'complete')
tc.assertTrue(spot.is_safety_automaton(aut))
ba = spot.translate(f, 'deterministic', 'complete')
tc.assertTrue(spot.is_safety_automaton(aut))
ba = spot.translate(f, 'generic', 'sbacc')
tc.assertTrue(spot.is_safety_automaton(aut))