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

To build an automaton, 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', 'BA', 'complete'); a


Out[2]:
G 0 0 I->0 0->0 a & !b 1 1 0->1 b 4 4 0->4 !a & !b 1->1 c & d 2 2 1->2 !c & d 3 3 1->3 !d 4->4 1 2->1 c 2->2 !c 3->1 c & d 3->2 !c & d 3->3 !d

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]:
G 0 0 I->0 0->0 a & !b 1 1 0->1 b 4 4 0->4 !a & !b 1->1 c & d 2 2 1->2 !c & d 3 3 1->3 !d 4->4 1 2->1 c 2->2 !c 3->1 c & d 3->2 !c & d 3->3 !d

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(".ast")


Out[4]:
G Inf( ) cluster_0 cluster_1 cluster_2 0 0 I->0 0->0 a & !b 1 1 0->1 b 4 4 0->4 !a & !b 1->1 c & d 2 2 1->2 !c & d 3 3 1->3 !d 2->1 c 2->2 !c 3->1 c & d 3->2 !c & d 3->3 !d 4->4 1

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]:
G 1 1 I->1 1->1 a & !b 0 0 1->0 b 0->0 1

In [7]:
f.translate()


Out[7]:
G 1 1 I->1 1->1 a & !b 0 0 1->0 b 0->0 1

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


In [8]:
f.translate('mon')


Out[8]:
G 1 1 I->1 1->1 a & !b 0 0 1->0 b 0->0 1

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('ba', 'small').show('.v')


Out[10]:
G 0 0 I->0 1 1 0->1 a 2 2 0->2 b 3 3 0->3 c 1->1 a 2->2 b 3->3 c

In [11]:
f.translate('ba', 'det').show('v.')


Out[11]:
G 6 6 I->6 6->6 a & b & c 0 0 6->0 !a & !b & c 1 1 6->1 !a & b & c 2 2 6->2 !a & b & !c 3 3 6->3 a & b & !c 5 5 6->5 a & !b & !c 4 4 6->4 a & !b & c 0->0 c 1->0 !b & c 1->1 b & c 1->2 b & !c 2->2 b 3->2 !a & b 3->3 a & b 3->5 a & !b 5->5 a 4->0 !a & c 4->5 a & !c 4->4 a & c

Here is how to build an unambiguous automaton:


In [12]:
spot.translate('GFa -> GFb', 'unambig')


Out[12]:
G 0 0 I->0 1 1 0->1 1 2 2 0->2 !a & !b 3 3 0->3 a | b 4 4 0->4 !a & !b 1->1 b 1->1 !b 2->2 !a & !b 3->2 !a & !b 3->3 a | b 3->4 !a & !b 4->3 a | b 4->4 !a & !b

Compare with the standard translation:


In [13]:
spot.translate('GFa -> GFb')


Out[13]:
G 1 1 I->1 1->1 1 0 0 1->0 !a 2 2 1->2 b 0->0 !a 2->2 b 2->2 !b

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


In [14]:
spot.translate('GFa -> GFb', 'sbacc')


Out[14]:
G 0 0 I->0 0->0 1 1 1 0->1 !a 2 2 0->2 b 1->1 !a 2->2 !b 3 3 2->3 b 3->2 !b 3->3 b

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


In [15]:
a = spot.translate('F(a & X(!a &Xb))', "any"); a


Out[15]:
G 0 0 I->0 0->0 1 1 1 0->1 a 2 2 1->2 !a 3 3 2->3 b 3->3 1

In [16]:
spot.sl(a)


Out[16]:
G 0 0 I->0 1 1 0->1 a & b 2 2 0->2 !a & b 3 3 0->3 a & !b 4 4 0->4 !a & !b 5 5 0->5 a & b 6 6 0->6 a & !b 1->1 a & b 1->2 !a & b 1->3 a & !b 1->4 !a & !b 1->5 a & b 1->6 a & !b 2->1 a & b 2->2 !a & b 2->3 a & !b 2->4 !a & !b 2->5 a & b 2->6 a & !b 3->1 a & b 3->2 !a & b 3->3 a & !b 3->4 !a & !b 3->5 a & b 3->6 a & !b 4->1 a & b 4->2 !a & b 4->3 a & !b 4->4 !a & !b 4->5 a & b 4->6 a & !b 5->5 a & b 7 7 5->7 !a & b 8 8 5->8 !a & !b 6->6 a & !b 6->7 !a & b 6->8 !a & !b 7->7 !a & b 9 9 7->9 a & b 10 10 7->10 !a & b 8->8 !a & !b 8->9 a & b 8->10 !a & b 9->9 a & b 9->10 !a & b 11 11 9->11 a & !b 12 12 9->12 !a & !b 10->9 a & b 10->10 !a & b 10->11 a & !b 10->12 !a & !b 11->9 a & b 11->10 !a & b 11->11 a & !b 11->12 !a & !b 12->9 a & b 12->10 !a & b 12->11 a & !b 12->12 !a & !b

In [17]:
a.is_empty()


Out[17]:
False

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


In [18]:
%%file example1.aut
HOA: v1
States: 3
Start: 0
AP: 2 "a" "b"
acc-name: Buchi
Acceptance: 4 Inf(0)&Fin(1)&Fin(3) | Inf(2)&Inf(3) | Inf(1)
--BODY--
State: 0 {3}
[t] 0
[0] 1 {1}
[!0] 2 {0}
State: 1 {3}
[1] 0
[0&1] 1 {0}
[!0&1] 2 {2}
State: 2
[!1] 0
[0&!1] 1 {0}
[!0&!1] 2 {0}
--END--


Writing example1.aut

In [19]:
a = spot.automaton('example1.aut')
display(a.show('.a'))
display(spot.remove_fin(a).show('.a'))
display(a.postprocess('TGBA', 'complete').show('.a'))
display(a.postprocess('BA'))


G (Fin( ) & Fin( ) & Inf( )) | (Inf( )&Inf( )) | Inf( ) 0 0 I->0 0->0 1 1 1 0->1 a 2 2 0->2 !a 1->0 b 1->1 a & b 1->2 !a & b 2->0 !b 2->1 a & !b 2->2 !a & !b
G Inf( ) | Inf( ) | (Inf( )&Inf( )) 0 0 I->0 0->0 1 1 1 0->1 a 2 2 0->2 !a 1->0 b 1->1 a & b 1->2 !a & b 2->0 !b 2->1 a & !b 2->2 !a & !b 3 3 2->3 !a & !b 3->3 !a & !b
G Inf( ) 1 1 I->1 1->1 1 2 2 1->2 a 3 3 1->3 !a 0 0 0->0 !a & !b 4 4 0->4 a | b 4->4 1 2->1 b 2->4 !b 2->2 a & b 2->3 !a & b 3->1 !b 3->0 !a & !b 3->4 b 3->2 a & !b 3->3 !a & !b
G 2 2 I->2 2->2 1 0 0 2->0 a 3 3 2->3 !a 0->2 b 4 4 0->4 a & b 5 5 0->5 !a & b 4->4 a & b 4->5 !a & b 5->2 !b 5->4 a & !b 1 1 5->1 !a & !b 5->3 !a & !b 1->1 !a & !b 3->4 a & !b 3->1 !a & !b

In [20]:
!rm example1.aut

In [21]:
spot.complete(a)


Out[21]:
G 0 0 I->0 0->0 1 1 1 0->1 a 2 2 0->2 !a 1->0 b 1->1 a & b 1->2 !a & b 3 3 1->3 !b 2->0 !b 2->1 a & !b 2->2 !a & !b 2->3 b 3->3 1

In [22]:
spot.complete(spot.translate('Ga'))


Out[22]:
G 0 0 I->0 0->0 a 1 1 0->1 !a 1->1 1

In [23]:
# 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('a W c'); display(a1.show('.bat'))
a2 = spot.translate('a U b'); display(a2.show('.bat+1'))
# the product should display pairs of states, unless asked not to (using 1).
p = spot.product(a1, a2); display(p.show('.bat')); display(p.show('.bat1'))


G Inf( ) 1 1 I->1 1->1 a & !c 0 0 1->0 c 0->0 1
G Inf( ) 1 1 I->1 1->1 a & !b 0 0 1->0 b 0->0 1
G Inf( )&Inf( ) 0 1,1 I->0 0->0 a & !b & !c 1 0,0 0->1 b & c 2 0,1 0->2 a & !b & c 3 1,0 0->3 a & b & !c 1->1 1 2->1 b 2->2 a & !b 3->1 c 3->3 a & !c
G Inf( )&Inf( ) 0 0 I->0 0->0 a & !b & !c 1 1 0->1 b & c 2 2 0->2 a & !b & c 3 3 0->3 a & b & !c 1->1 1 2->1 b 2->2 a & !b 3->1 c 3->3 a & !c

Explicit determinization after translation:


In [24]:
a = spot.translate('FGa')
display(a)
display(a.is_deterministic())


G 1 1 I->1 1->1 1 0 0 1->0 a 0->0 a
False

In [25]:
spot.tgba_determinize(a).show('.ba')


Out[25]:
G Fin( ) & Inf( ) 0 0 I->0 0->0 !a 1 1 0->1 a 1->0 !a 1->1 a

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


In [26]:
aut = spot.translate('FGa', 'generic', 'deterministic'); aut


Out[26]:
G 0 0 I->0 0->0 !a 1 1 0->1 a 1->0 !a 1->1 a

Adding an automatic proposition to all edges


In [27]:
import buddy
b = buddy.bdd_ithvar(aut.register_ap('b'))
for e in aut.edges():
    e.cond &= b
aut


Out[27]:
G 0 0 I->0 0->0 !a & b 1 1 0->1 a & b 1->0 !a & b 1->1 a & b

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


In [28]:
c = buddy.bdd_ithvar(aut.register_ap('c'))
for e in aut.out(0):
    if e.dst == 1:
        e.cond &= c
aut


Out[28]:
G 0 0 I->0 0->0 !a & b 1 1 0->1 a & b & c 1->0 !a & b 1->1 a & b