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
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:
- The spot.automata() and spot.automaton() functions now accept a

1
THANKS
View file

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

View file

@ -23,6 +23,8 @@
#include <spot/twaalgos/minimize.hh>
#include <spot/twaalgos/isdet.hh>
#include <spot/twaalgos/sccfilter.hh>
#include <spot/twaalgos/contains.hh>
#include <spot/twaalgos/stripacc.hh>
using namespace std::string_literals;
@ -182,23 +184,55 @@ namespace spot
{
if (aut->acc().is_t())
return true;
if (!aut->is_existential())
throw std::runtime_error
("is_safety_automaton() does not support alternation");
bool need_si = !si;
if (need_si)
si = new scc_info(aut);
std::unique_ptr<scc_info> localsi;
if (!si)
{
localsi = std::make_unique<scc_info>(aut);
si = localsi.get();
}
si->determine_unknown_acceptance();
bool res = true;
unsigned scount = si->scc_count();
for (unsigned scc = 0; scc < scount; ++scc)
if (!si->is_trivial(scc) && si->is_rejecting_scc(scc))
// a trim automaton without rejecting cycle is a safety automaton
bool has_rejecting_cycle = false;
// 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;
}
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)
delete si;
return res;
// If the automaton has a rejecting loop and is deterministic, it
// cannot be a safety automaton.
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.
///
/// A safety automaton has only accepting SCCs (or trivial
/// SCCs).
/// An automaton is a safety automaton if its acceptance condition
/// can be changed to "true" without changing its language.
///
/// A minimized WDBA (as returned by a successful run of
/// minimize_obligation()) represents safety property if it is a
/// safety automaton.
/// The test performed by this function differs depending on
/// the nature of the input \a aut.
///
/// 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
///

View file

@ -446,6 +446,7 @@ TESTS_python = \
python/relabel.py \
python/remfin.py \
python/removeap.py \
python/safety.py \
python/satmin.py \
python/sbacc.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))