parity: introduce reduce_parity()
* spot/twaalgos/parity.cc, spot/twaalgos/parity.hh: Here. * tests/core/parity.cc: Add test case. * tests/python/parity.ipynb, NEWS: More documentation.
This commit is contained in:
parent
f6575d2ec5
commit
ebfa3a377a
5 changed files with 1280 additions and 5 deletions
6
NEWS
6
NEWS
|
|
@ -108,6 +108,12 @@ New in spot 2.7.5.dev (not yet released)
|
|||
colorize_parity() learned that coloring transitiant edges does not
|
||||
require the introduction of a new color.
|
||||
|
||||
- A new reduce_parity() function implements and generalizes the
|
||||
algorithm for minimizing parity acceptance by Carton and Maceiras
|
||||
(Computing the Rabin index of a parity automaton, 1999). This is
|
||||
a better replacement for cleanup_parity() and colorize_parity().
|
||||
See https://spot.lrde.epita.fr/ipynb/parity.html for examples.
|
||||
|
||||
New in spot 2.7.5 (2019-06-05)
|
||||
|
||||
Build:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <spot/twaalgos/product.hh>
|
||||
#include <spot/twaalgos/complete.hh>
|
||||
#include <spot/twaalgos/sccinfo.hh>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
|
|
@ -386,4 +387,247 @@ namespace spot
|
|||
return aut;
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
reduce_parity(const const_twa_graph_ptr& aut, bool colored)
|
||||
{
|
||||
return reduce_parity_here(make_twa_graph(aut, twa::prop_set::all()),
|
||||
colored);
|
||||
}
|
||||
|
||||
twa_graph_ptr
|
||||
reduce_parity_here(twa_graph_ptr aut, bool colored)
|
||||
{
|
||||
unsigned num_sets = aut->num_sets();
|
||||
if (!colored && num_sets == 0)
|
||||
return aut;
|
||||
|
||||
bool current_max;
|
||||
bool current_odd;
|
||||
if (!aut->acc().is_parity(current_max, current_odd, true))
|
||||
input_is_not_parity("reduce_parity");
|
||||
if (!aut->is_existential())
|
||||
throw std::runtime_error
|
||||
("reduce_parity_here() does not support alternation");
|
||||
|
||||
// The algorithm assumes "max odd" or "max even" parity. "min"
|
||||
// parity is handled by converting it to "max" while the algorithm
|
||||
// reads the automaton, and then back to "min" when it writes it.
|
||||
//
|
||||
// The main idea of the algorithm is to refine the SCCs of the
|
||||
// automaton as will peel the parity layers one by one, starting
|
||||
// with the maximum color.
|
||||
//
|
||||
// In the following tree, assume Sᵢ denotes SCCs and t. denotes a
|
||||
// trivial SCC. Let's assume our original graph is made of one
|
||||
// SCC S₁. In each SCC, we "peel the maximum layer", i.e., we
|
||||
// remove the edges with the maximum colors. Doing so, we may
|
||||
// introduce more sub-SCCs, in which we do this process
|
||||
// recursively.
|
||||
//
|
||||
// S₁
|
||||
// |
|
||||
// max=4
|
||||
// ╱ ╲
|
||||
// S₂ S₃
|
||||
// | |
|
||||
// max=2 max=1
|
||||
// ╱ ╲ |
|
||||
// S₄ t. t.
|
||||
// |
|
||||
// max=0
|
||||
// |
|
||||
// t.
|
||||
//
|
||||
// Now the trick assign the same colors to all leaves, and then
|
||||
// compress consecutive layers with identical parity. Let S₁[3]
|
||||
// denote the transitions with color 3 in SCC S₁, let C(S₁[3])
|
||||
// denote the new color we will assign to those sets.
|
||||
//
|
||||
// Assume we decide C(t.)=k=2n, this is arbitrary and imaginary
|
||||
// (as there are no transitions to color in t.)
|
||||
//
|
||||
// Then
|
||||
// C(S₄[0])=k because 0 and C(t.)=k have same parity
|
||||
// C(S₂[2])=k because 2 and max(C(S₄[0]),C(t.)) have same parity
|
||||
// C(S₃[1])=k+1 because 1 and C(t.)=k have different parity
|
||||
// C(S₁[4])=k+2 because 4 and max(C(S₂[2]),C(S₃[1])) have different parity
|
||||
// So in the case, the resulting automaton would use 3 colors, k,k+1,k+2.
|
||||
//
|
||||
// If we do the same computation with C(t.)=k=2n+1, the result is
|
||||
// better:
|
||||
// C(S₄[0])=k+1 because 0 and C(t.)=k have different parity
|
||||
// C(S₂[2])=k+1 because 2 and max(C(S₄[0]),C(t.)) have same parity
|
||||
// C(S₃[1])=k because 1 and C(t.)=k have same parity
|
||||
// C(S₁[4])=k+1 because 4 and max(C(S₂[2]),C(S₃[1])) have same
|
||||
// Here only two colors are needed.
|
||||
//
|
||||
// So the following code evaluates those two possible scenarios,
|
||||
// using k=0 or k=1.
|
||||
//
|
||||
// -2 means the edge was never assigned a color.
|
||||
std::vector<int> piprime1(aut->num_edges() + 1, -2); // k=1
|
||||
std::vector<int> piprime2(aut->num_edges() + 1, -2); // k=0
|
||||
bool sba = aut->prop_state_acc().is_true();
|
||||
|
||||
auto rec =
|
||||
[&](const scc_and_mark_filter& filter, auto& self) -> std::pair<int, int>
|
||||
{
|
||||
scc_info si(filter);
|
||||
unsigned numscc = si.scc_count();
|
||||
std::pair<int, int> res = {1, 0}; // k=1, k=0
|
||||
for (unsigned scc = 0; scc < numscc; ++scc)
|
||||
if (!si.is_trivial(scc))
|
||||
{
|
||||
int piri; // π(Rᵢ)
|
||||
int color; // corresponding color, to deal with "min" kind
|
||||
if (current_max)
|
||||
{
|
||||
piri = color = si.acc_sets_of(scc).max_set() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = si.acc_sets_of(scc).min_set() - 1;
|
||||
if (color < 0)
|
||||
color = num_sets;
|
||||
// The algorithm works with max parity, so reverse
|
||||
// the color range.
|
||||
piri = num_sets - color - 1;
|
||||
}
|
||||
std::pair<int, int> m;
|
||||
if (piri < 0)
|
||||
{
|
||||
// Uncolored transition. Always odd.
|
||||
m = {1, 1};
|
||||
}
|
||||
else
|
||||
{
|
||||
scc_and_mark_filter filter2(si, scc, {unsigned(color)});
|
||||
m = self(filter2, self);
|
||||
m.first += (piri - m.first) & 1;
|
||||
m.second += (piri - m.second) & 1;
|
||||
}
|
||||
for (unsigned state: si.states_of(scc))
|
||||
for (auto& e: aut->out(state))
|
||||
if ((sba || si.scc_of(e.dst) == scc) &&
|
||||
((piri >= 0 && e.acc.has(color)) || (piri < 0 && !e.acc)))
|
||||
{
|
||||
unsigned en = aut->edge_number(e);
|
||||
piprime1[en] = m.first;
|
||||
piprime2[en] = m.second;
|
||||
}
|
||||
res.first = std::max(res.first, m.first);
|
||||
res.second = std::max(res.second, m.second);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
scc_and_mark_filter filter1(aut, {});
|
||||
rec(filter1, rec);
|
||||
|
||||
// compute the used range for each vector.
|
||||
int min1 = num_sets;
|
||||
int max1 = -2;
|
||||
for (int m : piprime1)
|
||||
{
|
||||
if (m <= -2)
|
||||
continue;
|
||||
if (m < min1)
|
||||
min1 = m;
|
||||
if (m > max1)
|
||||
max1 = m;
|
||||
}
|
||||
|
||||
if (SPOT_UNLIKELY(max1 == -2))
|
||||
{
|
||||
aut->set_acceptance(0, spot::acc_cond::acc_code::f());
|
||||
return aut;
|
||||
}
|
||||
int min2 = num_sets;
|
||||
int max2 = -2;
|
||||
for (int m : piprime2)
|
||||
{
|
||||
if (m <= -2)
|
||||
continue;
|
||||
if (m < min2)
|
||||
min2 = m;
|
||||
if (m > max2)
|
||||
max2 = m;
|
||||
}
|
||||
|
||||
unsigned size1 = max1 + 1 - min1;
|
||||
unsigned size2 = max2 + 1 - min2;
|
||||
if (size2 < size1)
|
||||
{
|
||||
std::swap(size1, size2);
|
||||
std::swap(min1, min2);
|
||||
std::swap(piprime1, piprime2);
|
||||
}
|
||||
|
||||
unsigned new_num_sets = size1;
|
||||
if (current_max)
|
||||
{
|
||||
for (int& m : piprime1)
|
||||
if (m > -2)
|
||||
m -= min1;
|
||||
else
|
||||
m = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int& m : piprime1)
|
||||
if (m > -2)
|
||||
m = new_num_sets - (m - min1) - 1;
|
||||
else
|
||||
m = new_num_sets - 1;
|
||||
}
|
||||
|
||||
// The parity style changes if we shift colors by an odd number.
|
||||
bool new_odd = current_odd ^ (min1 & 1);
|
||||
if (!current_max)
|
||||
// Switching from min<->max changes the parity style every time
|
||||
// the number of colors is even. If the input was "min", we
|
||||
// switched once to "max" to apply the reduction and once again
|
||||
// to go back to "min". So there are two points where the
|
||||
// parity style may have changed.
|
||||
new_odd ^= !(num_sets & 1) ^ !(new_num_sets & 1);
|
||||
if (!colored)
|
||||
{
|
||||
new_odd ^= current_max;
|
||||
new_num_sets -= 1;
|
||||
|
||||
// It seems we have nothing to win by changing automata with a
|
||||
// single set (unless we reduced it to 0 sets, of course).
|
||||
if (new_num_sets == num_sets && num_sets == 1)
|
||||
return aut;
|
||||
}
|
||||
|
||||
aut->set_acceptance(new_num_sets,
|
||||
acc_cond::acc_code::parity(current_max, new_odd,
|
||||
new_num_sets));
|
||||
if (colored)
|
||||
for (auto& e: aut->edges())
|
||||
{
|
||||
unsigned n = aut->edge_number(e);
|
||||
e.acc = acc_cond::mark_t({unsigned(piprime1[n])});
|
||||
}
|
||||
else if (current_max)
|
||||
for (auto& e: aut->edges())
|
||||
{
|
||||
unsigned n = piprime1[aut->edge_number(e)];
|
||||
if (n == 0)
|
||||
e.acc = acc_cond::mark_t({});
|
||||
else
|
||||
e.acc = acc_cond::mark_t({n - 1});
|
||||
}
|
||||
else
|
||||
for (auto& e: aut->edges())
|
||||
{
|
||||
unsigned n = piprime1[aut->edge_number(e)];
|
||||
if (n >= new_num_sets)
|
||||
e.acc = acc_cond::mark_t({});
|
||||
else
|
||||
e.acc = acc_cond::mark_t({n});
|
||||
}
|
||||
|
||||
return aut;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// Copyright (C) 2016-2018 Laboratoire de Recherche et Développement
|
||||
// Copyright (C) 2016-2019 Laboratoire de Recherche et Développement
|
||||
// de l'Epita (LRDE).
|
||||
//
|
||||
// This file is part of Spot, a model checking library.
|
||||
|
|
@ -133,4 +133,37 @@ namespace spot
|
|||
SPOT_API twa_graph_ptr
|
||||
colorize_parity_here(twa_graph_ptr aut, bool keep_style = false);
|
||||
/// @}
|
||||
|
||||
|
||||
/// \brief Reduce the parity acceptance condition to use a minimal
|
||||
/// number of colors.
|
||||
///
|
||||
/// This implements an algorithm derived from the following article,
|
||||
/// but generalized to all types of parity acceptance.
|
||||
/** \verbatim
|
||||
@Article{carton.99.ita,
|
||||
author = {Olivier Carton and Ram{\'o}n Maceiras},
|
||||
title = {Computing the {R}abin index of a parity automaton},
|
||||
journal = {Informatique théorique et applications},
|
||||
year = {1999},
|
||||
volume = {33},
|
||||
number = {6},
|
||||
pages = {495--505}
|
||||
}
|
||||
\endverbatim */
|
||||
///
|
||||
/// The kind of parity (min/max) is preserved, but the style
|
||||
/// (odd/even) may be altered to reduce the number of colors used.
|
||||
///
|
||||
/// If \a colored is true, colored automata are output (this is what
|
||||
/// the above paper assumes). Otherwise, the smallest or highest
|
||||
/// colors (depending on the parity kind) is removed to simplify the
|
||||
/// acceptance condition.
|
||||
/// @{
|
||||
SPOT_API twa_graph_ptr
|
||||
reduce_parity(const const_twa_graph_ptr& aut, bool colored = false);
|
||||
|
||||
SPOT_API twa_graph_ptr
|
||||
reduce_parity_here(twa_graph_ptr aut, bool colored = false);
|
||||
/// @}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -384,6 +384,23 @@ int main()
|
|||
is_max, is_odd, acc_num_sets))
|
||||
throw std::runtime_error("cleanup_parity: wrong acceptance.");
|
||||
}
|
||||
|
||||
// Check reduce_parity
|
||||
for (auto colored: { true, false })
|
||||
{
|
||||
auto output = spot::reduce_parity(aut, colored);
|
||||
if (!colored && !is_almost_colored(output))
|
||||
throw std::runtime_error(
|
||||
"reduce_parity: too many acc on a transition.");
|
||||
if (colored && !is_colored_printerr(output))
|
||||
throw std::runtime_error("reduce_parity: not colored.");
|
||||
if (!are_equiv(aut, output))
|
||||
throw std::runtime_error("reduce_parity: not equivalent.");
|
||||
if (!is_right_parity(output, to_parity_kind(is_max),
|
||||
spot::parity_style_any,
|
||||
is_max, is_odd, acc_num_sets))
|
||||
throw std::runtime_error("reduce_parity: wrong acceptance.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue