implement maximum cardinality search

* spot/twaalgos/mcs.cc, spot/twaalgos/mcs.hh: New files.
* spot/twaalgos/Makefile.am: Add them.
* python/spot/impl.i: Include mcs.hh.
* bin/autfilt.cc: Add --mcs option.
* NEWS: Mention it.
* doc/spot.bib: Add reference.
* tests/core/mcs.test: New file.
* tests/Makefile.am: Add it.
This commit is contained in:
Alexandre Duret-Lutz 2024-09-18 12:04:23 +02:00
parent 77a17881a3
commit 7b0e15a7fb
9 changed files with 501 additions and 5 deletions

View file

@ -37,6 +37,7 @@ twaalgos_HEADERS = \
compsusp.hh \
contains.hh \
copy.hh \
couvreurnew.hh \
cycles.hh \
dbranch.hh \
deadends.hh \
@ -65,9 +66,9 @@ twaalgos_HEADERS = \
magic.hh \
mask.hh \
matchstates.hh \
mcs.hh \
minimize.hh \
mealy_machine.hh \
couvreurnew.hh \
neverclaim.hh \
parity.hh \
postproc.hh \
@ -114,6 +115,7 @@ libtwaalgos_la_SOURCES = \
complement.cc \
compsusp.cc \
contains.cc \
couvreurnew.cc \
cycles.cc \
dbranch.cc \
deadends.cc \
@ -141,9 +143,9 @@ libtwaalgos_la_SOURCES = \
magic.cc \
mask.cc \
matchstates.cc \
mcs.cc \
minimize.cc \
mealy_machine.cc \
couvreurnew.cc \
ndfs_result.hxx \
neverclaim.cc \
parity.cc \

196
spot/twaalgos/mcs.cc Normal file
View file

