In [1]:
import spot
import spot.ltsmin
spot.setup()

Part 3 – Python bindings with visualizations¶

Simple objects: LTL, automata¶

In [2]:
f = spot.formula("F(a & X(!a & Gb))")
f
Out[2]:
$\mathsf{F} (a \land \mathsf{X} (\lnot a \land \mathsf{G} b))$
In [3]:
aut = spot.translate(f)
aut
Out[3]:
[Büchi] 0 0 I->0 0->0 1 1 1 0->1 a 2 2 1->2 !a & b 2->2 b
In [4]:
spot.is_stutter_invariant(f)
Out[4]:
True
In [5]:
a = spot.translate("a & !b & X!b & G(a <-> X!a) & G(b <-> XXX!b) & GFc")
a
Out[5]:
Inf( ⓿ ) [Büchi] 0 0 I->0 1 1 0->1 a & !b 2 2 1->2 !a & !b 3 3 2->3 a & !b 4 4 2->4 a & b 5 5 3->5 !a & b & !c 3->5 !a & b & c ⓿ 6 6 4->6 !a & b & !c 4->6 !a & b & c ⓿ 7 7 5->7 a & b & !c 5->7 a & b & c ⓿ 8 8 6->8 a & b & !c 6->8 a & b & c ⓿ 9 9 7->9 !a & b & !c 7->9 !a & b & c ⓿ 10 10 8->10 !a & !b & !c 8->10 !a & !b & c ⓿ 11 11 9->11 a & !b & !c 9->11 a & !b & c ⓿ 12 12 10->12 a & !b & !c 10->12 a & !b & c ⓿ 13 13 11->13 !a & !b & !c 11->13 !a & !b & c ⓿ 14 14 12->14 !a & !b & !c 12->14 !a & !b & c ⓿ 13->3 a & !b & !c 13->3 a & !b & c ⓿ 14->4 a & b & !c 14->4 a & b & c ⓿
In [6]:
r = a.accepting_run()
r.highlight(5)
a
Out[6]:
Inf( ⓿ ) [Büchi] 0 0 I->0 1 1 0->1 a & !b 2 2 1->2 !a & !b 3 3 2->3 a & !b 4 4 2->4 a & b 5 5 3->5 !a & b & !c 3->5 !a & b & c ⓿ 6 6 4->6 !a & b & !c 4->6 !a & b & c ⓿ 7 7 5->7 a & b & !c 5->7 a & b & c ⓿ 8 8 6->8 a & b & !c 6->8 a & b & c ⓿ 9 9 7->9 !a & b & !c 7->9 !a & b & c ⓿ 10 10 8->10 !a & !b & !c 8->10 !a & !b & c ⓿ 11 11 9->11 a & !b & !c 9->11 a & !b & c ⓿ 12 12 10->12 a & !b & !c 10->12 a & !b & c ⓿ 13 13 11->13 !a & !b & !c 11->13 !a & !b & c ⓿ 14 14 12->14 !a & !b & !c 12->14 !a & !b & c ⓿ 13->3 a & !b & !c 13->3 a & !b & c ⓿ 14->4 a & b & !c 14->4 a & b & c ⓿
In [7]:
w = spot.twa_word(r)
w
Out[7]:
$a \land \lnot b; \lnot a \land \lnot b; a \land \lnot b; \mathsf{cycle}\{\lnot a \land b \land c; a \land b \land \lnot c; \lnot a \land b \land \lnot c; a \land \lnot b \land \lnot c; \lnot a \land \lnot b \land \lnot c; a \land \lnot b \land \lnot c\}$
In [8]:
w.show()
Out[8]:
abcprefixcycle cycle
In [9]:
a = spot.automaton("colorful2.hoa")
a
Out[9]:
(Fin( ⓿ ) & Fin( ❶ )) | ((Fin( ❹ )|Fin( ❺ )) & (Inf( ❷ )&Inf( ❸ ))) 0 0 I->0 0->0 !p0 & !p1 ⓿ ❶ ❷ ❸ ❺ 1 1 0->1 p0 & !p1 2 2 0->2 p0 & p1 3 3 0->3 !p0 & p1 ❶ ❷ ❸ 1->1 !p0 & !p1 ❶ ❷ ❸ ❺ 1->1 p0 & !p1 ❷ ❹ ❺ 4 4 1->4 !p0 & p1 ❶ ❸ 1->4 p0 & p1 ❹ 2->1 p0 & !p1 ❷ ❹ ❺ 2->2 !p0 & p1 ❶ ❸ 2->4 p0 & p1 ❹ 6 6 2->6 !p0 & !p1 ❶ ❸ ❺ 3->0 !p0 & !p1 ⓿ ❶ ❸ ❺ 3->1 p0 & !p1 3->3 !p0 & p1 ❶ ❸ 3->4 p0 & p1 4->1 p0 & !p1 ❷ ❹ ❺ 4->2 p0 & p1 ❷ ❹ 4->4 !p0 & p1 ❶ ❸ 5 5 4->5 !p0 & !p1 ❶ ❷ ❸ ❺ 6->1 p0 & !p1 ❷ ❹ ❺ 6->2 !p0 & p1 ❶ ❷ ❸ 6->2 p0 & p1 ❷ ❹ 6->6 !p0 & !p1 ❶ ❷ ❸ ❺ 5->1 p0 & !p1 ❷ ❹ ❺ 5->4 !p0 & p1 ❶ ❸ 5->4 p0 & p1 ❹ 5->6 !p0 & !p1 ❶ ❸ ❺
In [10]:
r = a.accepting_run()
r.highlight(5)
a
Out[10]:
(Fin( ⓿ ) & Fin( ❶ )) | ((Fin( ❹ )|Fin( ❺ )) & (Inf( ❷ )&Inf( ❸ ))) 0 0 I->0 0->0 !p0 & !p1 ⓿ ❶ ❷ ❸ ❺ 1 1 0->1 p0 & !p1 2 2 0->2 p0 & p1 3 3 0->3 !p0 & p1 ❶ ❷ ❸ 1->1 !p0 & !p1 ❶ ❷ ❸ ❺ 1->1 p0 & !p1 ❷ ❹ ❺ 4 4 1->4 !p0 & p1 ❶ ❸ 1->4 p0 & p1 ❹ 2->1 p0 & !p1 ❷ ❹ ❺ 2->2 !p0 & p1 ❶ ❸ 2->4 p0 & p1 ❹ 6 6 2->6 !p0 & !p1 ❶ ❸ ❺ 3->0 !p0 & !p1 ⓿ ❶ ❸ ❺ 3->1 p0 & !p1 3->3 !p0 & p1 ❶ ❸ 3->4 p0 & p1 4->1 p0 & !p1 ❷ ❹ ❺ 4->2 p0 & p1 ❷ ❹ 4->4 !p0 & p1 ❶ ❸ 5 5 4->5 !p0 & !p1 ❶ ❷ ❸ ❺ 6->1 p0 & !p1 ❷ ❹ ❺ 6->2 !p0 & p1 ❶ ❷ ❸ 6->2 p0 & p1 ❷ ❹ 6->6 !p0 & !p1 ❶ ❷ ❸ ❺ 5->1 p0 & !p1 ❷ ❹ ❺ 5->4 !p0 & p1 ❶ ❸ 5->4 p0 & p1 ❹ 5->6 !p0 & !p1 ❶ ❸ ❺

