add ltlsynt executable

For now, ltlsynt only handles LTL realizability. It uses a reduction to
parity game followed by Calude et al.'s reduction from parity game to
reachability game.

* bin/ltlsynt.cc, bin/Makefile.am, bin/man/ltlsynt.x,
bin/man/Makefile.am, bin/.gitignore: New binary.
* doc/org/arch.tex, doc/Makefile.am, doc/org/tools.org,
doc/org/ltlsynt.org: Document it.
* spot/misc/game.cc, spot/misc/game.hh, spot/misc/Makefile.am: Parity
game wrapper for parity automata + reachability game interface from
Calude et al.'s paper.
This commit is contained in:
Thibaud Michaud 2017-09-10 22:47:50 +02:00
parent 7a11842613
commit 0821c97eb8
13 changed files with 812 additions and 4 deletions

View file

@ -38,6 +38,7 @@ misc_HEADERS = \
escape.hh \
fixpool.hh \
formater.hh \
game.hh \
hash.hh \
hashfunc.hh \
intvcomp.hh \
@ -62,6 +63,7 @@ libmisc_la_SOURCES = \
bitvect.cc \
escape.cc \
formater.cc \
game.cc \
intvcomp.cc \
intvcmp2.cc \
memusage.cc \

224
spot/misc/game.cc Normal file
View file

@ -0,0 +1,224 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2017 Laboratoire de Recherche et Développement
// de l'Epita (LRDE).
//
// 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 <cmath>
#include "spot/misc/game.hh"
namespace spot
{
void parity_game::print(std::ostream& os)
{
os << "parity " << num_states() - 1 << ";\n";
std::vector<bool> seen(num_states(), false);
std::vector<unsigned> todo({get_init_state_number()});
while (!todo.empty())
{
unsigned src = todo.back();
todo.pop_back();
seen[src] = true;
os << src << ' ';
os << out(src).begin()->acc.max_set() - 1 << ' ';
os << owner(src) << ' ';
bool first = true;
for (auto& e: out(src))
{
if (!first)
os << ',';
first = false;
os << e.dst;
if (!seen[e.dst])
todo.push_back(e.dst);
}
if (src == get_init_state_number())
os << " \"INIT\"";
os << ";\n";
}
}
bool parity_game::solve_qp() const
{
return reachability_game(*this).is_reachable();
}
int reachability_state::compare(const state* other) const
{
auto o = down_cast<const reachability_state*>(other);
assert(o);
if (num_ != o->num())
return num_ - o->num();
if (b_ < o->b())
return -1;
if (b_ > o->b())
return 1;
return 0;
}
bool reachability_state::operator<(const reachability_state& o) const
{
// Heuristic to process nodes with a higher chance of leading to a target
// node first.
assert(b_.size() == o.b().size());
for (unsigned i = b_.size(); i > 0; --i)
if (b_[i - 1] != o.b()[i - 1])
return b_[i - 1] > o.b()[i - 1];
return num_ < o.num();
}
const reachability_state* reachability_game_succ_iterator::dst() const
{
// NB: colors are indexed at 1 in Calude et al.'s paper and at 0 in spot
// All acceptance sets are therefore incremented (which is already done by
// max_set), so that 0 can be kept as a special value indicating that no
// i-sequence is tracked at this index. Hence the parity switch in the
// following implementation, compared to the paper.
std::vector<unsigned> b = state_.b();
unsigned a = it_->acc.max_set();
assert(a);
unsigned i = -1U;
bool all_even = a % 2 == 0;
for (unsigned j = 0; j < b.size(); ++j)
{
if ((b[j] % 2 == 1 || b[j] == 0) && all_even)
i = j;
else if (b[j] > 0 && a > b[j])
i = j;
all_even = all_even && b[j] > 0 && b[j] % 2 == 0;
}
if (i != -1U)
{
b[i] = a;
for (unsigned j = 0; j < i; ++j)
b[j] = 0;
}
return new reachability_state(it_->dst, b, !state_.anke());
}
const reachability_state* reachability_game::get_init_state() const
{
// b[ceil(log(n + 1))] != 0 implies there is an i-sequence of length
// 2^(ceil(log(n + 1))) >= 2^log(n + 1) = n + 1, so it has to contain a
// cycle.
unsigned i = std::ceil(std::log2(pg_.num_states() + 1));
return new reachability_state(pg_.get_init_state_number(),
std::vector<unsigned>(i + 1),
false);
}
reachability_game_succ_iterator*
reachability_game::succ_iter(const state* s) const
{
auto state = down_cast<const reachability_state*>(s);
return new reachability_game_succ_iterator(pg_, *state);
}
std::string reachability_game::format_state(const state* s) const
{
auto state = down_cast<const reachability_state*>(s);
std::ostringstream fmt;
bool first = true;
fmt << state->num() << ", ";
fmt << '[';
for (unsigned b : state->b())
{
if (!first)
fmt << ',';
else
first = false;
fmt << b;
}
fmt << ']';
return fmt.str();
}
bool reachability_game::is_reachable()
{
std::set<spot::reachability_state> todo{*init_state_};
while (!todo.empty())
{
spot::reachability_state v = *todo.begin();
todo.erase(todo.begin());
std::vector<spot::const_reachability_state_ptr> succs;
spot::reachability_game_succ_iterator* it = succ_iter(&v);
for (it->first(); !it->done(); it->next())
succs.push_back(spot::const_reachability_state_ptr(it->dst()));
if (is_target(v))
{
c_[v] = 1;
if (mark(v))
return true;
continue;
}
else if (v.anke())
c_[v] = 1;
else
c_[v] = succs.size();
for (auto succ: succs)
{
if (parents_[*succ].empty())
{
if (*succ != *init_state_)
{
todo.insert(*succ);
parents_[*succ] = { v };
c_[*succ] = -1U;
}
}
else
{
parents_[*succ].push_back(v);
if (c_[*succ] == 0 && mark(v))
return true;
}
}
}
return false;
}
bool reachability_game::mark(const spot::reachability_state& s)
{
if (c_[s] > 0)
{
--c_[s];
if (c_[s] == 0)
{
if (s == *init_state_)
return true;
for (auto& u: parents_[s])
if (mark(u))
return true;
}
}
return false;
}
bool reachability_game::is_target(const reachability_state& v)
{
return v.b().back();
}
}

