dualize: should not call cleanup_acceptance_here

Based on a report by Emmanuel Filiot, who was surprized that dualizing
Büchi did not always produce co-Büchi.

* spot/twaalgos/dualize.cc: Remove the call to
cleanup_acceptance_here.
* spot/twaalgos/dualize.hh: Improve documentation.
* NEWS: Mention the possible backward incompatible change.
* tests/core/dualize.test, tests/python/dualize.py,
tests/python/pdegen.py: Adjust test cases.
* spot/twaalgos/complement.cc (complement): Call
cleanup_acceptance_here when dualize() returns a smaller automaton.
* THANKS: Add Emmanuel.
This commit is contained in:
Alexandre Duret-Lutz 2024-03-06 17:53:23 +01:00
parent 60f046a574
commit 1b81ecb80c
8 changed files with 67 additions and 23 deletions

7
NEWS
View file

@ -148,6 +148,13 @@ New in spot 2.11.6.dev (not yet released)
should raise an exception of return nullptr if it requires more should raise an exception of return nullptr if it requires more
acceptance sets than supported. acceptance sets than supported.
- [Potential backward incompatibility] spot::dualize() does not call
cleanup_acceptance() anymore. This change ensures that the dual
of a Büchi automaton will always be a co-Büchi automaton.
Previously cleanup_acceptance(), which remove unused colors from
the acceptance, was sometimes able to simplify co-Büchi to "t",
causing surprizes.
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

@ -15,6 +15,7 @@ David Dokoupil
David Müller David Müller
Dávid Smolka Dávid Smolka
Edmond Irani Liu Edmond Irani Liu
Emmanuel Filiot
Ernesto Posse Ernesto Posse
Étienne Renault Étienne Renault
Fabrice Kordon Fabrice Kordon

View file

@ -25,6 +25,7 @@
#include <spot/twaalgos/postproc.hh> #include <spot/twaalgos/postproc.hh>
#include <spot/twaalgos/strength.hh> #include <spot/twaalgos/strength.hh>
#include <spot/twaalgos/sccinfo.hh> #include <spot/twaalgos/sccinfo.hh>
#include <spot/twaalgos/cleanacc.hh>
namespace spot namespace spot
{ {
@ -510,7 +511,19 @@ namespace spot
complement(const const_twa_graph_ptr& aut, const output_aborter* aborter) complement(const const_twa_graph_ptr& aut, const output_aborter* aborter)
{ {
if (!aut->is_existential() || is_universal(aut)) if (!aut->is_existential() || is_universal(aut))
return dualize(aut); {
twa_graph_ptr res = dualize(aut);
// There are cases with "t" acceptance that get converted to
// Büchi during completion, then dualized to co-Büchi, but the
// acceptance is still not used. To try to clean it up in this
// case.
if (aut->num_sets() == 0 ||
// Also dualize removes sink states, but doesn't simplify
// the acceptance condition.
res->num_states() < aut->num_states())
cleanup_acceptance_here(res);
return res;
}
if (is_very_weak_automaton(aut)) if (is_very_weak_automaton(aut))
// removing alternation may need more acceptance sets than we support. // removing alternation may need more acceptance sets than we support.
// in this case res==nullptr and we try the other determinization. // in this case res==nullptr and we try the other determinization.

View file

@ -308,7 +308,7 @@ namespace spot
} }
if (is_deterministic(aut_)) if (is_deterministic(aut_))
{ {
res = cleanup_acceptance_here(spot::complete(aut_)); res = spot::complete(aut_);
res->set_acceptance(res->num_sets(), res->set_acceptance(res->num_sets(),
res->get_acceptance().complement()); res->get_acceptance().complement());
// Complementing the acceptance is likely to break the terminal // Complementing the acceptance is likely to break the terminal
@ -368,8 +368,6 @@ namespace spot
res->prop_terminal(trival::maybe()); res->prop_terminal(trival::maybe());
if (!has_sink) if (!has_sink)
res->prop_complete(true); res->prop_complete(true);
cleanup_acceptance_here(res);
return res; return res;
} }
}; };

View file

