From 2c267dd89442c9d9886ccccc0f9631c9c43e3b55 Mon Sep 17 00:00:00 2001
From: philipp
Date: Tue, 10 Aug 2021 23:58:26 +0200
Subject: [PATCH] Adding dot suppport for aiger class
* spot/twaalgos/aiger.cc: Useless assert
* spot/twaalgos/dot.hh,
spot/twaalgos/dot.cc: aig to dot
* python/spot/__init__.py: Adapting
* tests/python/games.ipynb: Additional tests
---
python/spot/__init__.py | 53 ++++++
spot/twaalgos/aiger.cc | 2 -
spot/twaalgos/dot.cc | 197 ++++++++++++++++++++++
spot/twaalgos/dot.hh | 5 +
tests/python/games.ipynb | 343 +++++++++++++++++++++++++++++++++++++--
5 files changed, 583 insertions(+), 17 deletions(-)
diff --git a/python/spot/__init__.py b/python/spot/__init__.py
index 25ca26138..9ce1701c1 100644
--- a/python/spot/__init__.py
+++ b/python/spot/__init__.py
@@ -136,6 +136,28 @@ def __om_init_new(self, str=None):
option_map.__init__ = __om_init_new
+@_extend(aig)
+class aig:
+ def _repr_svg_(self, opt=None):
+ ostr = ostringstream()
+ print_dot(ostr, self, opt)
+ return _ostream_to_svg(ostr)
+
+ def show(self, opt=None):
+ from spot.jupyter import SVG
+ return SVG(self._repr_svg_(opt))
+
+ def to_str(self, format='circuit', opt=None):
+ format = format.lower()
+ if format == 'circuit':
+ ostr = ostringstream()
+ print_aiger(ostr, self)
+ return ostr.str()
+ if format == 'dot':
+ ostr = ostringstream()
+ print_dot(ostr, self, opt)
+ return ostr.str()
+ raise ValueError("unknown string format: " + format)
__twa__acc1 = twa.acc
__twa__acc2 = twa.get_acceptance
@@ -683,6 +705,37 @@ def automaton(filename, **kwargs):
except StopIteration:
raise RuntimeError("Failed to read automaton from {}".format(filename))
+def aiger_circuits(*sources, bdd_dict = None):
+ """Read aiger circuits from a list of sources.
+
+ Parameters
+ ----------
+ *sources : list of str
+ These sources can be either commands (end with `|`),
+ textual representations of the circuit in restricted aag format
+ (start with aag and contain '\n'), or filenames (else).
+ bdd_dict : If not supplied, a fresh bdd_dict will be created, common for
+ all of the circuits.
+ """
+
+ bdd_dict = bdd_dict if bdd_dict is not None else make_bdd_dict()
+
+ for source in sources:
+ if (source.startswith("aag") and '\n' in source):
+ yield aig.parse_aag(source, "", bdd_dict)
+ else:
+ yield aig.parse_aag(source, bdd_dict)
+
+def aiger_circuit(source, bdd_dict = None):
+ """Read a single aiger circuit from file or textual representation.
+
+ See `spot.aiger_circuits` for a list of supported formats."""
+ try:
+ return next(aiger_circuits(source, bdd_dict = bdd_dict))
+ except StopIteration:
+ raise RuntimeError("Failed to read an aiger circuit "
+ "from {}".format(source))
+
def _postproc_translate_options(obj, default_type, *args):
type_name_ = None
diff --git a/spot/twaalgos/aiger.cc b/spot/twaalgos/aiger.cc
index 78ca35f58..17cfbc5da 100644
--- a/spot/twaalgos/aiger.cc
+++ b/spot/twaalgos/aiger.cc
@@ -1086,8 +1086,6 @@ namespace spot
std::function get_gate_bdd;
get_gate_bdd = [&](unsigned g)->bdd
{
- assert(v2g(circ.gate_var(g) == g));
-
unsigned v = circ.gate_var(g);
auto it = var2bdd.find(v);
if (it != var2bdd.end())
diff --git a/spot/twaalgos/dot.cc b/spot/twaalgos/dot.cc
index 2fb9d7772..5eed4f65c 100644
--- a/spot/twaalgos/dot.cc
+++ b/spot/twaalgos/dot.cc
@@ -1065,5 +1065,202 @@ namespace spot
return os;
}
+ namespace
+ {
+ class dotty_aig final
+ {
+ public:
+ bool vertical = true;
+ bool latex = false;
+ std::ostream& os_;
+
+ void
+ parse_opts(const char *options)
+ {
+ while (char c = *options++)
+ switch (c)
+ {
+ case 'h':
+ {
+ vertical = false;
+ break;
+ }
+ case 'v':
+ {
+ vertical = true;
+ break;
+ }
+ default:
+ throw std::runtime_error("unknown option for print_dot(): "s + c);
+ }
+ }
+
+ dotty_aig(std::ostream &os, const char *options)
+ : os_(os)
+ {
+ parse_opts(options ? options : "");
+ }
+
+ void print(const aig_ptr &circuit)
+ {
+ if (!circuit)
+ return;
+ auto is_input = [nb_in = circuit->num_inputs()](unsigned val)
+ {
+ return (val & ~1) <= (2 * nb_in);
+ };
+
+ const auto& in_names = circuit->input_names();
+ const auto& out_names = circuit->output_names();
+
+ auto num_latches = circuit->num_latches();
+ auto latches = circuit->next_latches();
+ auto num_inputs = circuit->num_inputs();
+ auto num_outputs = circuit->num_outputs();
+ auto outputs = circuit->outputs();
+ auto num_gates = circuit->num_gates();
+ auto gates = circuit->gates();
+
+ // If a circuit doesn't use an input, we need a special placement
+ bool uses_in = false;
+
+ auto add_edge = [](std::ostream &stream, const unsigned src,
+ const unsigned dst) {
+ auto src_gate = src & ~1;
+ stream << src_gate;
+ stream << " -> " << dst;
+ if (src & 1)
+ stream << " [arrowhead=dot]";
+ stream << '\n';
+ };
+
+ if (vertical)
+ os_ << "digraph \"\" {\nrankdir = BT;\n";
+ else
+ os_ << "digraph \"\" {\nrankdir = LR;\n";
+
+ os_ << "# latches left\n";
+ for (unsigned i = 0; i < num_latches; ++i)
+ {
+ auto first = circuit->latch_var(i);
+ auto first_m_mod = first & ~1;
+ os_ << "node[shape=box, label=\"L" << i << "_out\"] "
+ << first_m_mod << ";\n";
+ }
+
+ //Predefine all nodes
+
+ os_ << "# input nodes\n";
+ for (unsigned i = 0; i < num_inputs; ++i)
+ os_ << "node [shape=triangle, label=\""
+ << in_names[i] << "\"] "
+ << circuit->input_var(i) << ";\n";
+
+ os_ << "# latch nodes\n";
+ for (unsigned i = 0; i < num_latches; ++i)
+ os_ << "node[shape=box, label=\"L" << i << "\"] L" << i << ";\n";
+
+ os_ << "# gate nodes\n";
+ for (unsigned i = 0; i < num_gates; ++i)
+ if ((gates[i].first != 0) && (gates[i].second != 0))
+ {
+ uses_in |= is_input(gates[i].first) | is_input(gates[i].second);
+ auto gate_var = circuit->gate_var(i);
+ os_ << "node [shape=circle, label=\"" << gate_var
+ << "\"] " << gate_var << ";\n";
+ }
+
+ os_ << "# and ins\n";
+ for (unsigned i = 0; i < num_gates; ++i)
+ if ((gates[i].first != 0) && (gates[i].second != 0))
+ {
+ auto gate_var = circuit->gate_var(i);
+ add_edge(os_, gates[i].first, gate_var);
+ add_edge(os_, gates[i].second, gate_var);
+ }
+
+ bool has_alone_gate = false;
+
+ os_ << "# Latches\n";
+ for (unsigned i = 0; i < num_latches; ++i)
+ {
+ auto second = latches[i];
+ os_ << "node[shape=box, label=\"L" << i << "_in\"] L" << i << ";\n";
+ if (second <= 1 && !has_alone_gate)
+ {
+ os_ << "node[shape=box, label=\"Const\"] 0" << std::endl;
+ has_alone_gate = true;
+ }
+ os_ << (second & ~1) << " -> L" << i;
+ if (i & 1)
+ os_ << "[arrowhead=dot]";
+ os_ << '\n';
+ }
+
+ // Outs can be defined after everything else
+ os_ << "# Outs\n";
+ const char* out_pos = vertical ? ":s" : ":w";
+ for (unsigned i = 0; i < num_outputs; ++i)
+ {
+ os_ << "node [shape=triangle, orientation=180, label=\""
+ << out_names[i] << "\"] o" << i
+ << out_names[i] << ";\n";
+ auto z = outputs[i];
+ uses_in |= is_input(z);
+ if (z <= 1 && !has_alone_gate)
+ {
+ os_ << "node[shape=box, label=\"Const\"] 0" << std::endl;
+ has_alone_gate = true;
+ }
+ os_ << (z & ~1) << "->" << 'o' << i << out_names[i] << out_pos;
+ if (outputs[i] & 1)
+ os_ << " [arrowhead=dot]";
+ os_ << '\n';
+ }
+
+ if (has_alone_gate || num_inputs > 0)
+ {
+ os_ << (uses_in ? "{rank = source; " : "{rank = same; ");
+ for (unsigned i = 0; i < num_inputs; ++i)
+ os_ << circuit->input_var(i) << "; ";
+ if (has_alone_gate)
+ os_ << "0; ";
+ os_ << "}\n";
+ }
+ if (num_outputs > 0)
+ {
+ os_ << "{rank = sink; ";
+ for (unsigned i = 0; i < num_outputs; ++i)
+ os_ << 'o' << i << out_names[i] << "; ";
+ os_ << "}\n";
+ }
+ if (num_latches > 0)
+ {
+ os_ << "{rank = same; ";
+ for (unsigned i = 0; i < num_latches; ++i)
+ os_ << 'L' << i << "; ";
+ os_ << "}\n{rank = same; ";
+ for (unsigned i = 0; i < num_latches; ++i)
+ {
+ auto first = circuit->latch_var(i);
+ auto first_m_mod = first & ~1;
+ os_ << first_m_mod << "; ";
+ }
+ os_ << "}\n";
+ }
+ os_ << "}\n";
+ }
+ };
+ }
+
+ std::ostream &
+ print_dot(std::ostream &os, aig_ptr circuit, const char* options)
+ {
+ dotty_aig d(os, options);
+ if (!circuit)
+ return os;
+ d.print(circuit);
+ return os;
+ }
}
diff --git a/spot/twaalgos/dot.hh b/spot/twaalgos/dot.hh
index 0fdea7ff7..1ef8c3e27 100644
--- a/spot/twaalgos/dot.hh
+++ b/spot/twaalgos/dot.hh
@@ -25,6 +25,7 @@
#include
#include
#include
+#include
namespace spot
{
@@ -44,4 +45,8 @@ namespace spot
print_dot(std::ostream& os,
const const_twa_ptr& g,
const char* options = nullptr);
+
+
+ SPOT_API std::ostream &
+ print_dot(std::ostream &os, aig_ptr circuit, const char * = nullptr);
}
diff --git a/tests/python/games.ipynb b/tests/python/games.ipynb
index 1030da908..de78d9ce9 100644
--- a/tests/python/games.ipynb
+++ b/tests/python/games.ipynb
@@ -769,7 +769,7 @@
"❶ \n",
"\n",
"\n",
- "\n",
+ "\n",
"2->4 \n",
" \n",
" \n",
@@ -783,7 +783,7 @@
"5 \n",
" \n",
"\n",
- "\n",
+ "\n",
"2->5 \n",
" \n",
" \n",
@@ -833,7 +833,7 @@
"❷ \n",
" \n",
"\n",
- "\n",
+ "\n",
"5->6 \n",
" \n",
" \n",
@@ -841,7 +841,7 @@
"❶ \n",
" \n",
"\n",
- "\n",
+ "\n",
"5->7 \n",
" \n",
" \n",
@@ -865,7 +865,7 @@
"❷ \n",
" \n",
"\n",
- "\n",
+ "\n",
"8->4 \n",
" \n",
" \n",
@@ -879,7 +879,7 @@
"9 \n",
" \n",
"\n",
- "\n",
+ "\n",
"8->9 \n",
" \n",
" \n",
@@ -912,7 +912,7 @@
"\n"
],
"text/plain": [
- " *' at 0x7fa3979355d0> >"
+ " *' at 0x7fbc7c65c660> >"
]
},
"execution_count": 8,
@@ -1112,7 +1112,7 @@
"❶ \n",
" \n",
"\n",
- "\n",
+ "\n",
"2->4 \n",
" \n",
" \n",
@@ -1126,7 +1126,7 @@
"5 \n",
" \n",
"\n",
- "\n",
+ "\n",
"2->5 \n",
" \n",
" \n",
@@ -1176,7 +1176,7 @@
"❷ \n",
" \n",
"\n",
- "\n",
+ "\n",
"5->6 \n",
" \n",
" \n",
@@ -1184,7 +1184,7 @@
"❶ \n",
" \n",
"\n",
- "\n",
+ "\n",
"5->7 \n",
" \n",
" \n",
@@ -1208,7 +1208,7 @@
"❷ \n",
" \n",
"\n",
- "\n",
+ "\n",
"8->4 \n",
" \n",
" \n",
@@ -1222,7 +1222,7 @@
"9 \n",
" \n",
"\n",
- "\n",
+ "\n",
"8->9 \n",
" \n",
" \n",
@@ -1255,7 +1255,7 @@
"\n"
],
"text/plain": [
- " *' at 0x7fa37f087db0> >"
+ " *' at 0x7fbc7c250240> >"
]
},
"execution_count": 11,
@@ -1267,6 +1267,319 @@
"spot.highlight_strategy(game)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Reading an aiger-file \n",
+ "\n",
+ "Read an aiger file. Note that we do not support the full \n",
+ " syntax , but are restricted to:\n",
+ "
\n",
+ " Input variables start at index 2 and are consecutively numbered. \n",
+ " Latch variables start at index (1 + #inputs) * 2 and are consecutively numbered. \n",
+ " If inputs or outputs are named in the comments, all of them have to be named. \n",
+ " Gate number $n$ can only have latches, inputs or previously defined gates ($\n",
+ " \n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "aag 5 2 0 2 3\n",
+ "2\n",
+ "4\n",
+ "10\n",
+ "6\n",
+ "6 2 4\n",
+ "8 3 5\n",
+ "10 7 9\n",
+ "i0 a\n",
+ "i1 b\n",
+ "o0 c\n",
+ "o1 d\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "#aiger file \n",
+ "aag_txt = \"\"\"aag 5 2 0 2 3\n",
+ "2\n",
+ "4\n",
+ "10\n",
+ "6\n",
+ "6 2 4\n",
+ "8 3 5\n",
+ "10 7 9\n",
+ "i0 a\n",
+ "i1 b\n",
+ "o0 c\n",
+ "o1 d\n",
+ "\"\"\"\n",
+ "print(aag_txt)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "this_aig = spot.aiger_circuit(aag_txt)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "%3 \n",
+ " \n",
+ "\n",
+ "\n",
+ "2 \n",
+ " \n",
+ "i0 \n",
+ " \n",
+ "\n",
+ "\n",
+ "6 \n",
+ "\n",
+ "6 \n",
+ " \n",
+ "\n",
+ "\n",
+ "2->6 \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "8 \n",
+ "\n",
+ "8 \n",
+ " \n",
+ "\n",
+ "\n",
+ "2->8 \n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "4 \n",
+ " \n",
+ "i1 \n",
+ " \n",
+ "\n",
+ "\n",
+ "4->6 \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "4->8 \n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "10 \n",
+ "\n",
+ "10 \n",
+ " \n",
+ "\n",
+ "\n",
+ "6->10 \n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "o1o1 \n",
+ " \n",
+ "o1 \n",
+ " \n",
+ "\n",
+ "\n",
+ "6->o1o1:s \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "8->10 \n",
+ " \n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ "o0o0 \n",
+ " \n",
+ "o0 \n",
+ " \n",
+ "\n",
+ "\n",
+ "10->o0o0:s \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "display(this_aig.show())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "aag 5 2 0 2 3\n",
+ "2\n",
+ "4\n",
+ "10\n",
+ "6\n",
+ "6 2 4\n",
+ "8 3 5\n",
+ "10 7 9\n",
+ "i0 i0\n",
+ "i1 i1\n",
+ "o0 o0\n",
+ "o1 o1\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(this_aig.to_str())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "((2, 4), (3, 5), (7, 9))\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(this_aig.gates())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Verifying an implementation \n",
+ "\n",
+ "\n",
+ " An aiger circuit can be transformed into a monitor. If the aiger represents the implementation of a strategy in the context of active control, it can be check for correctness. This is done by computing the intersecting of the monitor and the negation of the specification.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ "t \n",
+ "[all] \n",
+ "\n",
+ "\n",
+ "\n",
+ "0 \n",
+ "\n",
+ "0 \n",
+ " \n",
+ "\n",
+ "\n",
+ "I->0 \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ "\n",
+ "0->0 \n",
+ " \n",
+ " \n",
+ "(!i0 & !i1 & !o0 & !o1) | (!i0 & i1 & o0 & !o1) | (i0 & !i1 & o0 & !o1) | (i0 & i1 & !o0 & o1) \n",
+ " \n",
+ " \n",
+ " \n"
+ ],
+ "text/plain": [
+ " *' at 0x7fbc7c1f07b0> >"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "aigasmon = this_aig.as_automaton()\n",
+ "display(aigasmon)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -1291,7 +1604,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.1rc1"
+ "version": "3.8.10"
}
},
"nbformat": 4,