In [1]:
from IPython.display import display
import spot
from spot.jupyter import display_inline
spot.setup()

To build an automaton from an LTL formula, simply call translate() with a formula, and a list of options to characterize the automaton you want (those options have the same name as the long options name of the ltl2tgba tool, and they can be abbreviated).

In [2]:
a = spot.translate('(a U b) & GFc & GFd', 'Buchi', 'state-based', 'complete'); a
Out[2]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\na & !b\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\nb & c & d\n\n\n\n2\n\n2\n\n\n\n0->2\n\n\nb & !d\n\n\n\n3\n\n3\n\n\n\n0->3\n\n\nb & !c & d\n\n\n\n4\n\n4\n\n\n\n0->4\n\n\n!a & !b\n\n\n\n1->1\n\n\nc & d\n\n\n\n1->2\n\n\n!d\n\n\n\n1->3\n\n\n!c & d\n\n\n\n2->1\n\n\nc & d\n\n\n\n2->2\n\n\n!d\n\n\n\n2->3\n\n\n!c & d\n\n\n\n3->1\n\n\nc\n\n\n\n3->3\n\n\n!c\n\n\n\n4->4\n\n\n1\n\n\n\n'

The call the spot.setup() in the first cells has installed a default style for the graphviz output. If you want to change this style temporarily, you can call the show(style) method explicitely. For instance here is a vertical layout with the default font of GraphViz.

In [3]:
a.show("v")
Out[3]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\na & !b\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\nb & c & d\n\n\n\n2\n\n2\n\n\n\n0->2\n\n\nb & !d\n\n\n\n3\n\n3\n\n\n\n0->3\n\n\nb & !c & d\n\n\n\n4\n\n4\n\n\n\n0->4\n\n\n!a & !b\n\n\n\n1->1\n\n\nc & d\n\n\n\n1->2\n\n\n!d\n\n\n\n1->3\n\n\n!c & d\n\n\n\n2->1\n\n\nc & d\n\n\n\n2->2\n\n\n!d\n\n\n\n2->3\n\n\n!c & d\n\n\n\n3->1\n\n\nc\n\n\n\n3->3\n\n\n!c\n\n\n\n4->4\n\n\n1\n\n\n\n'

If you want to add some style options to the existing one, pass a dot to the show() function in addition to your own style options:

In [4]:
a.show(".st")
Out[4]:
b'\n\n\nInf(\n\xe2\x93\xbf\n)\n[B\xc3\xbcchi]\n\ncluster_0\n\n\n\ncluster_1\n\n\n\ncluster_2\n\n\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\na & !b\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\nb & c & d\n\n\n\n2\n\n2\n\n\n\n0->2\n\n\nb & !d\n\n\n\n3\n\n3\n\n\n\n0->3\n\n\nb & !c & d\n\n\n\n4\n\n4\n\n\n\n0->4\n\n\n!a & !b\n\n\n\n1->1\n\n\nc & d\n\xe2\x93\xbf\n\n\n\n1->2\n\n\n!d\n\xe2\x93\xbf\n\n\n\n1->3\n\n\n!c & d\n\xe2\x93\xbf\n\n\n\n2->1\n\n\nc & d\n\n\n\n2->2\n\n\n!d\n\n\n\n2->3\n\n\n!c & d\n\n\n\n3->1\n\n\nc\n\n\n\n3->3\n\n\n!c\n\n\n\n4->4\n\n\n1\n\n\n\n'

The translate() function can also be called with a formula object. Either as a function, or as a method.

In [5]:
f = spot.formula('a U b'); f
Out[5]:
$a \mathbin{\mathsf{U}} b$
In [6]:
spot.translate(f)
Out[6]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n1\n\n1\n\n\n\nI->1\n\n\n\n\n\n1->1\n\n\na & !b\n\n\n\n0\n\n\n0\n\n\n\n1->0\n\n\nb\n\n\n\n0->0\n\n\n1\n\n\n\n'
In [7]:
f.translate()
Out[7]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n1\n\n1\n\n\n\nI->1\n\n\n\n\n\n1->1\n\n\na & !b\n\n\n\n0\n\n\n0\n\n\n\n1->0\n\n\nb\n\n\n\n0->0\n\n\n1\n\n\n\n'