270
spot/misc/game.hh Normal file
View file

@ -0,0 +1,270 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2017 Laboratoire de Recherche et Développement
// de l'Epita (LRDE).
//
// 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 <algorithm>
#include <memory>
#include <ostream>
#include <vector>
#include <bddx.h>
#include <spot/twa/twagraph.hh>
#include <spot/twaalgos/parity.hh>
namespace spot
{
class SPOT_API parity_game
{
private:
const const_twa_graph_ptr dpa_;
const std::vector<bool> owner_;
public:
/// \a parity_game provides an interface to manipulate a deterministic parity
/// automaton as a parity game, including methods to solve the game.
///
/// \param dpa the underlying deterministic parity automaton
/// \param owner a vector of Booleans indicating the owner of each state,
/// with the convention that true represents player 1 and false represents
/// player 0.
parity_game(const twa_graph_ptr dpa, std::vector<bool> owner)
: dpa_(dpa), owner_(owner)
{
bool max;
bool odd;
dpa_->acc().is_parity(max, odd, true);
SPOT_ASSERT(max && odd);
SPOT_ASSERT(owner_.size() == dpa_->num_states());
}
unsigned num_states() const
{
return dpa_->num_states();
}
unsigned get_init_state_number() const
{
return dpa_->get_init_state_number();
}
internal::state_out<const twa_graph::graph_t>
out(unsigned src) const
{
return dpa_->out(src);
}
internal::state_out<const twa_graph::graph_t>
out(unsigned src)
{
return dpa_->out(src);
}
bool owner(unsigned src) const
{
return owner_[src];
}
/// Print the parity game in PGSolver's format.
void print(std::ostream& os);
/// Whether player 1 has a winning strategy from the initial state.
/// Implements Calude et al.'s quasipolynomial time algorithm.
/** \verbatim
@inproceedings{calude.17.stoc,
author = {Calude, Cristian S. and Jain, Sanjay and Khoussainov,
Bakhadyr and Li, Wei and Stephan, Frank},
title = {Deciding Parity Games in Quasipolynomial Time},
booktitle = {Proceedings of the 49th Annual ACM SIGACT Symposium on
Theory of Computing},
series = {STOC 2017},
year = {2017},
isbn = {978-1-4503-4528-6},
location = {Montreal, Canada},
pages = {252--263},
numpages = {12},
url = {http://doi.acm.org/10.1145/3055399.3055409},
doi = {10.1145/3055399.3055409},
acmid = {3055409},
publisher = {ACM},
address = {New York, NY, USA},
keywords = {Muller Games, Parity Games, Quasipolynomial Time Algorithm},
}
\endverbatim */
bool solve_qp() const;
};
class reachability_state: public state
{
private:
unsigned num_;
std::vector<unsigned> b_;
bool anke_;
public:
reachability_state(unsigned state, const std::vector<unsigned>& b,
bool anke)
: num_(state), b_(b), anke_(anke)
{
}
int compare(const state* other) const override;
bool operator==(const reachability_state& o) const
{
return compare(&o) == 0;
}
bool operator!=(const reachability_state& o) const
{
return compare(&o) != 0;
}
bool operator<(const reachability_state& o) const;
size_t hash() const override
{
size_t hash = wang32_hash(num_);
for (unsigned i = 0; i < b_.size(); ++i)
hash ^= wang32_hash(b_[i]) ^ wang32_hash(i);
return hash;
}
reachability_state* clone() const override
{
return new reachability_state(*this);
}
std::vector<unsigned> b() const
{
return b_;
}
unsigned num() const
{
return num_;
}
bool anke() const
{
return anke_;
}
};
typedef std::shared_ptr<const reachability_state>
const_reachability_state_ptr;
struct reachability_state_hash
{
size_t operator()(const reachability_state& state) const
{
return state.hash();
}
};
class reachability_game_succ_iterator final: public twa_succ_iterator
{
private:
const parity_game& pg_;
const reachability_state& state_;
internal::edge_iterator<const twa_graph::graph_t> it_;
public:
reachability_game_succ_iterator(const parity_game& pg,
const reachability_state& s)
: pg_(pg), state_(s)
{
}
bool first() override
{
it_ = pg_.out(state_.num()).begin();
return it_ != pg_.out(state_.num()).end();
}
bool next() override
{
++it_;
return it_ != pg_.out(state_.num()).end();
}
bool done() const override
{
return it_ == pg_.out(state_.num()).end();
}
const reachability_state* dst() const override;
bdd cond() const override
{
return bddtrue;
}
acc_cond::mark_t acc() const override
{
return 0;
}
};
// On-the-fly reachability game interface for a max even parity game such
// that a target is reachable iff there is a memoryless winning strategy
// in the parity game for player 1.
class reachability_game final: public twa
{
private:
typedef std::unordered_map<spot::reachability_state, unsigned,
spot::reachability_state_hash> wincount_t;
typedef std::unordered_map<spot::reachability_state,
std::vector<spot::reachability_state>,
spot::reachability_state_hash> parents_t;
const parity_game& pg_;
// number of successors that need to have a winning strategy in order for
// a given node to have a winning strategy.
wincount_t c_;
parents_t parents_;
const_reachability_state_ptr init_state_; // cache
public:
reachability_game(const parity_game& pg)
: twa(std::make_shared<bdd_dict>()),
pg_(pg)
{
init_state_ = std::shared_ptr<const reachability_state>(get_init_state());
}
const reachability_state* get_init_state() const override;
reachability_game_succ_iterator* succ_iter(const state* s) const override;
std::string format_state(const state* s) const override;
bool is_reachable();
private:
bool mark(const spot::reachability_state& s);
bool is_target(const reachability_state& s);
};
}