twa_run: let as_twa work on the result of intersecting_run

Reported by Philipp Schlehuber-Caissier.

* spot/twaalgos/emptiness.cc (as_twa): Simplify considerably.  Don't
try to replay the run, and don't merge identical states.
* spot/twaalgos/word.hh, spot/twaalgos/emptiness.hh: Improve
documentation.
* tests/python/intrun.py: Add a test case.
* NEWS: Mention the bug.
This commit is contained in:
Alexandre Duret-Lutz 2023-04-18 14:48:10 +02:00
parent d152b3a316
commit ae10361bdd
5 changed files with 73 additions and 60 deletions

5
NEWS
View file

@ -25,6 +25,11 @@ New in spot 2.11.4.dev (not yet released)
- Building from the git repository would fail to report a missing - Building from the git repository would fail to report a missing
emacs (issue #528). emacs (issue #528).
- Fix exception raised by aut1.intersecting_run(aut2).as_twa()
because the run did not match transitions present in aut1
verbatim. We also changed the behavior of as_twa() to not merge
identical states.
New in spot 2.11.4 (2023-02-10) New in spot 2.11.4 (2023-02-10)
Python: Python:

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2009, 2011-2019, 2021 Laboratoire de Recherche et // Copyright (C) 2009, 2011-2019, 2021, 2023 Laboratoire de Recherche et
// Développement de l'Epita (LRDE). // Développement de l'Epita (LRDE).
// Copyright (C) 2004, 2005 Laboratoire d'Informatique de Paris 6 (LIP6), // Copyright (C) 2004, 2005 Laboratoire d'Informatique de Paris 6 (LIP6),
// département Systèmes Répartis Coopératifs (SRC), Université Pierre // département Systèmes Répartis Coopératifs (SRC), Université Pierre
@ -570,7 +570,7 @@ namespace spot
if (debug) if (debug)
os << "ERROR: First state of run (in " << in << "): " os << "ERROR: First state of run (in " << in << "): "
<< aut->format_state(i->s) << aut->format_state(i->s)
<< "\ndoes not match initial state of automata: " << "\ndoes not match initial state of automaton: "
<< aut->format_state(s) << '\n'; << aut->format_state(s) << '\n';
s->destroy(); s->destroy();
return false; return false;
@ -802,38 +802,38 @@ namespace spot
res->set_named_prop("state-names", names); res->set_named_prop("state-names", names);
} }
const state* s = aut->get_init_state();
unsigned src; unsigned src;
unsigned dst; unsigned dst;
const twa_run::steps* l; const twa_run::steps* l;
acc_cond::mark_t seen_acc = {}; unsigned cycle_entry = 0;
state_map<unsigned> seen;
if (prefix.empty()) if (prefix.empty())
l = &cycle; l = &cycle;
else else
l = &prefix; l = &prefix;
twa_run::steps::const_iterator i = l->begin(); twa_run::steps::const_iterator i = l->begin();
assert(s->compare(i->s) == 0); #if NDEBUG
const state* init = aut->get_init_state();
assert(init->compare(i->s) == 0);
init->destroy();
#endif
src = res->new_state(); src = res->new_state();
seen.emplace(i->s, src);
if (names) if (names)
names->push_back(aut->format_state(s)); names->push_back(aut->format_state(i->s));
for (; i != l->end();) for (; i != l->end();)
{ {
// expected outgoing transition
bdd label = i->label; bdd label = i->label;
acc_cond::mark_t acc = i->acc; acc_cond::mark_t acc = i->acc;
// compute the next expected state // compute the next expected state
const state* next; const state* next;
++i; ++i;
if (i != l->end()) if (i != l->end())
{ {
dst = res->new_state();
next = i->s; next = i->s;
} }
else else
@ -842,57 +842,24 @@ namespace spot
{ {
l = &cycle; l = &cycle;
i = l->begin(); i = l->begin();
cycle_entry = dst = res->new_state();
}
else
{
dst = cycle_entry;
} }
next = l->begin()->s; next = l->begin()->s;
} }
// browse the actual outgoing transitions and if (names && i != l->end())
// look for next;
const state* the_next = nullptr;
for (auto j: aut->succ(s))
{ {
if (j->cond() != label assert(dst == names->size());
|| j->acc() != acc) names->push_back(aut->format_state(next));
continue;
const state* s2 = j->dst();
if (s2->compare(next) == 0)
{
the_next = s2;
break;
}
s2->destroy();
} }
s->destroy();
if (!the_next)
throw std::runtime_error("twa_run::as_twa() unable to replay run");
s = the_next;
auto p = seen.emplace(next, 0);
if (p.second)
{
unsigned ns = res->new_state();
p.first->second = ns;
if (names)
{
assert(ns == names->size());
names->push_back(aut->format_state(next));
}
}
dst = p.first->second;
res->new_edge(src, dst, label, acc); res->new_edge(src, dst, label, acc);
src = dst; src = dst;
// Sum acceptance conditions.
if (l == &cycle && i != l->begin())
seen_acc |= acc;
} }
s->destroy();
assert(aut->acc().accepting(seen_acc));
return res; return res;
} }

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2011, 2013-2018, 2020-2021 Laboratoire de // Copyright (C) 2011, 2013-2018, 2020-2021, 2023 Laboratoire de
// Recherche et Developpement de l'Epita (LRDE). // Recherche et Developpement de l'Epita (LRDE).
// Copyright (C) 2004, 2005 Laboratoire d'Informatique de Paris 6 (LIP6), // Copyright (C) 2004, 2005 Laboratoire d'Informatique de Paris 6 (LIP6),
// département Systèmes Répartis Coopératifs (SRC), Université Pierre // département Systèmes Répartis Coopératifs (SRC), Université Pierre
@ -451,9 +451,9 @@ namespace spot
/// Note that this works only if the automaton is a twa_graph_ptr. /// Note that this works only if the automaton is a twa_graph_ptr.
void highlight(unsigned color); void highlight(unsigned color);
/// \brief Return a twa_graph_ptr corresponding to \a run /// \brief Convert the run into a lasso-shaped automaton
/// ///
/// Identical states are merged. /// This preserves the original acceptance condition.
/// ///
/// If \a preserve_names is set, the created states are named /// If \a preserve_names is set, the created states are named
/// using the format_state() result from the original state. /// using the format_state() result from the original state.

