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,