Fixes #582. * spot/twaalgos/stats.cc: Add 0.5 to the result of bdd_satcountset() before truncating it. * NEWS: Mention the bug.
507 lines
13 KiB
C++
507 lines
13 KiB
C++
// -*- 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 <iostream>
|
|
#include <sstream>
|
|
#include <spot/twa/twa.hh>
|
|
#include <spot/twaalgos/stats.hh>
|
|
#include <spot/twaalgos/reachiter.hh>
|
|
#include <spot/tl/print.hh>
|
|
#include <spot/twaalgos/isdet.hh>
|
|
#include <spot/twaalgos/isweakscc.hh>
|
|
#include <cstring>
|
|
|
|
namespace spot
|
|
{
|
|
unsigned long long
|
|
count_all_transitions(const const_twa_graph_ptr& g)
|
|
{
|
|
unsigned long long tr = 0;
|
|
bdd v = g->ap_vars();
|
|
for (auto& e: g->edges())
|
|
// We add 0.5 to work around rounding errors in the computation
|
|
// of bdd_satcountset(), as the conversion is done by
|
|
// truncation. See issue #582.
|
|
tr += 0.5 + bdd_satcountset(e.cond, v);
|
|
return tr;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
class stats_bfs: public twa_reachable_iterator_breadth_first
|
|
{
|
|
public:
|
|
stats_bfs(const const_twa_ptr& a, twa_statistics& s)
|
|
: twa_reachable_iterator_breadth_first(a), s_(s)
|
|
{
|
|
}
|
|
|
|
void
|
|
process_state(const state*, int, twa_succ_iterator*) override final
|
|
{
|
|
++s_.states;
|
|
}
|
|
|
|
void
|
|
process_link(const state*, int, const state*, int,
|
|
const twa_succ_iterator*) override
|
|
{
|
|
++s_.edges;
|
|
}
|
|
|
|
private:
|
|
twa_statistics& s_;
|
|
};
|
|
|
|
class sub_stats_bfs final: public stats_bfs
|
|
{
|
|
public:
|
|
sub_stats_bfs(const const_twa_ptr& a, twa_sub_statistics& s)
|
|
: stats_bfs(a, s), s_(s), apvars_(a->ap_vars())
|
|
{
|
|
}
|
|
|
|
void
|
|
process_link(const state*, int, const state*, int,
|
|
const twa_succ_iterator* it) override
|
|
{
|
|
++s_.edges;
|
|
// We add 0.5 to work around rounding errors in the
|
|
// computation of bdd_satcountset(), as the conversion is done
|
|
// by truncation. See issue #582.
|
|
s_.transitions += 0.5 + bdd_satcountset(it->cond(), apvars_);
|
|
}
|
|
|
|
private:
|
|
twa_sub_statistics& s_;
|
|
bdd apvars_;
|
|
};
|
|
|
|
|
|
|
|
template<typename SU, typename EU>
|
|
void dfs(const const_twa_graph_ptr& ge, SU state_update, EU edge_update)
|
|
{
|
|
unsigned init = ge->get_init_state_number();
|
|
unsigned num_states = ge->num_states();
|
|
// The TODO vector serves two purposes:
|
|
// - it is a stack of states to process,
|
|
// - it is a set of processed states.
|
|
// The lower 31 bits of each entry is a state on the stack. (The
|
|
// next usable entry on the stack is indicated by todo_pos.) The
|
|
// 32th bit (i.e., the sign bit) of todo[x] indicates whether
|
|
// states number x has been seen.
|
|
std::vector<unsigned> todo(num_states, 0);
|
|
const unsigned seen = 1 << (sizeof(unsigned)*8-1);
|
|
const unsigned mask = seen - 1;
|
|
unsigned todo_pos = 0;
|
|
for (unsigned i: ge->univ_dests(init))
|
|
{
|
|
todo[todo_pos++] = i;
|
|
todo[i] |= seen;
|
|
}
|
|
do
|
|
{
|
|
state_update();
|
|
unsigned cur = todo[--todo_pos] & mask;
|
|
todo[todo_pos] ^= cur; // Zero the state
|
|
for (auto& t: ge->out(cur))
|
|
{
|
|
edge_update(t.cond);
|
|
for (unsigned dst: ge->univ_dests(t.dst))
|
|
if (!(todo[dst] & seen))
|
|
{
|
|
todo[dst] |= seen;
|
|
todo[todo_pos++] |= dst;
|
|
}
|
|
}
|
|
}
|
|
while (todo_pos > 0);
|
|
}
|
|
|
|
} // anonymous
|
|
|
|
|
|
std::ostream& twa_statistics::dump(std::ostream& out) const
|
|
{
|
|
out << "edges: " << edges << '\n';
|
|
out << "states: " << states << '\n';
|
|
return out;
|
|
}
|
|
|
|
std::ostream& twa_sub_statistics::dump(std::ostream& out) const
|
|
{
|
|
out << "transitions: " << transitions << '\n';
|
|
this->twa_statistics::dump(out);
|
|
return out;
|
|
}
|
|
|
|
twa_statistics
|
|
stats_reachable(const const_twa_ptr& g)
|
|
{
|
|
twa_statistics s;
|
|
auto ge = std::dynamic_pointer_cast<const twa_graph>(g);
|
|
if (!ge)
|
|
{
|
|
stats_bfs d(g, s);
|
|
d.run();
|
|
}
|
|
else
|
|
{
|
|
dfs(ge,
|
|
[&s](){ ++s.states; },
|
|
[&s](bdd){ ++s.edges; });
|
|
}
|
|
return s;
|
|
}
|
|
|
|
twa_sub_statistics
|
|
sub_stats_reachable(const const_twa_ptr& g)
|
|
{
|
|
twa_sub_statistics s;
|
|
auto ge = std::dynamic_pointer_cast<const twa_graph>(g);
|
|
if (!ge)
|
|
{
|
|
sub_stats_bfs d(g, s);
|
|
d.run();
|
|
}
|
|
else
|
|
{
|
|
dfs(ge,
|
|
[&s](){ ++s.states; },
|
|
[&s, &ge](bdd cond)
|
|
{
|
|
++s.edges;
|
|
// We add 0.5 to work around rounding errors in the
|
|
// computation of bdd_satcountset(), as the conversion
|
|
// is done by truncation. See issue #582.
|
|
s.transitions += 0.5 + bdd_satcountset(cond, ge->ap_vars());
|
|
});
|
|
}
|
|
return s;
|
|
}
|
|
|
|
void printable_formula::print(std::ostream& os, const char*) const
|
|
{
|
|
print_psl(os, val_);
|
|
}
|
|
|
|
void printable_scc_info::print(std::ostream& os, const char* pos) const
|
|
{
|
|
unsigned n = val_->scc_count();
|
|
if (*pos != '[')
|
|
{
|
|
os << n;
|
|
return;
|
|
}
|
|
bool accepting = false;
|
|
bool rejecting = false;
|
|
bool trivial = false;
|
|
bool non_trivial = false;
|
|
bool terminal = false;
|
|
bool non_terminal = false;
|
|
bool weak = false;
|
|
bool non_weak = false;
|
|
bool inherently_weak = false;
|
|
bool non_inherently_weak = false;
|
|
bool complete = false;
|
|
bool non_complete = false;
|
|
|
|
const char* beg = pos;
|
|
auto error = [&](std::string str)
|
|
{
|
|
std::ostringstream tmp;
|
|
const char* end = std::strchr(pos, ']');
|
|
tmp << "unknown option '" << str << "' in '%"
|
|
<< std::string(beg, end + 2) << '\'';
|
|
throw std::runtime_error(tmp.str());
|
|
};
|
|
|
|
do
|
|
{
|
|
++pos;
|
|
switch (*pos)
|
|
{
|
|
case 'a':
|
|
case 'R':
|
|
accepting = true;
|
|
break;
|
|
case 'A':
|
|
case 'r':
|
|
rejecting = true;
|
|
break;
|
|
case 'c':
|
|
complete = true;
|
|
break;
|
|
case 'C':
|
|
non_complete = true;
|
|
break;
|
|
case 't':
|
|
terminal = true;
|
|
break;
|
|
case 'T':
|
|
non_terminal = true;
|
|
break;
|
|
case 'v':
|
|
trivial = true;
|
|
break;
|
|
case 'V':
|
|
non_trivial = true;
|
|
break;
|
|
case 'w':
|
|
weak = true;
|
|
break;
|
|
case 'W':
|
|
non_weak = true;
|
|
break;
|
|
case 'i':
|
|
if (pos[1] == 'w')
|
|
{
|
|
inherently_weak = true;
|
|
++pos;
|
|
}
|
|
else
|
|
{
|
|
error(std::string(pos, pos + 2));
|
|
}
|
|
break;
|
|
case 'I':
|
|
if (pos[1] == 'W')
|
|
{
|
|
non_inherently_weak = true;
|
|
++pos;
|
|
}
|
|
else
|
|
{
|
|
error(std::string(pos, pos + 2));
|
|
}
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
case ',':
|
|
case ']':
|
|
break;
|
|
default:
|
|
error(std::string(pos, pos + 1));
|
|
}
|
|
}
|
|
while (*pos != ']');
|
|
|
|
val_->determine_unknown_acceptance();
|
|
unsigned count = 0U;
|
|
|
|
for (unsigned i = 0; i < n; ++i)
|
|
{
|
|
if (accepting && val_->is_rejecting_scc(i))
|
|
continue;
|
|
if (rejecting && val_->is_accepting_scc(i))
|
|
continue;
|
|
if (trivial && !val_->is_trivial(i))
|
|
continue;
|
|
if (non_trivial && val_->is_trivial(i))
|
|
continue;
|
|
if (complete && !is_complete_scc(*val_, i))
|
|
continue;
|
|
if (non_complete && is_complete_scc(*val_, i))
|
|
continue;
|
|
if (terminal && !is_terminal_scc(*val_, i))
|
|
continue;
|
|
if (non_terminal && is_terminal_scc(*val_, i))
|
|
continue;
|
|
if (weak && !is_weak_scc(*val_, i))
|
|
continue;
|
|
if (non_weak && is_weak_scc(*val_, i))
|
|
continue;
|
|
if (inherently_weak && !is_inherently_weak_scc(*val_, i))
|
|
continue;
|
|
if (non_inherently_weak && is_inherently_weak_scc(*val_, i))
|
|
continue;
|
|
++count;
|
|
}
|
|
|
|
os << count;
|
|
}
|
|
|
|
void printable_acc_cond::print(std::ostream& os, const char* pos) const
|
|
{
|
|
if (*pos != '[')
|
|
{
|
|
os << val_.get_acceptance();
|
|
return;
|
|
}
|
|
const char* beg = pos;
|
|
++pos;
|
|
const char* end = strchr(pos, ']');
|
|
try
|
|
{
|
|
os << val_.name(std::string(pos, end).c_str());
|
|
}
|
|
catch (const std::runtime_error& e)
|
|
{
|
|
std::ostringstream tmp;
|
|
tmp << "while processing %"
|
|
<< std::string(beg, end + 2) << ", ";
|
|
tmp << e.what();
|
|
throw std::runtime_error(tmp.str());
|
|
}
|
|
}
|
|
|
|
void printable_size::print(std::ostream& os, const char* pos) const
|
|
{
|
|
char p = 'r';
|
|
if (*pos == '[')
|
|
{
|
|
p = pos[1];
|
|
if (pos[2] != ']' || !(p == 'r' || p == 'u' || p == 'a'))
|
|
{
|
|
const char* end = strchr(pos + 1, ']');
|
|
std::ostringstream tmp;
|
|
tmp << "while processing %"
|
|
<< std::string(pos, end + 2) << ", "
|
|
<< "only [a], [r], or [u] is supported.";
|
|
throw std::runtime_error(tmp.str());
|
|
}
|
|
}
|
|
switch (p)
|
|
{
|
|
case 'r':
|
|
os << reachable_;
|
|
return;
|
|
case 'a':
|
|
os << all_;
|
|
return;
|
|
case 'u':
|
|
os << all_ - reachable_;
|
|
return;
|
|
}
|
|
SPOT_UNREACHABLE();
|
|
return;
|
|
}
|
|
|
|
void printable_long_size::print(std::ostream& os, const char* pos) const
|
|
{
|
|
char p = 'r';
|
|
if (*pos == '[')
|
|
{
|
|
p = pos[1];
|
|
if (pos[2] != ']' || !(p == 'r' || p == 'u' || p == 'a'))
|
|
{
|
|
const char* end = strchr(pos + 1, ']');
|
|
std::ostringstream tmp;
|
|
tmp << "while processing %"
|
|
<< std::string(pos, end + 2) << ", "
|
|
<< "only [a], [r], or [u] is supported.";
|
|
throw std::runtime_error(tmp.str());
|
|
}
|
|
}
|
|
switch (p)
|
|
{
|
|
case 'r':
|
|
os << reachable_;
|
|
return;
|
|
case 'a':
|
|
os << all_;
|
|
return;
|
|
case 'u':
|
|
os << all_ - reachable_;
|
|
return;
|
|
}
|
|
SPOT_UNREACHABLE();
|
|
return;
|
|
}
|
|
|
|
|
|
stat_printer::stat_printer(std::ostream& os, const char* format)
|
|
: format_(format)
|
|
{
|
|
declare('a', &acc_);
|
|
declare('c', &scc_);
|
|
declare('d', &deterministic_);
|
|
declare('e', &edges_);
|
|
declare('f', &form_);
|
|
declare('g', &gen_acc_);
|
|
declare('n', &nondetstates_);
|
|
declare('p', &complete_);
|
|
declare('s', &states_);
|
|
declare('S', &scc_); // Historical. Deprecated. Use %c instead.
|
|
declare('t', &trans_);
|
|
set_output(os);
|
|
if (format)
|
|
prime(format);
|
|
}
|
|
|
|
std::ostream&
|
|
stat_printer::print(const const_twa_graph_ptr& aut, formula f)
|
|
{
|
|
form_ = f;
|
|
|
|
if (has('t'))
|
|
{
|
|
twa_sub_statistics s = sub_stats_reachable(aut);
|
|
states_.set(s.states, aut->num_states());
|
|
edges_.set(s.edges, aut->num_edges());
|
|
trans_.set(s.transitions, count_all_transitions(aut));
|
|
}
|
|
else if (has('s') || has('e'))
|
|
{
|
|
twa_statistics s = stats_reachable(aut);
|
|
states_.set(s.states, aut->num_states());
|
|
edges_.set(s.edges, aut->num_edges());
|
|
}
|
|
|
|
if (has('a'))
|
|
acc_ = aut->num_sets();
|
|
|
|
// %S was renamed to %c in Spot 1.2, so that autfilt could use %S
|
|
// and %s to designate the state numbers in input and output
|
|
// automate. We still recognize %S an obsolete and undocumented
|
|
// alias for %c, if it is not overridden by a subclass.
|
|
if (has('c') || has('S'))
|
|
scc_.automaton(aut);
|
|
|
|
if (has('n'))
|
|
{
|
|
nondetstates_ = count_nondet_states(aut);
|
|
deterministic_ = (nondetstates_ == 0);
|
|
}
|
|
else if (has('d'))
|
|
{
|
|
// This is more efficient than calling count_nondet_state().
|
|
deterministic_ = is_deterministic(aut);
|
|
}
|
|
|
|
if (has('p'))
|
|
{
|
|
complete_ = is_complete(aut);
|
|
}
|
|
|
|
if (has('g'))
|
|
gen_acc_ = aut->acc();
|
|
|
|
auto& os = format(format_);
|
|
// Make sure we do not hold a pointer to the automaton or the
|
|
// formula, as these may prevent atomic proposition to be freed
|
|
// before a next job.
|
|
scc_.reset();
|
|
form_ = nullptr;
|
|
return os;
|
|
}
|
|
|
|
}
|