View file

@ -1,6 +1,6 @@
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2013, 2014, 2015, 2016, 2018, 2019 Laboratoire de Recherche et // Copyright (C) 2013-2016, 2018-2019, 2023 Laboratoire de Recherche
// Développement de l'Epita (LRDE). // et Développement de l'Epita (LRDE).
// //
// This file is part of Spot, a model checking library. // This file is part of Spot, a model checking library.
// //
@ -80,6 +80,9 @@ namespace spot
/// \brief Convert the twa_word as an automaton. /// \brief Convert the twa_word as an automaton.
/// ///
/// Convert the twa_word into a lasso-shapred automaton
/// with "true" acceptance condition.
///
/// This is useful to evaluate a word on an automaton. /// This is useful to evaluate a word on an automaton.
twa_graph_ptr as_automaton() const; twa_graph_ptr as_automaton() const;

View file

@ -1,5 +1,5 @@
# -*- mode: python; coding: utf-8 -*- # -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2020, 2022 Laboratoire de Recherche et Développement # Copyright (C) 2020, 2022, 2023 Laboratoire de Recherche et Développement
# de l'Epita (LRDE). # de l'Epita (LRDE).
# #
# This file is part of Spot, a model checking library. # This file is part of Spot, a model checking library.
@ -38,3 +38,41 @@ r = b.intersecting_run(spot.complement(a));
c = spot.twa_word(r).as_automaton() c = spot.twa_word(r).as_automaton()
tc.assertTrue(c.intersects(b)) tc.assertTrue(c.intersects(b))
tc.assertFalse(c.intersects(a)) tc.assertFalse(c.intersects(a))
# The next test came from Philipp Schlehuber-Caissier: running
# as_twa() on a run built from a A.intersecting_run(B) failed to build
# the automaton because it tried to rebuild the run on A and did not
# find transitions matching exactly. Additionally the idea of merging
# states in as_twa() seems to be a way to create some disasters, so we
# removed that too.
a = spot.translate("a");
b = spot.translate("{a;1;a}");
r = a.intersecting_run(b)
tc.assertEqual(str(r), """Prefix:
1
| a
0
| 1 {0}
0
| a {0}
Cycle:
0
| 1 {0}
""")
tc.assertEqual(r.as_twa().to_str(), """HOA: v1
States: 4
Start: 0
AP: 1 "a"
acc-name: Buchi
Acceptance: 1 Inf(0)
properties: trans-labels explicit-labels state-acc deterministic
--BODY--
State: 0
[0] 1
State: 1 {0}
[t] 2
State: 2 {0}
[0] 3
State: 3 {0}
[t] 3
--END--""")