Model Checking with the LTSmin interface¶

In [11]:
%%pml model
active proctype P() {
int a = 0;
int b = 0;
x: if
  :: a < 3 && b < 3  ->  a = a + 1; goto x;
  :: a < 3 && b < 3  ->  b = b + 1; goto x;
fi
}
SpinS Promela Compiler - version 1.1 (3-Feb-2015)
(C) University of Twente, Formal Methods and Tools group

Parsing tmp51y3jmi6.pml...
Parsing tmp51y3jmi6.pml done (0.0 sec)

Optimizing graphs...
   StateMerging changed 2 states/transitions.
   RemoveUselessActions changed 2 states/transitions.
   RemoveUselessGotos changed 2 states/transitions.
   RenumberAll changed 1 states/transitions.
Optimization done (0.0 sec)

Generating next-state function ...
   Instantiating processes
   Statically binding references
   Creating transitions
Generating next-state function done (0.0 sec)

Creating state vector
Creating state labels
Generating transitions/state dependency matrices (2 / 3 slots) ... 
   Found          5 /         15 ( 33.3%) Guard/slot reads               
   Found          6 /          6 (100.0%) Transition/slot tests               
   Found         2,         4,         4 /        18 ( 11.1%,  22.2%,  22.2%) Actions/slot r,W,w               
   Found         2,         4,         4 /         6 ( 33.3%,  66.7%,  66.7%) Atomics/slot r,W,w               
   Found         6,         4,         4 /         6 (100.0%,  66.7%,  66.7%) Transition/slot r,W,w               
Generating transition/state dependency matrices done (0.0 sec)

Generating guard dependency matrices (5 guards) ...
   Found          3 /         12 ( 25.0%) Guard/guard dependencies               
   Found          8 /         10 ( 80.0%) Transition/guard writes               

   Found          4 /          4 (100.0%) Transition/transition writes               
   Found          2 /         12 ( 16.7%) !MCE guards               
   Found          1 /          2 ( 50.0%) !MCE transitions               
   Found          7 /         25 ( 28.0%) !ICE guards               
   Found         10 /         10 (100.0%) !NES guards               
   Found          4 /          4 (100.0%) !NES transitions               
   Found          8 /         10 ( 80.0%) !NDS guards               
   Found          0 /         10 (  0.0%) MDS guards               
   Found          4 /         10 ( 40.0%) MES guards               
   Found          0 /          4 (  0.0%) !NDS transitions               
   Found          0 /          2 (  0.0%) !DNA transitions               
   Found          2 /          2 (100.0%) Commuting actions               