When used as a method, all the arguments are translation options. Here is a monitor:

In [8]:
f.translate('mon')
Out[8]:
b'\n\n\nt\n[all]\n\n\n\n1\n\n1\n\n\n\nI->1\n\n\n\n\n\n1->1\n\n\na & !b\n\n\n\n0\n\n0\n\n\n\n1->0\n\n\nb\n\n\n\n0->0\n\n\n1\n\n\n\n'

The following three cells show a formulas for which it makes a difference to select 'small' or 'deterministic'.

In [9]:
f = spot.formula('Ga | Gb | Gc'); f
Out[9]:
$\mathsf{G} a \lor \mathsf{G} b \lor \mathsf{G} c$
In [10]:
f.translate('buchi', 'state-based', 'small').show('.v')
Out[10]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\na\n\n\n\n2\n\n\n2\n\n\n\n0->2\n\n\nb\n\n\n\n3\n\n\n3\n\n\n\n0->3\n\n\nc\n\n\n\n1->1\n\n\na\n\n\n\n2->2\n\n\nb\n\n\n\n3->3\n\n\nc\n\n\n\n'
In [11]:
f.translate('buchi', 'state-based', 'det').show('v.')
Out[11]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n6\n\n\n6\n\n\n\nI->6\n\n\n\n\n\n6->6\n\n\na & b & c\n\n\n\n0\n\n\n0\n\n\n\n6->0\n\n\n!a & !b & c\n\n\n\n1\n\n\n1\n\n\n\n6->1\n\n\n!a & b & !c\n\n\n\n2\n\n\n2\n\n\n\n6->2\n\n\na & !b & !c\n\n\n\n3\n\n\n3\n\n\n\n6->3\n\n\n!a & b & c\n\n\n\n4\n\n\n4\n\n\n\n6->4\n\n\na & !b & c\n\n\n\n5\n\n\n5\n\n\n\n6->5\n\n\na & b & !c\n\n\n\n0->0\n\n\nc\n\n\n\n1->1\n\n\nb\n\n\n\n2->2\n\n\na\n\n\n\n3->0\n\n\n!b & c\n\n\n\n3->1\n\n\nb & !c\n\n\n\n3->3\n\n\nb & c\n\n\n\n4->0\n\n\n!a & c\n\n\n\n4->2\n\n\na & !c\n\n\n\n4->4\n\n\na & c\n\n\n\n5->1\n\n\n!a & b\n\n\n\n5->2\n\n\na & !b\n\n\n\n5->5\n\n\na & b\n\n\n\n'

Here is how to build an unambiguous automaton:

In [12]:
spot.translate('GFa -> GFb', 'unambig')
Out[12]:
b'\n\n\nInf(\n\xe2\x93\xbf\n)\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\n1\n\n\n\n2\n\n2\n\n\n\n0->2\n\n\n!a & !b\n\n\n\n3\n\n3\n\n\n\n0->3\n\n\n!a & !b\n\n\n\n4\n\n4\n\n\n\n0->4\n\n\na | b\n\n\n\n1->1\n\n\n!b\n\n\n\n1->1\n\n\nb\n\xe2\x93\xbf\n\n\n\n2->2\n\n\n!a & !b\n\n\n\n2->4\n\n\na | b\n\n\n\n3->3\n\n\n!a & !b\n\xe2\x93\xbf\n\n\n\n4->2\n\n\n!a & !b\n\n\n\n4->3\n\n\n!a & !b\n\n\n\n4->4\n\n\na | b\n\n\n\n'

Compare with the standard translation:

In [13]:
spot.translate('GFa -> GFb')
Out[13]:
b'\n\n\nInf(\n\xe2\x93\xbf\n)\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\nb\n\n\n\n2\n\n2\n\n\n\n0->2\n\n\n!a\n\n\n\n1->1\n\n\n!b\n\n\n\n1->1\n\n\nb\n\xe2\x93\xbf\n\n\n\n2->2\n\n\n!a\n\xe2\x93\xbf\n\n\n\n'

And here is the automaton above with state-based acceptance:

In [14]:
a = spot.translate('GFa -> GFb', 'sbacc')
a
Out[14]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\nb\n\n\n\n2\n\n\n2\n\n\n\n0->2\n\n\n!a\n\n\n\n1->1\n\n\nb\n\n\n\n3\n\n3\n\n\n\n1->3\n\n\n!b\n\n\n\n2->2\n\n\n!a\n\n\n\n3->1\n\n\nb\n\n\n\n3->3\n\n\n!b\n\n\n\n'

Some example of running the self-loopization algorithm on an automaton:

In [15]:
a.is_empty()
Out[15]:
False

Reading from file (see automaton-io.ipynb for more examples).

In [16]:
%%file example1.aut
HOA: v1
States: 3
Start: 0
AP: 2 "a" "b"
acc-name: Buchi
Acceptance: 5 Inf(0)&Fin(1)&Fin(4) | Inf(2)&Inf(3) | Inf(1)
--BODY--
State: 0 {3}
[t] 0
[0] 1 {1}
[!0] 2 {0 4}
State: 1 {3}
[1] 0
[0&1] 1 {0}
[!0&1] 2 {2 4}
State: 2 
[!1] 0
[0&!1] 1 {0}
[!0&!1] 2 {0 4}
--END--
Writing example1.aut
In [17]:
a = spot.automaton('example1.aut')
display(a)
display(spot.remove_fin(a))
display(a.postprocess('GeneralizedBuchi', 'complete'))
display(a.postprocess('Buchi', "SBAcc"))
b'\n\n\n(Inf(\n\xe2\x93\xbf\n) & Fin(\n\xe2\x9d\xb6\n) & Fin(\n\xe2\x9d\xb9\n)) | (Inf(\n\xe2\x9d\xb7\n)&Inf(\n\xe2\x9d\xb8\n)) | Inf(\n\xe2\x9d\xb6\n)\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\xe2\x9d\xb8\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\na\n\xe2\x9d\xb6\n\xe2\x9d\xb8\n\n\n\n2\n\n2\n\n\n\n0->2\n\n\n!a\n\xe2\x93\xbf\n\xe2\x9d\xb8\n\xe2\x9d\xb9\n\n\n\n1->0\n\n\nb\n\xe2\x9d\xb8\n\n\n\n1->1\n\n\na & b\n\xe2\x93\xbf\n\xe2\x9d\xb8\n\n\n\n1->2\n\n\n!a & b\n\xe2\x9d\xb7\n\xe2\x9d\xb8\n\xe2\x9d\xb9\n\n\n\n2->0\n\n\n!b\n\n\n\n2->1\n\n\na & !b\n\xe2\x93\xbf\n\n\n\n2->2\n\n\n!a & !b\n\xe2\x93\xbf\n\xe2\x9d\xb9\n\n\n\n'
b'\n\n\nInf(\n\xe2\x93\xbf\n)\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\na\n\xe2\x93\xbf\n\n\n\n2\n\n2\n\n\n\n0->2\n\n\n!a\n\n\n\n3\n\n3\n\n\n\n0->3\n\n\n1\n\n\n\n1->0\n\n\nb\n\n\n\n1->1\n\n\na & b\n\n\n\n1->2\n\n\n!a & b\n\xe2\x93\xbf\n\n\n\n4\n\n4\n\n\n\n1->4\n\n\na & b\n\n\n\n2->0\n\n\n!b\n\n\n\n2->1\n\n\na & !b\n\n\n\n2->2\n\n\n!a & !b\n\n\n\n3->3\n\n\n1\n\n\n\n4->4\n\n\na & b\n\xe2\x93\xbf\n\n\n\n'
b'\n\n\nInf(\n\xe2\x93\xbf\n)\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\na\n\xe2\x93\xbf\n\n\n\n1->0\n\n\nb\n\n\n\n1->1\n\n\na & b\n\n\n\n2\n\n2\n\n\n\n1->2\n\n\n!a & b\n\xe2\x93\xbf\n\n\n\n3\n\n3\n\n\n\n1->3\n\n\na & b\n\n\n\n4\n\n4\n\n\n\n1->4\n\n\n!b\n\n\n\n2->0\n\n\n!b\n\n\n\n2->1\n\n\na & !b\n\n\n\n2->4\n\n\nb\n\n\n\n3->3\n\n\na & b\n\xe2\x93\xbf\n\n\n\n3->4\n\n\n!a | !b\n\n\n\n4->4\n\n\n1\n\n\n\n'
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\na\n\n\n\n1->0\n\n\nb\n\n\n\n2\n\n2\n\n\n\n1->2\n\n\na & b\n\n\n\n3\n\n\n3\n\n\n\n1->3\n\n\n!a & b\n\n\n\n4\n\n\n4\n\n\n\n1->4\n\n\na & b\n\n\n\n2->2\n\n\na & b\n\n\n\n2->3\n\n\n!a & b\n\n\n\n2->4\n\n\na & b\n\n\n\n3->0\n\n\n!b\n\n\n\n3->2\n\n\na & !b\n\n\n\n4->4\n\n\na & b\n\n\n\n'
In [18]:
!rm example1.aut
In [19]:
spot.complete(a)
Out[19]:
b'\n\n\n(Inf(\n\xe2\x93\xbf\n) & Fin(\n\xe2\x9d\xb6\n) & Fin(\n\xe2\x9d\xb9\n)) | (Inf(\n\xe2\x9d\xb7\n)&Inf(\n\xe2\x9d\xb8\n)) | Inf(\n\xe2\x9d\xb6\n)\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\xe2\x9d\xb8\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\na\n\xe2\x9d\xb6\n\xe2\x9d\xb8\n\n\n\n2\n\n2\n\n\n\n0->2\n\n\n!a\n\xe2\x93\xbf\n\xe2\x9d\xb8\n\xe2\x9d\xb9\n\n\n\n1->0\n\n\nb\n\xe2\x9d\xb8\n\n\n\n1->1\n\n\na & b\n\xe2\x93\xbf\n\xe2\x9d\xb8\n\n\n\n1->2\n\n\n!a & b\n\xe2\x9d\xb7\n\xe2\x9d\xb8\n\xe2\x9d\xb9\n\n\n\n3\n\n3\n\n\n\n1->3\n\n\n!b\n\n\n\n2->0\n\n\n!b\n\n\n\n2->1\n\n\na & !b\n\xe2\x93\xbf\n\n\n\n2->2\n\n\n!a & !b\n\xe2\x93\xbf\n\xe2\x9d\xb9\n\n\n\n2->3\n\n\nb\n\n\n\n3->3\n\n\n1\n\n\n\n'
In [20]:
spot.complete(spot.translate('Ga'))
Out[20]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\na\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\n!a\n\n\n\n1->1\n\n\n1\n\n\n\n'
In [21]:
spot.formula('(a W c) & FGa').is_syntactic_persistence()
Out[21]:
True
In [22]:
# Using +1 in the display options is a convient way to shift the 
# set numbers in the output, as an aid in reading the product.
a1 = spot.translate('GF(a <-> Xa)')
print(a1.prop_weak())
a2 = spot.translate('a U b & GFc')
display_inline(a1.show('.t'), a2.show('.t+1'))
# the product should display pairs of states, unless asked not to (using '1').
p = spot.product(a1, a2)
display_inline(p.show('.t'), p.show('.t1'), per_row=2)
no
Inf( ⓿ ) [Büchi] 0 0 I->0 0->0 a ⓿ 1 1 0->1 !a 1->0 a 1->1 !a ⓿
Inf( ❶ ) [Büchi] 0 0 I->0 0->0 a & !b 1 1 0->1 b 1->1 !c 1->1 c ❶
Inf( ⓿ )&Inf( ❶ ) [gen. Büchi 2] 0 0,0 I->0 0->0 a & !b ⓿ 1 0,1 0->1 a & b ⓿ 2 1,1 0->2 !a & b 1->1 a & !c ⓿ 1->1 a & c ⓿ ❶ 1->2 !a & !c 1->2 !a & c ❶ 2->1 a & !c 2->1 a & c ❶ 2->2 !a & !c ⓿ 2->2 !a & c ⓿ ❶
Inf( ⓿ )&Inf( ❶ ) [gen. Büchi 2] 0 0 I->0 0->0 a & !b ⓿ 1 1 0->1 a & b ⓿ 2 2 0->2 !a & b 1->1 a & !c ⓿ 1->1 a & c ⓿ ❶ 1->2 !a & !c 1->2 !a & c ❶ 2->1 a & !c 2->1 a & c ❶ 2->2 !a & !c ⓿ 2->2 !a & c ⓿ ❶

Explicit determinization after translation:

In [23]:
a = spot.translate('FGa')
display(a)
display(a.is_deterministic())
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\na\n\n\n\n1->1\n\n\na\n\n\n\n'
False
In [24]:
spot.tgba_determinize(a)
Out[24]:
b'\n\n\nFin(\n\xe2\x93\xbf\n) & Inf(\n\xe2\x9d\xb6\n)\n[Rabin 1]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n!a\n\n\n\n1\n\n1\n\n\n\n0->1\n\n\na\n\xe2\x9d\xb6\n\n\n\n1->0\n\n\n!a\n\xe2\x93\xbf\n\n\n\n1->1\n\n\na\n\xe2\x9d\xb6\n\n\n\n'

Determinization by translate(). The generic option allows any acceptance condition to be used instead of the default generalized Büchi.

In [25]:
spot.translate('FGa', 'generic', 'deterministic')
Out[25]:
b'\n\n\nFin(\n\xe2\x93\xbf\n)\n[co-B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n!a\n\xe2\x93\xbf\n\n\n\n0->0\n\n\na\n\n\n\n'

Translation to state-based co-Büchi automaton

In [26]:
spot.translate('FGa', 'coBuchi', 'deterministic', 'sbacc').show('.b')
Out[26]:
b'\n\n\nFin(\n\xe2\x93\xbf\n)\n[co-B\xc3\xbcchi]\n\n\n\n1\n\n1\n\xe2\x93\xbf\n\n\n\nI->1\n\n\n\n\n\n1->1\n\n\n!a\n\n\n\n0\n\n0\n\n\n\n1->0\n\n\na\n\n\n\n0->1\n\n\n!a\n\n\n\n0->0\n\n\na\n\n\n\n'

Translation to parity automaton. Specifying just parity max odd requires a parity acceptance. Adding colored ensures that each transition (or state if sbacc is also given) has a color, as people usually expect in parity automata.

In [27]:
spot.translate('FGa', 'parity max odd', 'colored')
Out[27]:
b'\n\n\nFin(\n\xe2\x9d\xb7\n) & (Inf(\n\xe2\x9d\xb6\n) | Fin(\n\xe2\x93\xbf\n))\n[parity max odd 3]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\na\n\xe2\x9d\xb6\n\n\n\n0->0\n\n\n!a\n\xe2\x9d\xb7\n\n\n\n'

Adding an atomic proposition to all edges

In [28]:
import buddy
display(a)
b = buddy.bdd_ithvar(a.register_ap('b'))
for e in a.edges():
    e.cond &= b
display(a)
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\n1\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\na\n\n\n\n1->1\n\n\na\n\n\n\n'
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\nb\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\na & b\n\n\n\n1->1\n\n\na & b\n\n\n\n'

Adding an atomic proposition to the edge between 0 and 1:

In [29]:
c = buddy.bdd_ithvar(a.register_ap('c'))
for e in a.out(0):
    if e.dst == 1:
        e.cond &= c
a
Out[29]:
b'\n\n\n[B\xc3\xbcchi]\n\n\n\n0\n\n0\n\n\n\nI->0\n\n\n\n\n\n0->0\n\n\nb\n\n\n\n1\n\n\n1\n\n\n\n0->1\n\n\na & b & c\n\n\n\n1->1\n\n\na & b\n\n\n\n'