@ -26,23 +26,50 @@ namespace spot
/// \ingroup twa_misc /// \ingroup twa_misc
/// \brief Complement an automaton by dualizing it. /// \brief Complement an automaton by dualizing it.
/// ///
/// Given an automaton \a aut of any type, produces the dual as output. The /// Given an automaton \a aut of any type, produces the dual as
/// automaton will be completed if it isn't already. If it is deterministic /// output. Before dualization, the automaton will be completed if
/// and complete, complementing the automaton can be done by just /// it isn't already, but any sink state in the output might then be
/// complementing the acceptance condition. /// removed.
/// ///
/// In particular, this implies that an input that use generalized Büchi will /// Dualizing the automaton is done by interpreting the outgoing
/// be output as generalized co-Büchi. /// transitions of a state as a Boolean function, and then swapping
/// operators ∧ and ̇∨. This first step does not have to be done on
/// deterministic automata. Additionally, the acceptance condition
/// is dualized by swapping operators ∧ and ̇∨, and swapping Inf and
/// Fin.
/// ///
/// Functions like to_generalized_buchi() or remove_fin() are frequently /// For instance, the dual of a generalized Büchi automaton will be
/// called on existential automata after dualize() to obtain an easier /// a generalized co-Büchi automaton.
/// acceptance condition, but maybe at the cost of losing determinism. ///
/// If the input acceptance condition accepts every infinite path
/// (such as "t" or "Inf(0)|Fin(0)") and the automaton is not
/// complete, then the input automaton will be assumed to have Büchi
/// acceptance in order to complete it, and the output will then
/// have co-Büchi acceptance.
///
/// Due to a defect in the way transition-based alternating automata
/// are represented in Spot and in the HOA format, existential
/// automata with transition-based acceptance will be converted to
/// use state-based acceptance before dualization. See
/// https://github.com/adl/hoaf/issues/68 for more information.
/// ///
/// If the input automaton is deterministic, the output will be deterministic. /// If the input automaton is deterministic, the output will be deterministic.
/// If the input automaton is existential, the output will be universal. /// If the input automaton is existential, the output will be universal.
/// If the input automaton is universal, the output will be existential. /// If the input automaton is universal, the output will be existential.
/// Finally, if the input automaton is alternating, the result is alternating. /// Finally, if the input automaton is alternating, the result is alternating.
/// More can be found on page 22 (Definition 1.6) of \cite loding.98.msc . /// More can be found on page 22 (Definition 1.6) of \cite loding.98.msc .
///
/// Functions like to_generalized_buchi() or remove_fin() are frequently
/// called on existential automata after dualize() to obtain an easier
/// acceptance condition, but maybe at the cost of losing determinism.
///
/// Up to version 2.11.6, this function used to call
/// cleanup_acceptance_here() to simplify the acceptance condition
/// after dualization. This caused some surprizes, users expected
/// the dual of a Büchi automaton to be a co-Büchi automaton, but
/// cleanup_acceptance_here() sometimes reduced the condition to `t`
/// when all states where accepting. This function is not called
/// anymore since version 2.12.
SPOT_API twa_graph_ptr SPOT_API twa_graph_ptr
dualize(const const_twa_graph_ptr& aut); dualize(const const_twa_graph_ptr& aut);
} }

View file

@ -49,8 +49,7 @@ HOA: v1
States: 2 States: 2
Start: 0 Start: 0
AP: 2 "a" "b" AP: 2 "a" "b"
acc-name: all Acceptance: 3 (Fin(0)|Fin(1)) | Inf(2)
Acceptance: 0 t
properties: trans-labels explicit-labels trans-acc complete properties: trans-labels explicit-labels trans-acc complete
properties: deterministic properties: deterministic
--BODY-- --BODY--
@ -114,8 +113,7 @@ HOA: v1
States: 9 States: 9
Start: 8 Start: 8
AP: 2 "p0" "p1" AP: 2 "p0" "p1"
acc-name: co-Buchi Acceptance: 2 Fin(0) & Fin(1)
Acceptance: 1 Fin(0)
properties: trans-labels explicit-labels state-acc univ-branch properties: trans-labels explicit-labels state-acc univ-branch
--BODY-- --BODY--
State: 0 State: 0
@ -148,8 +146,7 @@ HOA: v1
States: 9 States: 9
Start: 8 Start: 8
AP: 2 "p0" "p1" AP: 2 "p0" "p1"
acc-name: co-Buchi Acceptance: 2 Fin(0) & Fin(1)
Acceptance: 1 Fin(0)
properties: trans-labels explicit-labels state-acc univ-branch properties: trans-labels explicit-labels state-acc univ-branch
Alias: @a 0&!1 Alias: @a 0&!1
Alias: @b !0&!1 Alias: @b !0&!1

View file

@ -191,8 +191,8 @@ tc.assertEqual(h, """HOA: v1
States: 2 States: 2
Start: 1 Start: 1
AP: 2 "a" "b" AP: 2 "a" "b"
acc-name: all acc-name: co-Buchi
Acceptance: 0 t Acceptance: 1 Fin(0)
properties: trans-labels explicit-labels state-acc deterministic properties: trans-labels explicit-labels state-acc deterministic
--BODY-- --BODY--
State: 0 State: 0
@ -227,8 +227,7 @@ tc.assertEqual(h, """HOA: v1
States: 2 States: 2
Start: 1 Start: 1
AP: 2 "a" "b" AP: 2 "a" "b"
acc-name: all Acceptance: 2 Fin(0) & Fin(1)
Acceptance: 0 t
properties: trans-labels explicit-labels state-acc deterministic properties: trans-labels explicit-labels state-acc deterministic
--BODY-- --BODY--
State: 0 State: 0

View file

@ -115,6 +115,8 @@ tc.assertEqual(dd.num_states(), 1)
tc.assertEqual(str(dd.get_acceptance()), 'Inf(1) & Fin(0)') tc.assertEqual(str(dd.get_acceptance()), 'Inf(1) & Fin(0)')
e = spot.dualize(b) e = spot.dualize(b)
spot.cleanup_acceptance_here(e)
tc.assertEqual(str(e.get_acceptance()), 'Fin(0)|Fin(1)')
de = spot.partial_degeneralize(e, [0, 1]) de = spot.partial_degeneralize(e, [0, 1])
tc.assertTrue(de.equivalent_to(e)) tc.assertTrue(de.equivalent_to(e))
tc.assertEqual(de.num_states(), 4) tc.assertEqual(de.num_states(), 4)