Generating guard dependency matrices done (0.0 sec)

Written C code to /home/adl/git/spot-grow/jupyter/tmp51y3jmi6.pml.spins.c
Compiled C code to PINS library tmp51y3jmi6.pml.spins

In [12]:
model
Out[12]:
ltsmin model with the following variables:
  P_0._pc: pc
  P_0.a: int
  P_0.b: int
In [13]:
k = model.kripke(['P_0.a < 2', 'P_0.b > 1']); k.show(".<5")
Out[13]:
t [all] 0 P_0._pc=0, P_0.a=0, P_0.b=0 "P_0.a < 2" & !"P_0.b > 1" & !dead I->0 1 P_0._pc=0, P_0.a=1, P_0.b=0 "P_0.a < 2" & !"P_0.b > 1" & !dead 0->1 2 P_0._pc=0, P_0.a=0, P_0.b=1 "P_0.a < 2" & !"P_0.b > 1" & !dead 0->2 3 P_0._pc=0, P_0.a=2, P_0.b=0 ... 1->3 4 P_0._pc=0, P_0.a=1, P_0.b=1 ... 1->4 2->4 u2 ... 2->u2 u3 ... 3->u3 u4 ... 4->u4
In [14]:
k.show('.1K<5')
Out[14]:
t [all] 0 0 I->0 1 1 0->1 "P_0.a < 2" & !"P_0.b > 1" & !dead 2 2 0->2 "P_0.a < 2" & !"P_0.b > 1" & !dead 3 3 1->3 "P_0.a < 2" & !"P_0.b > 1" & !dead 4 4 1->4 "P_0.a < 2" & !"P_0.b > 1" & !dead 2->4 "P_0.a < 2" & !"P_0.b > 1" & !dead u2 ... 2->u2 u3 ... 3->u3 u4 ... 4->u4
In [15]:
def model_check(f, m):
    nf = spot.formula.Not(f)
    ss = m.kripke(spot.atomic_prop_collect(nf))
    return spot.otf_product(ss, nf.translate()).is_empty() #.accepting_run().project(ss).as_twa(True)
In [16]:
model_check('F("P_0.b > 2")', model)
Out[16]:
False

SAT-based Minimization¶

In [17]:
def ltl2dstar(f):
    f = spot.formula(f)
    return spot.automaton(f"echo {f:lq} | ltl2dstar --ltl2nba=spin:ltl2tgba@-Ds - - |")
In [18]:
a = ltl2dstar("(FGa | Fb) & FGc")
a
Out[18]:
(Fin( ⓿ ) & Inf( ❶ )) | (Fin( ❷ ) & Inf( ❸ )) [Rabin 2] 5 5 ⓿ ❷ I->5 5->5 (!a & !b) | (!b & !c) 4 4 ⓿ ❷ 5->4 b & !c 2 2 ❶ ❷ 5->2 b & c 3 3 ❶ ❷ 5->3 a & !b & c 0 0 ❶ ❸ 0->0 a & c 1 1 ⓿ ❸ 0->1 !a & c 0->4 !c 1->1 c 1->4 !c 4->4 !c 4->2 c 2->4 !c 2->2 c 3->5 (!a & !b) | (!b & !c) 3->0 a & b & c 3->4 (!a & b) | (b & !c) 3->3 a & !b & c
In [19]:
spot.sat_minimize(a)
Out[19]:
(Fin( ⓿ ) & Inf( ❶ )) | (Fin( ❷ ) & Inf( ❸ )) [Rabin 2] 0 0 I->0 0->0 !a & !b & c ❷ 0->0 !b & !c ⓿ ❷ 0->0 a & !b & c ❸ 1 1 0->1 b & !c ⓿ ❷ 0->1 b & c ❶ ❸ 1->1 !c ⓿ ❷ 1->1 (!a & b & c) | (a & !b & c) ❶ ❷ 1->1 (!a & !b & c) | (a & b & c) ❶ ❸
In [20]:
spot.sat_minimize(a, state_based=True)
Out[20]:
(Fin( ⓿ ) & Inf( ❶ )) | (Fin( ❷ ) & Inf( ❸ )) [Rabin 2] 0 0 ⓿ I->0 0->0 !a & !b & !c 1 1 ❸ 0->1 (!a & b) | (b & c) 2 2 ❶ ❷ 0->2 (a & !b) | (!b & c) 3 3 ⓿ ❷ 0->3 a & b & !c 1->1 c 1->3 !c 2->0 (!a & !b) | (!b & !c) 2->2 a & !b & c 2->3 b 3->1 a | b | c 3->3 !a & !b & !c
In [21]:
spot.sat_minimize(a, acc="Rabin 1", display_log=True)
input.states target.states reachable.states edges transitions variables clauses enc.user enc.sys sat.user sat.sys
0 5 3 3 10 24 810 25262 1 0 0 0
1 3 1 NaN NaN NaN 69 676 0 0 0 0
2 3 2 2 6 16 276 5299 0 0 0 0
Out[21]:
Fin( ⓿ ) & Inf( ❶ ) [Rabin 1] 0 0 I->0 0->0 (!a & !b) | (!b & !c) ⓿ 0->0 a & !b & c ❶ 1 1 0->1 b & !c ⓿ 0->1 b & c ❶ 1->1 !c ⓿ 1->1 c ❶

