product: add product_xor() and product_xnor()

* spot/twaalgos/product.cc, spot/twaalgos/product.hh: Add those
functions.
* tests/python/_product_weak.ipynb, tests/python/except.py: Test them.
* NEWS: Mention them.
This commit is contained in:
Alexandre Duret-Lutz 2020-05-16 16:20:39 +02:00
parent a78137f9d4
commit 822b749166
5 changed files with 8975 additions and 200 deletions

11
NEWS
View file

@ -1,6 +1,13 @@
New in spot 2.9.0.dev (not yet released) New in spot 2.9.0.dev (not yet released)
Nothing yet. Library:
- product_xor() and product_xnor() are two new versions of the
synchronized product. They only work with operands that are
deterministic automata, and build automata whose language are
respectively the symmetric difference of the operands, and the
complement of that.
New in spot 2.9 (2020-04-30) New in spot 2.9 (2020-04-30)
@ -19,7 +26,7 @@ New in spot 2.9 (2020-04-30)
spot-rejected-word: "!a; !a; cycle{a}" spot-rejected-word: "!a; !a; cycle{a}"
- autfilt learned the --partial-degeneralize option, to remove - autfilt learned the --partial-degeneralize option, to remove
conjunctions of Inf, or disjunction of Fin that appears in conjunctions of Inf, or disjunctions of Fin that appears in
arbitrary conditions. arbitrary conditions.
- ltlfilt and autfilt learned a --nth=RANGE (a.k.a. -N) option to - ltlfilt and autfilt learned a --nth=RANGE (a.k.a. -N) option to

View file

@ -22,6 +22,7 @@
#include <spot/twa/twagraph.hh> #include <spot/twa/twagraph.hh>
#include <spot/twaalgos/complete.hh> #include <spot/twaalgos/complete.hh>
#include <spot/twaalgos/sccinfo.hh> #include <spot/twaalgos/sccinfo.hh>
#include <spot/twaalgos/isdet.hh>
#include <deque> #include <deque>
#include <unordered_map> #include <unordered_map>
#include <spot/misc/hash.hh> #include <spot/misc/hash.hh>
@ -41,7 +42,6 @@ namespace spot
} }
}; };
template<typename T> template<typename T>
static static
void product_main(const const_twa_graph_ptr& left, void product_main(const const_twa_graph_ptr& left,
@ -100,12 +100,15 @@ namespace spot
} }
} }
enum acc_op { and_acc, or_acc, xor_acc, xnor_acc };
static static
twa_graph_ptr product_aux(const const_twa_graph_ptr& left, twa_graph_ptr product_aux(const const_twa_graph_ptr& left,
const const_twa_graph_ptr& right, const const_twa_graph_ptr& right,
unsigned left_state, unsigned left_state,
unsigned right_state, unsigned right_state,
bool and_acc, acc_op aop,
const output_aborter* aborter) const output_aborter* aborter)
{ {
if (SPOT_UNLIKELY(!(left->is_existential() && right->is_existential()))) if (SPOT_UNLIKELY(!(left->is_existential() && right->is_existential())))
@ -122,32 +125,55 @@ namespace spot
bool leftweak = left->prop_weak().is_true(); bool leftweak = left->prop_weak().is_true();
bool rightweak = right->prop_weak().is_true(); bool rightweak = right->prop_weak().is_true();
// We have optimization to the standard product in case one // We have optimization to the standard product in case one
// of the arguments is weak. However these optimizations // of the arguments is weak.
// are pointless if the said arguments are "t" or "f". if (leftweak || rightweak)
if ((leftweak || rightweak)
&& (left->num_sets() > 0) && (right->num_sets() > 0))
{ {
// If both automata are weak, we can restrict the result to // If both automata are weak, we can restrict the result to
// Büchi or co-Büchi. We will favor Büchi unless the two // t, f, Büchi or co-Büchi. We use co-Büchi only when
// operands are co-Büchi. // t and f cannot be used, and both acceptance conditions
// are in {t,f,co-Büchi}.
if (leftweak && rightweak) if (leftweak && rightweak)
{ {
weak_weak: weak_weak:
res->prop_weak(true);
acc_cond::mark_t accmark = {0}; acc_cond::mark_t accmark = {0};
acc_cond::mark_t rejmark = {}; acc_cond::mark_t rejmark = {};
if (left->acc().is_co_buchi() && right->acc().is_co_buchi()) auto& lacc = left->acc();
auto& racc = right->acc();
if ((lacc.is_co_buchi() && (racc.is_co_buchi()
|| racc.num_sets() == 0))
|| (lacc.num_sets() == 0 && racc.is_co_buchi()))
{ {
res->set_co_buchi(); res->set_co_buchi();
std::swap(accmark, rejmark); std::swap(accmark, rejmark);
} }
else if ((aop == and_acc && lacc.is_t() && racc.is_t())
|| (aop == or_acc && (lacc.is_t() || racc.is_t()))
|| (aop == xnor_acc && ((lacc.is_t() && racc.is_t()) ||
(lacc.is_f() && racc.is_f())))
|| (aop == xor_acc && ((lacc.is_t() && racc.is_f()) ||
(lacc.is_f() && racc.is_t()))))
{
res->set_acceptance(0, acc_cond::acc_code::t());
accmark = {};
}
else if ((aop == and_acc && (lacc.is_f() || racc.is_f()))
|| (aop == or_acc && lacc.is_f() && racc.is_f())
|| (aop == xor_acc && ((lacc.is_t() && racc.is_t()) ||
(lacc.is_f() && racc.is_f())))
|| (aop == xnor_acc && ((lacc.is_t() && racc.is_f()) ||
(lacc.is_f() && racc.is_t()))))
{
res->set_acceptance(0, acc_cond::acc_code::f());
accmark = {};
}
else else
{ {
res->set_buchi(); res->set_buchi();
} }
res->prop_weak(true); switch (aop)
auto& lacc = left->acc(); {
auto& racc = right->acc(); case and_acc:
if (and_acc)
product_main(left, right, left_state, right_state, res, product_main(left, right, left_state, right_state, res,
[&] (acc_cond::mark_t ml, acc_cond::mark_t mr) [&] (acc_cond::mark_t ml, acc_cond::mark_t mr)
{ {
@ -156,7 +182,8 @@ namespace spot
else else
return rejmark; return rejmark;
}, aborter); }, aborter);
else break;
case or_acc:
product_main(left, right, left_state, right_state, res, product_main(left, right, left_state, right_state, res,
[&] (acc_cond::mark_t ml, acc_cond::mark_t mr) [&] (acc_cond::mark_t ml, acc_cond::mark_t mr)
{ {
@ -165,10 +192,34 @@ namespace spot
else else
return rejmark; return rejmark;
}, aborter); }, aborter);
break;
case xor_acc:
product_main(left, right, left_state, right_state, res,
[&] (acc_cond::mark_t ml, acc_cond::mark_t mr)
{
if (lacc.accepting(ml) ^ racc.accepting(mr))
return accmark;
else
return rejmark;
}, aborter);
break;
case xnor_acc:
product_main(left, right, left_state, right_state, res,
[&] (acc_cond::mark_t ml, acc_cond::mark_t mr)
{
if (lacc.accepting(ml) == racc.accepting(mr))
return accmark;
else
return rejmark;
}, aborter);
break;
}
} }
else if (!rightweak) else if (!rightweak)
{ {
if (and_acc) switch (aop)
{
case and_acc:
{ {
auto rightunsatmark = right->acc().unsat_mark(); auto rightunsatmark = right->acc().unsat_mark();
if (!rightunsatmark.first) if (!rightunsatmark.first)
@ -189,8 +240,9 @@ namespace spot
else else
return rejmark; return rejmark;
}, aborter); }, aborter);
break;
} }
else case or_acc:
{ {
auto rightsatmark = right->acc().sat_mark(); auto rightsatmark = right->acc().sat_mark();
if (!rightsatmark.first) if (!rightsatmark.first)
@ -211,13 +263,32 @@ namespace spot
else else
return accmark; return accmark;
}, aborter); }, aborter);
break;
}
case xor_acc:
case xnor_acc:
{
auto rightsatmark = right->acc().sat_mark();
auto rightunsatmark = right->acc().unsat_mark();
if (!rightunsatmark.first || !rightsatmark.first)
{
// Left is weak. Right was not weak, but it
// is either always rejecting or always
// accepting. We can therefore pretend that
// right is weak.
goto weak_weak;
}
goto generalcase;
break;
} }
} }
else }
else // right weak
{ {
assert(!leftweak); assert(!leftweak);
if (and_acc) switch (aop)
{
case and_acc:
{ {
auto leftunsatmark = left->acc().unsat_mark(); auto leftunsatmark = left->acc().unsat_mark();
if (!leftunsatmark.first) if (!leftunsatmark.first)
@ -238,8 +309,9 @@ namespace spot
else else
return rejmark; return rejmark;
}, aborter); }, aborter);
break;
} }
else case or_acc:
{ {
auto leftsatmark = left->acc().sat_mark(); auto leftsatmark = left->acc().sat_mark();
if (!leftsatmark.first) if (!leftsatmark.first)
@ -261,25 +333,63 @@ namespace spot
return accmark; return accmark;
}, aborter); }, aborter);
break;
}
case xor_acc:
case xnor_acc:
{
auto leftsatmark = left->acc().sat_mark();
auto leftunsatmark = left->acc().unsat_mark();
if (!leftunsatmark.first || !leftsatmark.first)
{
// Right is weak. Left was not weak, but it
// is either always rejecting or always
// accepting. We can therefore pretend that
// left is weak.
goto weak_weak;
}
goto generalcase;
break;
}
} }
} }
} }
else // general case else // general case
{ {
generalcase:
auto left_num = left->num_sets(); auto left_num = left->num_sets();
auto& left_acc = left->get_acceptance();
auto right_acc = right->get_acceptance() << left_num; auto right_acc = right->get_acceptance() << left_num;
if (and_acc) switch (aop)
right_acc &= left->get_acceptance(); {
else case and_acc:
right_acc |= left->get_acceptance(); right_acc &= left_acc;
break;
case or_acc:
right_acc |= left_acc;
break;
case xor_acc:
{
auto tmp = right_acc.complement() & left_acc;
right_acc &= left_acc.complement();
right_acc |= tmp;
break;
}
case xnor_acc:
{
auto tmp = right_acc.complement() & left_acc.complement();
right_acc &= left_acc;
tmp |= right_acc;
std::swap(tmp, right_acc);
break;
}
}
res->set_acceptance(left_num + right->num_sets(), right_acc); res->set_acceptance(left_num + right->num_sets(), right_acc);
product_main(left, right, left_state, right_state, res, product_main(left, right, left_state, right_state, res,
[&] (acc_cond::mark_t ml, acc_cond::mark_t mr) [&] (acc_cond::mark_t ml, acc_cond::mark_t mr)
{ {
return ml | (mr << left_num); return ml | (mr << left_num);
}, aborter); }, aborter);
} }
if (!res) // aborted if (!res) // aborted
@ -323,7 +433,7 @@ namespace spot
unsigned right_state, unsigned right_state,
const output_aborter* aborter) const output_aborter* aborter)
{ {
return product_aux(left, right, left_state, right_state, true, aborter); return product_aux(left, right, left_state, right_state, and_acc, aborter);
} }
twa_graph_ptr product(const const_twa_graph_ptr& left, twa_graph_ptr product(const const_twa_graph_ptr& left,
@ -341,7 +451,7 @@ namespace spot
unsigned right_state) unsigned right_state)
{ {
return product_aux(complete(left), complete(right), return product_aux(complete(left), complete(right),
left_state, right_state, false, nullptr); left_state, right_state, or_acc, nullptr);
} }
twa_graph_ptr product_or(const const_twa_graph_ptr& left, twa_graph_ptr product_or(const const_twa_graph_ptr& left,
@ -352,6 +462,32 @@ namespace spot
right->get_init_state_number()); right->get_init_state_number());
} }
twa_graph_ptr product_xor(const const_twa_graph_ptr& left,
const const_twa_graph_ptr& right)
{
if (SPOT_UNLIKELY(!is_deterministic(left) || !is_deterministic(right)))
throw std::runtime_error
("product_xor() only works with deterministic automata");
return product_aux(complete(left), complete(right),
left->get_init_state_number(),
right->get_init_state_number(),
xor_acc, nullptr);
}
twa_graph_ptr product_xnor(const const_twa_graph_ptr& left,
const const_twa_graph_ptr& right)
{
if (SPOT_UNLIKELY(!is_deterministic(left) || !is_deterministic(right)))
throw std::runtime_error
("product_xnor() only works with deterministic automata");
return product_aux(complete(left), complete(right),
left->get_init_state_number(),
right->get_init_state_number(),
xnor_acc, nullptr);
}
namespace namespace
{ {

View file

@ -1,5 +1,5 @@
// -*- coding: utf-8 -*- // -*- coding: utf-8 -*-
// Copyright (C) 2014, 2015, 2018, 2019 Laboratoire de Recherche et // Copyright (C) 2014-2015, 2018-2020 Laboratoire de Recherche et
// Développement de l'Epita (LRDE). // 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.
@ -127,6 +127,45 @@ namespace spot
unsigned left_state, unsigned left_state,
unsigned right_state); unsigned right_state);
/// \ingroup twa_algorithms
/// \brief XOR two deterministic automata using a synchronous product
///
/// The two operands must be deterministic.
///
/// The resulting automaton will accept the symmetric difference of
/// both languages and have an acceptance condition that is the xor
/// of the acceptance conditions of the two input automata. In case
/// both operands are weak, the acceptance condition of the result
/// is made simpler.
///
/// The algorithm also defines a named property called
/// "product-states" with type spot::product_states. This stores
/// the pair of original state numbers associated to each state of
/// the product.
SPOT_API
twa_graph_ptr product_xor(const const_twa_graph_ptr& left,
const const_twa_graph_ptr& right);
/// \ingroup twa_algorithms
/// \brief XNOR two automata using a synchronous product
///
/// The two operands must be deterministic.
///
/// The resulting automaton will accept words that are either in
/// both input languages, or not in both languages. (The XNOR gate
/// it the logical complement of XOR. XNOR is also known as logical
/// equivalence.) The output will have an acceptance condition that
/// is the XNOR of the acceptance conditions of the two input
/// automata. In case both the operands are weak, the acceptance
/// condition of the result is made simpler.
///
/// The algorithm also defines a named property called
/// "product-states" with type spot::product_states. This stores
/// the pair of original state numbers associated to each state of
/// the product.
SPOT_API
twa_graph_ptr product_xnor(const const_twa_graph_ptr& left,
const const_twa_graph_ptr& right);
/// \ingroup twa_algorithms /// \ingroup twa_algorithms
/// \brief Build the product of an automaton with a suspendable /// \brief Build the product of an automaton with a suspendable
@ -136,7 +175,7 @@ namespace spot
/// languages of both input automata. /// languages of both input automata.
/// ///
/// This function *assumes* that \a right_susp is a suspendable /// This function *assumes* that \a right_susp is a suspendable
/// automaton, i.e., it its language L satisfies L = Σ*.L. /// automaton, i.e., its language L satisfies L = Σ*.L.
/// Therefore the product between the two automata need only be done /// Therefore the product between the two automata need only be done
/// with the accepting SCCs of left. /// with the accepting SCCs of left.
/// ///
@ -155,7 +194,7 @@ namespace spot
/// both input automata. /// both input automata.
/// ///
/// This function *assumes* that \a right_susp is a suspendable /// This function *assumes* that \a right_susp is a suspendable
/// automaton, i.e., it its language L satisfies L = Σ*.L. /// automaton, i.e., its language L satisfies L = Σ*.L.
/// Therefore, after left has been completed (this will be done by /// Therefore, after left has been completed (this will be done by
/// product_or_susp) the product between the two automata need only /// product_or_susp) the product between the two automata need only
/// be done with the SCCs of left that contains some rejecting cycles. /// be done with the SCCs of left that contains some rejecting cycles.

File diff suppressed because it is too large Load diff

View file

@ -188,3 +188,32 @@ except ValueError as e:
assert 'any' in s assert 'any' in s
else: else:
report_missing_exception() report_missing_exception()
a1 = spot.translate('FGa')
a2 = spot.translate('Gb')
assert not spot.is_deterministic(a1)
assert spot.is_deterministic(a2)
try:
spot.product_xor(a1, a2)
except RuntimeError as e:
assert "product_xor() only works with deterministic automata"
else:
report_missing_exception()
try:
spot.product_xor(a2, a1)
except RuntimeError as e:
assert "product_xor() only works with deterministic automata"
else:
report_missing_exception()
try:
spot.product_xnor(a1, a2)
except RuntimeError as e:
assert "product_xnor() only works with deterministic automata"
else:
report_missing_exception()
try:
spot.product_xnor(a2, a1)
except RuntimeError as e:
assert "product_xnor() only works with deterministic automata"
else:
report_missing_exception()