more documentation for twa_graph internals

* spot/graph/graph.hh, spot/twa/twagraph.hh, spot/twa/twagraph.cc:
Implement a dump_storage_as_dot() method.
* python/spot/__init__.py (twa_graph.show_storage): New method, above
dump_storage_as_dot().
* tests/python/twagraph-internals.ipynb: New file, with documentation
about the twa_graph internals, using show_storage() to illustrate
everything.
* tests/Makefile.am, doc/org/tut.org: Add it.
* python/spot/impl.i: Add bindings for out_iterasor, demonstrated in
the Python notebook.
* spot/twa/twa.hh: Add prop_reset().  Used in the notebook.
* NEWS: Mention the new notebook and function.
* doc/org/tut50.org: Link to the notebook.
* tests/python/ipnbdoctest.py: Adjust for twa_graph_ptr being
redefined in the spot namespace.
This commit is contained in:
Alexandre Duret-Lutz 2018-07-10 17:17:59 +02:00
parent d8bc50dcb7
commit 46590af693
12 changed files with 5880 additions and 6 deletions

View file

@ -1059,6 +1059,156 @@ namespace spot
}
}
enum dump_storage_items {
DSI_GraphHeader = 1,
DSI_GraphFooter = 2,
DSI_StatesHeader = 4,
DSI_StatesBody = 8,
DSI_StatesFooter = 16,
DSI_States = DSI_StatesHeader | DSI_StatesBody | DSI_StatesFooter,
DSI_EdgesHeader = 32,
DSI_EdgesBody = 64,
DSI_EdgesFooter = 128,
DSI_Edges = DSI_EdgesHeader | DSI_EdgesBody | DSI_EdgesFooter,
DSI_DestsHeader = 256,
DSI_DestsBody = 512,
DSI_DestsFooter = 1024,
DSI_Dests = DSI_DestsHeader | DSI_DestsBody | DSI_DestsFooter,
DSI_All =
DSI_GraphHeader | DSI_States | DSI_Edges | DSI_Dests | DSI_GraphFooter,
};
/// Dump the state and edge storage for debugging
void dump_storage_as_dot(std::ostream& o, int dsi = DSI_All) const
{
if (dsi & DSI_GraphHeader)
o << "digraph g { \nnode [shape=plaintext]\n";
unsigned send = states_.size();
if (dsi & DSI_StatesHeader)
{
o << ("states [label=<\n"
"<table border='0' cellborder='1' cellspacing='0'>\n"
"<tr><td sides='b' bgcolor='yellow' port='s'>states</td>\n");
for (unsigned s = 0; s < send; ++s)
o << "<td sides='b' bgcolor='yellow' port='s" << s << "'>"
<< s << "</td>\n";
o << "</tr>\n";
}
if (dsi & DSI_StatesBody)
{
o << "<tr><td port='ss'>succ</td>\n";
for (unsigned s = 0; s < send; ++s)
{
o << "<td port='ss" << s;
if (states_[s].succ)
o << "' bgcolor='cyan";
o << "'>" << states_[s].succ << "</td>\n";
}
o << "</tr><tr><td port='st'>succ_tail</td>\n";
for (unsigned s = 0; s < send; ++s)
{
o << "<td port='st" << s;
if (states_[s].succ_tail)
o << "' bgcolor='cyan";
o << "'>" << states_[s].succ_tail << "</td>\n";
}
o << "</tr>\n";
}
if (dsi & DSI_StatesFooter)
o << "</table>>]\n";
unsigned eend = edges_.size();
if (dsi & DSI_EdgesHeader)
{
o << ("edges [label=<\n"
"<table border='0' cellborder='1' cellspacing='0'>\n"
"<tr><td sides='b' bgcolor='cyan' port='e'>edges</td>\n");
for (unsigned e = 1; e < eend; ++e)
{
o << "<td sides='b' bgcolor='"
<< (e != edges_[e].next_succ ? "cyan" : "gray")
<< "' port='e" << e << "'>" << e << "</td>\n";
}
o << "</tr>";
}
if (dsi & DSI_EdgesBody)
{
o << "<tr><td port='ed'>dst</td>\n";
for (unsigned e = 1; e < eend; ++e)
{
o << "<td port='ed" << e;
int d = edges_[e].dst;
if (d < 0)
o << "' bgcolor='pink'>~" << ~d;
else
o << "' bgcolor='yellow'>" << d;
o << "</td>\n";
}
o << "</tr><tr><td port='en'>next_succ</td>\n";
for (unsigned e = 1; e < eend; ++e)
{
o << "<td port='en" << e;
if (edges_[e].next_succ)
{
if (edges_[e].next_succ != e)
o << "' bgcolor='cyan";
else
o << "' bgcolor='gray";
}
o << "'>" << edges_[e].next_succ << "</td>\n";
}
o << "</tr><tr><td port='es'>src</td>\n";
for (unsigned e = 1; e < eend; ++e)
o << "<td port='es" << e << "' bgcolor='yellow'>"
<< edges_[e].src << "</td>\n";
o << "</tr>\n";
}
if (dsi & DSI_EdgesFooter)
o << "</table>>]\n";
if (!dests_.empty())
{
unsigned dend = dests_.size();
if (dsi & DSI_DestsHeader)
{
o << ("dests [label=<\n"
"<table border='0' cellborder='1' cellspacing='0'>\n"
"<tr><td sides='b' bgcolor='pink' port='d'>dests</td>\n");
unsigned d = 0;
while (d < dend)
{
o << "<td sides='b' bgcolor='pink' port='d"
<< d << "'>~" << d << "</td>\n";
unsigned cnt = dests_[d];
d += cnt + 1;
while (cnt--)
o << "<td sides='b'></td>\n";
}
o << "</tr>\n";
}
if (dsi & DSI_DestsBody)
{
o << "<tr><td port='dd'>#cnt/dst</td>\n";
unsigned d = 0;
while (d < dend)
{
unsigned cnt = dests_[d];
o << "<td port='d'>#" << cnt << "</td>\n";
++d;
while (cnt--)
{
o << "<td bgcolor='yellow' port='dd"
<< d << "'>" << dests_[d] << "</td>\n";
++d;
}
}
o << "</tr>\n";
}
if (dsi & DSI_DestsFooter)
o << "</table>>]\n";
}
if (dsi & DSI_GraphFooter)
o << "}\n";
}
/// \brief Remove all dead edges.
///
/// The edges_ vector is left in a state that is incorrect and

View file

@ -1071,6 +1071,7 @@ namespace spot
bprop is;
};
protected:
#ifndef SWIG
// Dynamic properties, are given with a name and a destructor function.
std::unordered_map<std::string,
@ -1686,6 +1687,10 @@ namespace spot
prop_stutter_invariant(trival::maybe());
}
void prop_reset()
{
prop_keep({});
}
};
#ifndef SWIG

View file

@ -21,6 +21,8 @@
#include <spot/twa/twagraph.hh>
#include <spot/tl/print.hh>
#include <spot/misc/bddlt.hh>
#include <spot/twa/bddprint.hh>
#include <spot/misc/escape.hh>
#include <vector>
#include <deque>
@ -764,6 +766,141 @@ namespace spot
set_named_prop("state-names", names.release());
}
void twa_graph::dump_storage_as_dot(std::ostream& out,
const char* opt) const
{
bool want_vectors = false;
bool want_data = false;
bool want_properties = false;
if (!opt || !*opt)
{
want_vectors = want_data = want_properties = true;
}
else
{
while (*opt)
switch (*opt++)
{
case 'v':
want_vectors = true;
break;
case 'd':
want_data = true;
break;
case 'p':
want_properties = true;
break;
default:
throw std::runtime_error(std::string("dump_storage_as_dow(): "
"unsupported option '")
+ opt[-1] +"'");
}
}
const graph_t& g = get_graph();
g.dump_storage_as_dot(out, graph_t::DSI_GraphHeader);
out << "rankdir=BT\n";
if (want_vectors)
{
out << "{rank=same;\n";
g.dump_storage_as_dot(out, graph_t::DSI_States |
graph_t::DSI_EdgesHeader);
auto edges = g.edge_vector();
unsigned eend = edges.size();
out << "<tr><td>cond</td>\n";
for (unsigned e = 1; e < eend; ++e)
{
out << "<td>";
std::string f = bdd_format_formula(get_dict(), edges[e].cond);
escape_html(out, f);
out << "</td>\n";
}
out << "</tr>\n<tr><td>acc</td>\n";
for (unsigned e = 1; e < eend; ++e)
out << "<td>" << edges[e].acc << "</td>\n";
out << "</tr>\n";
g.dump_storage_as_dot(out, graph_t::DSI_EdgesBody
| graph_t::DSI_EdgesFooter
| graph_t::DSI_Dests);
out << "}\n";
}
if (want_data || want_properties)
{
out << "{rank=same;\n";
if (want_data)
{
out << ("meta [label=<\n"
"<table border='0' cellborder='0' cellspacing='0'>\n");
unsigned d = get_init_state_number();
out << ("<tr><td align='left'>init_state:</td>"
"<td align='left' bgcolor='");
if ((int)d < 0)
out << "pink'>~" << ~d;
else
out << "yellow'>" << d;
out << ("</td></tr><tr><td align='left'>num_sets:</td>"
"<td align='left' >")
<< num_sets()
<< ("</td></tr><tr><td align='left'>acceptance:</td>"
"<td align='left' >");
get_acceptance().to_html(out);
out << ("</td></tr><tr><td align='left'>ap_vars:</td>"
"<td align='left'>");
escape_html(out, bdd_format_sat(get_dict(), ap_vars()));
out << "</td></tr></table>>]\n";
}
if (want_properties)
{
out << ("props [label=<\n"
"<table border='0' cellborder='0' cellspacing='0'>\n");
#define print_prop(name) \
out << ("<tr><td align='left'>" #name ":</td>" \
"<td align='left' >") << name() << "</td></tr>\n";
print_prop(prop_state_acc);
print_prop(prop_inherently_weak);
print_prop(prop_terminal);
print_prop(prop_weak);
print_prop(prop_very_weak);
print_prop(prop_complete);
print_prop(prop_universal);
print_prop(prop_unambiguous);
print_prop(prop_semi_deterministic);
print_prop(prop_stutter_invariant);
#undef print_prop
out << "</table>>]\n";
if (!named_prop_.empty())
{
out << "namedprops [label=\"named properties:\n";
for (auto p: named_prop_)
escape_html(out, p.first) << '\n';
out << "\"]\n";
}
}
out << "}\n";
}
if (want_data && want_vectors)
out << "meta -> states [style=invis]\n";
if (want_properties && want_vectors)
{
out << "props -> edges [style=invis]\n";
if (!named_prop_.empty())
{
out << "namedprops -> edges [style=invis]\n";
if (!is_existential())
out << "namedprops -> dests [style=invis]\n";
}
}
g.dump_storage_as_dot(out, graph_t::DSI_GraphFooter);
}
namespace
{
twa_graph_ptr

View file

@ -471,6 +471,12 @@ namespace spot
return g_.out(src);
}
internal::killer_edge_iterator<graph_t>
out_iteraser(unsigned src)
{
return g_.out_iteraser(src);
}
internal::const_universal_dests
univ_dests(unsigned d) const noexcept
{
@ -525,11 +531,18 @@ namespace spot
SPOT_RETURN(g_.edge_vector());
auto edge_vector()
SPOT_RETURN(g_.edge_vector());
auto is_dead_edge(const graph_t::edge_storage_t& t) const
SPOT_RETURN(g_.is_dead_edge(t));
#endif
bool is_dead_edge(unsigned t) const
{
return g_.is_dead_edge(t);
}
bool is_dead_edge(const graph_t::edge_storage_t& t) const
{
return g_.is_dead_edge(t);
}
/// \brief Merge edges that can be merged.
///
/// This makes two passes over the automaton to reduce the number
@ -682,6 +695,14 @@ namespace spot
/// Use -1U to erase a state.
/// \param used_states the number of states used (after renumbering)
void defrag_states(std::vector<unsigned>&& newst, unsigned used_states);
/// \brief Print the data structures used to represent the
/// automaton in dot's format.
///
/// \a opt should be a substring of "vdp" if you want to print
/// only the vectors, data, or properties.
void dump_storage_as_dot(std::ostream& out,
const char* opt = nullptr) const;
};
/// \ingroup twa_representation