ACD with Interactive visualization¶

ACD = Alternative Cycle Decomposition

In [22]:
a = spot.automaton("colorful2.hoa")
acd = spot.acd(a)
acd
Out[22]:
(Fin( ⓿ ) & Fin( ❶ )) | ((Fin( ❹ )|Fin( ❺ )) & (Inf( ❷ )&Inf( ❸ ))) 0 0 I->0 0->0 !p0 & !p1 ⓿ ❶ ❷ ❸ ❺ 1 1 0->1 p0 & !p1 2 2 0->2 p0 & p1 3 3 0->3 !p0 & p1 ❶ ❷ ❸ 1->1 !p0 & !p1 ❶ ❷ ❸ ❺ 1->1 p0 & !p1 ❷ ❹ ❺ 4 4 1->4 !p0 & p1 ❶ ❸ 1->4 p0 & p1 ❹ 2->1 p0 & !p1 ❷ ❹ ❺ 2->2 !p0 & p1 ❶ ❸ 2->4 p0 & p1 ❹ 6 6 2->6 !p0 & !p1 ❶ ❸ ❺ 3->0 !p0 & !p1 ⓿ ❶ ❸ ❺ 3->1 p0 & !p1 3->3 !p0 & p1 ❶ ❸ 3->4 p0 & p1 4->1 p0 & !p1 ❷ ❹ ❺ 4->2 p0 & p1 ❷ ❹ 4->4 !p0 & p1 ❶ ❸ 5 5 4->5 !p0 & !p1 ❶ ❷ ❸ ❺ 6->1 p0 & !p1 ❷ ❹ ❺ 6->2 !p0 & p1 ❶ ❷ ❸ 6->2 p0 & p1 ❷ ❹ 6->6 !p0 & !p1 ❶ ❷ ❸ ❺ 5->1 p0 & !p1 ❷ ❹ ❺ 5->4 !p0 & p1 ❶ ❸ 5->4 p0 & p1 ❹ 5->6 !p0 & !p1 ❶ ❸ ❺
acd min odd 0 SCC #0 T: 5-12,17-28 {1,2,3,4,5} Q: 1,2,4-6 lvl: 0 2 T: 6,8-10,17,19 {2,4,5} Q: 1,2,4 lvl: 1 <2> 0->2 3 T: 11,12,26,28 {1,2,3,5} Q: 2,6 lvl: 1 0->3 4 T: 10,11,18,19 {1,2,3,4} Q: 2,4 lvl: 1 0->4 5 T: 18,20,22 {1,2,3,5} Q: 4,5 lvl: 1 0->5 6 T: 5 {1,2,3,5} Q: 1 lvl: 1 <6> 0->6 8 T: 11 {1,3} Q: 2 lvl: 2 <8> 3->8 9 T: 18 {1,3} Q: 4 lvl: 2 <9> 4->9 10 T: 11 {1,3} Q: 2 lvl: 2 <10> 4->10 11 T: 18 {1,3} Q: 4 lvl: 2 <11> 5->11 1 SCC #1 T: 3,4,15,16 {0,1,2,3,5} Q: 0,3 lvl: 1 7 T: 16 {1,3} Q: 3 lvl: 2 <7> 1->7
In [23]:
spot.acd_transform(a)   # this converts into a Parity automaton
Out[23]:
Fin( ⓿ ) & Inf( ❶ ) [Rabin 1] 0 0 I->0 0->0 !p0 & !p1 ❶ 1 1 0->1 p0 & !p1 2 2 0->2 p0 & p1 3 3 0->3 !p0 & p1 ❶ 1->1 p0 & !p1 ❶ 4 4 1->4 !p0 & !p1 ⓿ 5 5 1->5 !p0 & p1 ⓿ 6 6 1->6 p0 & p1 ❶ 2->1 p0 & !p1 ❶ 2->6 p0 & p1 ❶ 7 7 2->7 !p0 & p1 ⓿ 8 8 2->8 !p0 & !p1 ⓿ 3->0 !p0 & !p1 ❶ 3->1 p0 & !p1 3->3 !p0 & p1 3->6 p0 & p1 4->1 p0 & !p1 ⓿ 4->4 !p0 & !p1 ❶ 4->6 !p0 & p1 ⓿ 4->6 p0 & p1 ⓿ 5->4 p0 & !p1 ⓿ 5->5 !p0 & p1 9 9 5->9 p0 & p1 ❶ 10 10 5->10 !p0 & !p1 ⓿ 6->1 p0 & !p1 ❶ 6->2 p0 & p1 ❶ 6->5 !p0 & p1 ⓿ 6->10 !p0 & !p1 ⓿ 7->4 p0 & !p1 ⓿ 7->5 p0 & p1 ⓿ 7->7 !p0 & p1 7->8 !p0 & !p1 ❶ 8->4 p0 & !p1 ⓿ 8->7 !p0 & p1 ❶ 8->8 !p0 & !p1 ❶ 8->9 p0 & p1 ⓿ 9->4 p0 & !p1 ⓿ 9->5 p0 & p1 ❶ 9->8 !p0 & !p1 ⓿ 9->9 !p0 & p1 10->4 p0 & !p1 ⓿ 10->6 p0 & p1 ⓿ 10->8 !p0 & !p1 ⓿ 11 11 10->11 !p0 & p1 ❶ 11->2 p0 & p1 ⓿ 11->4 p0 & !p1 ⓿ 11->10 !p0 & !p1 ❶ 11->11 !p0 & p1