@ -0,0 +1,196 @@
// -*- 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/>.
#include "config.h"
#include <spot/twa/twagraph.hh>
#include <spot/twaalgos/mcs.hh>
#include <spot/twaalgos/sccinfo.hh>
namespace spot
{
namespace
{
struct mcs_vertex
{
// Each vertex is part of a circular doubly-linked list.
mcs_vertex* prev = nullptr;
mcs_vertex* next = nullptr;
// The weight of a vertex, initially 0, is the number of
// neighbors that already have an MCS-number. The weight is -1
// if the vertex itself has an MCS-number.
int weight = 0;
};
struct mcs_data
{
std::vector<mcs_vertex> vertex;
// set is an array of doubly-linked list built using vertex elements
std::vector<mcs_vertex*> set;
// Initialy, all n vertices are in set[0]
mcs_data(unsigned n)
: vertex(n), set(n, nullptr)
{
// make a circular list of everything in vertex
vertex[0].prev = &vertex[n - 1];
for (unsigned i = 0; i < n - 1; i++)
{
vertex[i].next = &vertex[i + 1];
vertex[i + 1].prev = &vertex[i];
}
vertex[n - 1].next = &vertex[0];
set[0] = &vertex[0];
}
void remove_vertex(unsigned vertex_i, unsigned from_set)
{
mcs_vertex* v = &vertex[vertex_i];
mcs_vertex* prev = v->prev;
mcs_vertex* next = v->next;
prev->next = next;
next->prev = prev;
if (v == set[from_set])
set[from_set] = (v == next) ? nullptr : next;
}
void insert_vertex(unsigned vertex_i, unsigned to_set)
{
mcs_vertex* v = &vertex[vertex_i];
mcs_vertex* next = set[to_set];
if (next == nullptr)
{
v->prev = v;
v->next = v;
set[to_set] = v;
}
else
{
mcs_vertex* prev = next->prev;
v->prev = prev;
v->next = next;
prev->next = v;
next->prev = v;
}
}
void increase_vertex_weight(unsigned vertex_i)
{
mcs_vertex* v = &vertex[vertex_i];
if (v->weight >= 0)
{
remove_vertex(vertex_i, v->weight);
++v->weight;
insert_vertex(vertex_i, v->weight);
}
}
unsigned select_any_vertex(unsigned from_set)
{
mcs_vertex* start = set[from_set];
assert(start);
return start - &vertex[0];
}
scc_info* si;
unsigned select_best_vertex_scc(unsigned from_set)
{
mcs_vertex* start = set[from_set];
assert(start);
assert(si);
unsigned best = start - &vertex[0];
unsigned best_scc = si->scc_of(best);
mcs_vertex* v = start->next;
while (v != start)
{
unsigned i = v - &vertex[0];
if (si->scc_of(i) > best_scc)
{
best = i;
best_scc = si->scc_of(i);
}
v = v->next;
}
return best;
}
};
}
/// \brief Return an ordering of the vertices computed by
/// a maximum cardinality search.
///
/// Unlike Tarjan's paper \cite tarjan.84.sicomp , where states are
/// numbered from N to 1, this number the states from 0 to N-1,
/// starting from the initial state. The next number is assigned to
/// a state that maximizes the number of already-numbered neighbors.
std::vector<unsigned>
maximum_cardinality_search(const const_twa_graph_ptr& a, mcs_tie_break tie)
{
unsigned n = a->num_states();
mcs_data data(n);
// We need to compute the neighbors of each state independently of
// the orientation of the edges.
std::vector<std::set<unsigned>> neighbors(n);
for (auto& e: a->edges())
{
neighbors[e.src].insert(e.dst);
neighbors[e.dst].insert(e.src);
}
// How to break ties when selecting the next vertex?
unsigned (mcs_data::* pick_state)(unsigned) = &mcs_data::select_any_vertex;
if (tie == MCS_TIE_SCC)
{
data.si = new scc_info(a, scc_info_options::NONE);
pick_state = &mcs_data::select_best_vertex_scc;
}
std::vector<unsigned> order(n, 0U); // order is α in Tarjan's paper
unsigned index = 0; // index is n-i in Tarjan's paper
int max_weight = 0; // max_weight is j in Tarjan's paper
auto number_state = [&](unsigned i)
{
order[i] = index++;
int& w = data.vertex[i].weight;
data.remove_vertex(i, w);
w = -1;
for (unsigned j: neighbors[i])
data.increase_vertex_weight(j);
++max_weight;
};
unsigned init = a->get_init_state_number();
number_state(init);
while (index < n)
{
while (max_weight > 0 && data.set[max_weight] == nullptr)
--max_weight;
number_state((data.*pick_state)(max_weight));
}
return order;
}
twa_graph_ptr
maximum_cardinality_search_reorder_here(twa_graph_ptr a, mcs_tie_break tie)
{
std::vector<unsigned> order = maximum_cardinality_search(a, tie);
a->defrag_states(order, order.size());
return a;
}
}

63
spot/twaalgos/mcs.hh Normal file
View file

@ -0,0 +1,63 @@
// -*- 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/>.
#pragma once
#include <spot/misc/common.hh>
#include <spot/twa/fwd.hh>
#include <vector>
namespace spot
{
enum mcs_tie_break
{
///\brief Break ties by picking the first possible state.
MCS_TIE_ANY = 0,
/// \brief Break ties by picking states from the "highest" SCCs
///
/// This is based on the topological ordering of SCCs computed
/// by scc_info. The initial state is always in the highest
/// SCC, and the smallest SCC has no exit.
MCS_TIE_SCC,
};
/// \brief Return an ordering of the vertices computed by
/// a maximum cardinality search.
///
/// Unlike Tarjan's paper \cite tarjan.84.sicomp , where states are
/// numbered from N to 1, this numbers the states from 0 to N-1,
/// starting from the initial state. The next number is assigned to
/// a state that maximizes the number of already-numbered neighbors.
///
/// This version returns a vector such that RESULTS[I] is the rank
/// of state I in the computed order.
///
/// \param tie specify how to break ties.
SPOT_API std::vector<unsigned>
maximum_cardinality_search(const const_twa_graph_ptr& a,
mcs_tie_break tie = MCS_TIE_ANY);
/// \brief Reorder the state of \a a according to the order
/// computed by maximum_cardinality_search().
///
/// This works in place and return the same automaton.
SPOT_API twa_graph_ptr
maximum_cardinality_search_reorder_here(twa_graph_ptr a,
mcs_tie_break tie = MCS_TIE_ANY);
}