Prototyping with graphical logs...¶

In [24]:
def print_scc_info(si, log=""):
    n = si.scc_count()
    print(log, "Number of SCCs:", n)
    for s in range(n):
        print("{}  - SCC{}: {}{}".format(log, s, si.states_of(s), " trivial" if si.is_trivial(s) else ""))

def display_scc(si, sccnum):
    display(si.split_on_sets(sccnum, [], True)[0])

def log_is_empty1(log, g):
    print(log, "is_empty() running SCC decomposition")
    # We need to pass the TRACK_STATE option for print_scc_info() to work
    si = spot.scc_info_with_options(g, spot.scc_info_options_TRACK_STATES)
    print_scc_info(si, log)
    for scc_num in range(si.scc_count()):
        if si.is_trivial(scc_num):
            continue
        if not log_is_scc_empty1("{}SCC{}:".format(log, scc_num), si, scc_num):
            return False
    return True

def log_is_scc_empty1(log, si, scc_num, acc=None):
    if acc is None:
        acc = si.get_aut().acc()
    print("{} is_scc_empty() on".format(log))
    display_scc(si, scc_num)
    occur, common = si.acc_sets_of(scc_num), si.common_sets_of(scc_num)
    print("{} occur={}, common={}".format(log, occur, common))
    acc = acc.restrict_to(occur)
    acc = acc.remove(common, False)
    print("{} acceptance reduced to {}".format(log, acc.get_acceptance()))
    if acc.is_t(): return False
    if acc.is_f(): return True
    if acc.accepting(occur): return False
    for idx, cl in enumerate(acc.top_disjuncts()):
        print("{}clause{}: checking clause {}".format(log, idx, cl.get_acceptance()))
        fu = cl.fin_unit() # Is there Fin at the top level
        if fu:
            print("{}clause{}: unit-Fin(s) present {}".format(log, idx, fu))
            with spot.scc_and_mark_filter(si, scc_num, fu) as filt:
                filt.override_acceptance(cl.remove(fu, True))
                if not log_is_empty1("{}clause{}:Fin{}=1:".format(log, idx, fu), filt):
                    return False
        else:
            # Pick some Fin term anywhere in the formula
            fo = cl.fin_one()
            print("{}clause{}: arbritrary Fin selected {}".format(log, idx, fo))
            # Try to solve assuming Fin(fo)=True
            with spot.scc_and_mark_filter(si, scc_num, [fo]) as filt:
                filt.override_acceptance(cl.remove([fo], True))
                if not log_is_empty1("{}clause{}:Fin({})=1:".format(log, idx, fo), filt):
                    return False
            # Try to solve assuming Fin(fo)=False
            if not log_is_scc_empty1("{}clause{}:Fin({})=0:".format(log, idx, fo), 
                                     si, scc_num, acc.force_inf([fo])):
                return False
    return True
In [25]:
log_is_empty1("", a)
 is_empty() running SCC decomposition
 Number of SCCs: 2
  - SCC0: (1, 4, 2, 6, 5)
  - SCC1: (0, 3)
SCC0: is_scc_empty() on
(Fin( ⓿ ) & Fin( ❶ )) | ((Fin( ❹ )|Fin( ❺ )) & (Inf( ❷ )&Inf( ❸ ))) 0 1 I->0 0->0 !p0 & !p1 ❶ ❷ ❸ ❺ 0->0 p0 & !p1 ❷ ❹ ❺ 1 4 0->1 !p0 & p1 ❶ ❸ 0->1 p0 & p1 ❹ 1->0 p0 & !p1 ❷ ❹ ❺ 1->1 !p0 & p1 ❶ ❸ 2 2 1->2 p0 & p1 ❷ ❹ 3 5 1->3 !p0 & !p1 ❶ ❷ ❸ ❺ 2->0 p0 & !p1 ❷ ❹ ❺ 2->1 p0 & p1 ❹ 2->2 !p0 & p1 ❶ ❸ 4 6 2->4 !p0 & !p1 ❶ ❸ ❺ 3->0 p0 & !p1 ❷ ❹ ❺ 3->1 !p0 & p1 ❶ ❸ 3->1 p0 & p1 ❹ 3->4 !p0 & !p1 ❶ ❸ ❺ 4->0 p0 & !p1 ❷ ❹ ❺ 4->2 !p0 & p1 ❶ ❷ ❸ 4->2 p0 & p1 ❷ ❹ 4->4 !p0 & !p1 ❶ ❷ ❸ ❺
SCC0: occur={1,2,3,4,5}, common={}
SCC0: acceptance reduced to Fin(1) | ((Fin(4)|Fin(5)) & (Inf(2)&Inf(3)))
SCC0:clause0: checking clause Fin(1)
SCC0:clause0: unit-Fin(s) present {1}
SCC0:clause0:Fin{1}=1: is_empty() running SCC decomposition
SCC0:clause0:Fin{1}=1: Number of SCCs: 3
SCC0:clause0:Fin{1}=1:  - SCC0: (1, 4, 2)
SCC0:clause0:Fin{1}=1:  - SCC1: (6,) trivial
SCC0:clause0:Fin{1}=1:  - SCC2: (5,) trivial
SCC0:clause0:Fin{1}=1:SCC0: is_scc_empty() on
t [Fin-less 6] 0 1 I->0 0->0 p0 & !p1 ❷ ❹ ❺ 1 4 0->1 p0 & p1 ❹ 1->0 p0 & !p1 ❷ ❹ ❺ 2 2 1->2 p0 & p1 ❷ ❹ 2->0 p0 & !p1 ❷ ❹ ❺ 2->1 p0 & p1 ❹
SCC0:clause0:Fin{1}=1:SCC0: occur={2,4,5}, common={4}
SCC0:clause0:Fin{1}=1:SCC0: acceptance reduced to t
Out[25]:
False

Part 4 – Reactive synthesis¶

LTL synthesis¶

In [26]:
spec = spot.formula("(F(i0 & X(i1)) <-> Fo)")
spec
Out[26]:
$\mathsf{F} (i_{0} \land \mathsf{X} i_{1}) \leftrightarrow \mathsf{F} o$
In [27]:
game = spot.automaton(f"ltlsynt -f {spec:q} --decompose=no --print-game|")
game.show('.t')
Out[27]:
Inf( ⓿ ) [Büchi] 4 4 I->4 7 7 4->7 !i0 ⓿ 9 9 4->9 i0 ⓿ 0 0 6 6 0->6 1 ⓿ 6->0 1 ⓿ 1 1 1->7 !i0 & !i1 ⓿ 8 8 1->8 i1 ⓿ 1->9 i0 & !i1 ⓿ 7->4 !o ⓿ 2 2 7->2 o ⓿ 8->0 o ⓿ 5 5 8->5 !o ⓿ 9->1 !o ⓿ 3 3 9->3 o ⓿ 10 10 2->10 !i0 11 11 2->11 i0 10->2 1 11->3 1 3->10 !i0 & !i1 3->11 i0 & !i1 12 12 3->12 i1 12->0 1 13 13 5->13 1 13->0 o 13->5 !o
In [28]:
spot.solve_game(game)
Out[28]:
True
In [29]:
spot.highlight_strategy(game)
game.show(".t")
Out[29]:
Inf( ⓿ ) [Büchi] 4 4 I->4 7 7 4->7 !i0 ⓿ 9 9 4->9 i0 ⓿ 0 0 6 6 0->6 1 ⓿ 6->0 1 ⓿ 1 1 1->7 !i0 & !i1 ⓿ 8 8 1->8 i1 ⓿ 1->9 i0 & !i1 ⓿ 7->4 !o ⓿ 2 2 7->2 o ⓿ 8->0 o ⓿ 5 5 8->5 !o ⓿ 9->1 !o ⓿ 3 3 9->3 o ⓿ 10 10 2->10 !i0 11 11 2->11 i0 10->2 1 11->3 1 3->10 !i0 & !i1 3->11 i0 & !i1 12 12 3->12 i1 12->0 1 13 13 5->13 1 13->0 o 13->5 !o
In [30]:
m = spot.solved_game_to_separated_mealy(game)
m
Out[30]:
0 0 I->0 0->0 !i0 / !o 1 1 0->1 i0 / !o 1->0 !i0 & !i1 / !o 1->1 i0 & !i1 / !o 2 2 1->2 i1 / o 2->2 1 / 1
In [31]:
spot.reduce_mealy_here(m)
m
Out[31]:
0 0 I->0 0->0 !i0 / !o 1 1 0->1 i0 / !o 1->0 !i0 & !i1 / !o 1->1 i0 & !i1 / !o 1->1 i1 / o
In [32]:
spot.mealy_machine_to_aig(m, "isop").show("h")
Out[32]:
6 L0_out 8 8 6->8 10 10 8->10 o0 o 8->o0:w L0 L0_in 10->L0 2 i0 2->10 4 i1 4->8

LTLf Synthesis?¶

In [33]:
spec_ltlf = spot.formula("(F(i0 & Xi1) <-> GFo) && X[!]1")
spec_ltlf 
Out[33]:
$(\mathsf{F} (i_{0} \land \mathsf{X} i_{1}) \leftrightarrow \mathsf{G} \mathsf{F} o) \land \mathsf{X^{[!]}}\top$

LTLf translation Via LTL?¶

In [34]:
spec_ltl = spot.from_ltlf(spec_ltlf)
spec_ltl
Out[34]:
$\mathit{alive} \land (\mathsf{F} (\mathit{alive} \land i_{0} \land \mathsf{X} (\lnot \mathit{alive} \lor i_{1})) \leftrightarrow \mathsf{G} (\lnot \mathit{alive} \lor \mathsf{F} (\mathit{alive} \land o))) \land \mathsf{X} \mathit{alive} \land (\mathit{alive} \mathbin{\mathsf{U}} \mathsf{G} \lnot \mathit{alive})$
In [35]:
dba = spot.translate(spec_ltl, "det")
dba
Out[35]:
[Büchi] 0 0 I->0 5 5 0->5 alive & !i0 6 6 0->6 alive & i0 5->5 alive & !i0 & o 5->6 alive & i0 & !o 1 1 5->1 alive & !i0 & !o 2 2 5->2 alive & i0 & o 6->5 alive & !i0 & !i1 & o 6->6 alive & i0 & !i1 & !o 6->1 alive & !i0 & !i1 & !o 6->2 alive & i0 & !i1 & o 4 4 6->4 alive & i1 & !o 7 7 6->7 alive & i1 & o 1->5 alive & !i0 & o 1->6 alive & i0 & !o 1->1 alive & !i0 & !o 1->2 alive & i0 & o 3 3 1->3 !alive 2->5 alive & !i0 & !i1 & o 2->6 alive & i0 & !i1 & !o 2->1 alive & !i0 & !i1 & !o 2->2 alive & i0 & !i1 & o 2->3 !alive 2->4 alive & i1 & !o 2->7 alive & i1 & o 3->3 !alive 4->4 alive & !o 4->7 alive & o 7->3 !alive 7->4 alive & !o 7->7 alive & o
In [36]:
dfa = spot.to_finite(dba)
dfa
Out[36]:
[Büchi] 0 0 I->0 4 4 0->4 !i0 5 5 0->5 i0 4->4 !i0 & o 4->5 i0 & !o 1 1 4->1 !i0 & !o 2 2 4->2 i0 & o 5->4 !i0 & !i1 & o 5->5 i0 & !i1 & !o 5->1 !i0 & !i1 & !o 5->2 i0 & !i1 & o 3 3 5->3 i1 & !o 6 6 5->6 i1 & o 1->4 !i0 & o 1->5 i0 & !o 1->1 !i0 & !o 1->2 i0 & o 2->4 !i0 & !i1 & o 2->5 i0 & !i1 & !o 2->1 !i0 & !i1 & !o 2->2 i0 & !i1 & o 2->3 i1 & !o 2->6 i1 & o 3->3 !o 3->6 o 6->3 !o 6->6 o

Using MONA-like MTBDD-based DFA¶

This stuff is not yet released, at the time of Spin'25. It will be part of Spot 2.14.

In [37]:
mtdfa = spot.ltlf_to_mtdfa(spec_ltlf)
mtdfa
Out[37]:
mtdfa S0 (F(i0 & Xi1) <-> GFo) & X[!]1 I->S0 B13558 i0 S0->B13558 S1 F(i0 & Xi1) <-> GFo B13562 i0 S1->B13562 S2 GFo <-> (i1 | F(i0 & Xi1)) B13570 i0 S2->B13570 S3 GFo B13571 o S3->B13571 B13566 o B13570->B13566 B13569 o B13570->B13569 B13557 GFo <-> (i1 | F(i0 & Xi1)) B13558->B13557 B13526 F(i0 & Xi1) <-> GFo B13558->B13526 B13560 o B13562->B13560 B13561 o B13562->B13561 B13563 GFo B13571->B13563 B13530 GFo B13571->B13530 B13559 F(i0 & Xi1) <-> GFo B13560->B13559 B13560->B13526 B13561->B13557 B13527 GFo <-> (i1 | F(i0 & Xi1)) B13561->B13527 B13564 i1 B13566->B13564 B13565 i1 B13566->B13565 B13568 i1 B13569->B13568 B13567 i1 B13569->B13567 B13564->B13563 B13564->B13559 B13565->B13530 B13565->B13526 B13568->B13530 B13568->B13527 B13567->B13563 B13567->B13557
In [38]:
mtdfa.as_twa()
Out[38]:
Inf( ⓿ ) [Büchi] 0 (F(i0 & Xi1) <-> GFo) & X[!]1 I->0 1 F(i0 & Xi1) <-> GFo 0->1 !i0 2 GFo <-> (i1 | F(i0 & Xi1)) 0->2 i0 1->1 !i0 & o 1->1 !i0 & !o ⓿ 1->2 i0 & !o 1->2 i0 & o ⓿ 2->1 !i0 & !i1 & o 2->1 !i0 & !i1 & !o ⓿ 2->2 i0 & !i1 & !o 2->2 i0 & !i1 & o ⓿ 3 GFo 2->3 i1 & !o 2->3 i1 & o ⓿ 3->3 !o 3->3 o ⓿
In [39]:
spot.product_xor(spot.twadfa_to_mtdfa(dfa), mtdfa).is_empty()
Out[39]:
True
In [40]:
with spot.bdd_dict_preorder("i1", "i0") as d:
    a = spot.ltlf_to_mtdfa(spec_ltlf, dict=d)
    a.set_controllable_variables("o")
In [41]:
a
Out[41]:
mtdfa S0 (F(i0 & Xi1) <-> GFo) & X[!]1 I->S0 B13647 i0 S0->B13647 S1 F(i0 & Xi1) <-> GFo B13650 i0 S1->B13650 S2 GFo <-> (i1 | F(i0 & Xi1)) B13652 i1 S2->B13652 S3 GFo B13651 o S3->B13651 B13652->B13650 B13652->B13651 B13526 F(i0 & Xi1) <-> GFo B13647->B13526 B13557 GFo <-> (i1 | F(i0 & Xi1)) B13647->B13557 B13648 o B13650->B13648 B13649 o B13650->B13649 B13530 GFo B13651->B13530 B13563 GFo B13651->B13563 B13648->B13526 B13559 F(i0 & Xi1) <-> GFo B13648->B13559 B13527 GFo <-> (i1 | F(i0 & Xi1)) B13649->B13527 B13649->B13557
In [42]:
with spot.bdd_dict_preorder("i1", "i0") as d:
    a = spot.ltlf_to_mtdfa_for_synthesis(spec_ltlf, ["o"], dict=d)
a
Out[42]:
mtdfa S0 (F(i0 & Xi1) <-> GFo) & X[!]1 I->S0 B13647 i0 S0->B13647 S1 F(i0 & Xi1) <-> GFo B13662 i0 S1->B13662 S2 GFo <-> (i1 | F(i0 & Xi1)) B13664 i1 S2->B13664 B13664->B13662 B6 o B13664->B6 B13662->B6 B7 o B13662->B7 B13557 GFo <-> (i1 | F(i0 & Xi1)) B13647->B13557 B13526 F(i0 & Xi1) <-> GFo B13647->B13526 B0 0 B6->B0 B1 1 B6->B1 B7->B0 B7->B1
In [43]:
m = spot.mtdfa_strategy_to_mealy(a)
m
Out[43]:
0 (F(i0 & Xi1) <-> GFo) & X[!]1 I->0 1 F(i0 & Xi1) <-> GFo 0->1 !i0 / 1 2 GFo <-> (i1 | F(i0 & Xi1)) 0->2 i0 / 1 3 1 1->3 !i0 / !o 1->3 i0 / o 2->3 !i0 & !i1 / !o 2->3 i0 | i1 / o 3->3 1 / 1
In [44]:
spot.reduce_mealy_here(m)
m
Out[44]:
0 (F(i0 & Xi1) <-> GFo) & X[!]1 I->0 1 F(i0 & Xi1) <-> GFo 0->1 !i0 / 1 2 GFo <-> (i1 | F(i0 & Xi1)) 0->2 i0 / 1 1->2 !i0 / !o 1->2 i0 / o 2->2 !i0 & !i1 / !o 2->2 i0 | i1 / o
In [45]:
spot.mealy_machine_to_aig(m, "isop").show("h")
Out[45]:
6 L0_out 10 10 6->10 14 14 6->14 18 18 6->18 26 26 6->26 32 32 6->32 34 34 6->34 8 L1_out 12 12 8->12 16 16 8->16 20 20 8->20 28 28 8->28 30 30 8->30 8->32 8->34 10->12 22 22 12->22 14->16 16->22 18->20 24 24 20->24 22->24 o0 o 24->o0:w 26->28 L0 L0_in 28->L0 36 36 30->36 32->36 38 38 34->38 36->38 L1 L1_in 38->L1 2 i1 2->10 4 i0 4->14 4->18 4->26 4->30
In [ ]: