Representation of ω-automata with state-based acceptance using MTBDDs.

We initially implemented a transition-based variant, before realizing that, when using MTBDDs, the transition-based and state-based versions of an automaton have exactly the same structure; the state-based version is even slightly more memory-efficient.

Although the transition-based variant still exists (see the mtdtwa.ipynb notebook), its state-based counterpart, presented here, is much more developed.

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

Multi-Terminal-BDD-based Deterministic State-based ω-Automaton (MTDSwA)

The MTBDD-based representation of these automata is very close to Mona's DFA representation. An mtdswa is simply a pair of arrays indexed by state numbers. The value of states[i] is an MTBDD encoding the successor states of state i, with terminals containing state indices. The value of colors[i] gives the set of colors associated with state i.

Compared to mtdtwa, the set of MTBDD nodes needed to represent an mtdswa is no larger than the one needed to represent an mtdtwa, because states with different colors but identical behavior can share their MTBDD. This representation seems much easier to work with.

In [2]:
a2 = spot.translate("GFa <-> c", "generic", "deterministic", "SBAcc")
a2
Out[2]:
(Inf( )&Inf( )) | (Fin( ) & Fin( )) 0 0 I->0 1 1 0->1 a & c 2 2 0->2 !a & c 3 3 0->3 a & !c 4 4 0->4 !a & !c 1->1 a 1->2 !a 2->1 a 2->2 !a 3->3 a 3->4 !a 4->3 a 4->4 !a
In [3]:
m2 = spot.dtwa_to_mtdswa(a2)
display(m2)
mtdswa (Inf( )&Inf( )) | (Fin( ) & Fin( )) S0 0 I->S0 B212 c S0->B212 S1 1 B202 a S1->B202 S2 2 S2->B202 S3 3 B211 a S3->B211 S4 4 S4->B211 B212->B202 B212->B211 B35 2 B202->B35 B34 1 B202->B34 B208 4 B211->B208 B204 3 B211->B204
In [4]:
for i, d in enumerate(m2.colors):
    print(f"colors[{i}] = {d}")
colors[0] = {}
colors[1] = {0,1}
colors[2] = {0}
colors[3] = {1}
colors[4] = {}
In [5]:
a3 = spot.translate("Ga | Fb")
a3
Out[5]:
[Büchi] 2 2 I->2 2->2 a & !b 0 0 2->0 !a & !b 1 1 2->1 b 0->0 !b 0->1 b 1->1 1
In [6]:
m3 = spot.dtwa_to_mtdswa(a3)
m3
Out[6]:
mtdswa Inf( ) [Büchi] S0 0 I->S0 B358 a S0->B358 S1 1 B352 b S1->B352 B358->B352 B357 b B358->B357 B1 1 B352->B1 B34 1 B352->B34 B357->B1 B37 0 B357->B37
In [7]:
display_inline(m3.as_twa(), m3.as_twa(True))
Inf( ) [Büchi] 0 0 I->0 0->0 a & !b 1 1 0->1 !a & !b 2 2 0->2 b 1->1 !b 1->2 b 2->2 1
[Büchi] 0 0 I->0 0->0 a & !b 1 1 0->1 !a & !b 2 2 0->2 b 1->1 !b 1->2 b 2->2 1
In [8]:
display_inline(m2.as_twa(), m2.as_twa(True))
(Inf( )&Inf( )) | (Fin( ) & Fin( )) 0 0 I->0 1 1 0->1 a & c 2 2 0->2 !a & c 3 3 0->3 a & !c 4 4 0->4 !a & !c 1->1 a 1->2 !a 2->1 a 2->2 !a 3->3 a 3->4 !a 4->3 a 4->4 !a
(Inf( )&Inf( )) | (Fin( ) & Fin( )) 0 0 I->0 4 4 0->4 !a & !c 3 3 0->3 a & !c 2 2 0->2 !a & c 1 1 0->1 a & c 4->4 !a 4->3 a 3->4 !a 3->3 a 2->2 !a 2->1 a 1->2 !a 1->1 a
In [9]:
m2
Out[9]:
mtdswa (Inf( )&Inf( )) | (Fin( ) & Fin( )) S0 0 I->S0 B212 c S0->B212 S1 1 B202 a S1->B202 S2 2 S2->B202 S3 3 B211 a S3->B211 S4 4 S4->B211 B212->B202 B212->B211 B35 2 B202->B35 B34 1 B202->B34 B208 4 B211->B208 B204 3 B211->B204
In [10]:
i1 = spot.dtwa_to_mtdswa(spot.translate("G(c)", "det")); i1
Out[10]:
mtdswa t [all] S0 0 I->S0 B364 c S0->B364 B0 0 B364->B0 B37 0 B364->B37

When using as_twa(), the third argument allows you to choose between an incomplete TwA (rejecting sinks are removed) and a complete TwA (rejecting sinks are added). Adding sinks may require changing the acceptance condition.

In [11]:
i1i = i1.as_twa(True, False, False)
i1c = i1.as_twa(True, False, True)
display_inline(i1i, i1i.prop_complete(), i1c, i1c.prop_complete())
t [all] 0 0 I->0 0->0 c
no
[co-Büchi] 0 0 I->0 0->0 c 1 1 0->1 !c 1->1 1
yes

Automata operations on MTDSwAs

Products

The Cartesian product of two MTDSwAs can be used to implement any Boolean operation. The Boolean operation just changes the way the acceptance conditions are combined, as well as some construction shortcuts for sinks. Each product state receives the colors of both original states, with colors from the right automaton shifted to avoid conflicts.

The following Boolean functions are available:

  • product() implements $\land$
  • product_or() implements $\lor$
  • product_xor() implements $\oplus$
  • product_xnor() implements $\leftrightarrow$
  • product_implies() implements $\rightarrow$

Let's build two MTDSwAs so we can combine them:

In [12]:
a1 = spot.translate("GFa", "generic", "deterministic", "complete", "SBAcc")
a2 = spot.translate("Gb | a", "generic", "deterministic", "complete", "SBAcc")
m1 = spot.dtwa_to_mtdswa(a1)
m2 = spot.dtwa_to_mtdswa(a2)
m1.sinks_as_constants()
m2.sinks_as_constants()
display_inline(m1, m2, per_row=2)
mtdswa Inf( ) [Büchi] S0 0 I->S0 B382 a S0->B382 S1 1 S1->B382 B34 1 B382->B34 B37 0 B382->B37
mtdswa Inf( ) [Büchi] S0 0 I->S0 B375 a S0->B375 S1 1 B374 b S1->B374 B375->B374 B1 1 B375->B1 B0 0 B374->B0 B34 1 B374->B34
In [13]:
display_inline(spot.product_xor(m1, m2), spot.product_xnor(m1, m2), per_row=2)
mtdswa (Inf( ) & Fin( )) | (Fin( ) & Inf( )) [Rabin-like 2] S0 0 I->S0 B389 a S0->B389 S1 1 B390 a S1->B390 S2 2 B393 a S2->B393 S3 3 B395 a S3->B395 S4 4 S4->B390 S5 5 S5->B393 S6 6 S6->B395 B392 b B393->B392 B388 b B393->B388 B389->B388 B204 3 B389->B204 B208 4 B390->B208 B34 1 B390->B34 B394 6 B395->B394 B395->B204 B392->B208 B391 5 B392->B391 B35 2 B388->B35 B388->B34
mtdswa (Fin( ) & Fin( )) | (Inf( )&Inf( )) S0 0 I->S0 B389 a S0->B389 S1 1 B390 a S1->B390 S2 2 B393 a S2->B393 S3 3 B395 a S3->B395 S4 4 S4->B390 S5 5 S5->B393 S6 6 S6->B395 B392 b B393->B392 B388 b B393->B388 B389->B388 B204 3 B389->B204 B208 4 B390->B208 B34 1 B390->B34 B394 6 B395->B394 B395->B204 B392->B208 B391 5 B392->B391 B35 2 B388->B35 B388->B34
In [14]:
display_inline(spot.product_implies(m1, m2))
mtdswa Inf( ) | Fin( ) [Streett 1] S0 0 I->S0 B396 a S0->B396 S1 1 B397 a S1->B397 S2 2 B399 a S2->B399 S3 3 S3->B397 S4 4 S4->B399 B388 b B399->B388 B398 b B399->B398 B204 3 B397->B204 B34 1 B397->B34 B396->B388 B1 1 B396->B1 B388->B34 B35 2 B388->B35 B398->B204 B208 4 B398->B208

Another example, a product with a more complex acceptance:

In [15]:
a1 = spot.translate("(p0 U p1) & GFp0 & GFp1 & FGp2", "generic", "deterministic", "complete", "SBAcc")
a2 = spot.translate("(p0 U p1) & GFp0 & GFp1 & FGp2", "generic", "deterministic", "complete", "SBAcc")
m1 = spot.dtwa_to_mtdswa(a1)
m2 = spot.dtwa_to_mtdswa(a2)
m1.sinks_as_constants()
m2.sinks_as_constants()
m12 = spot.product(m1, m2)

display_inline(m1, m2, m12, per_row=2)
mtdswa (Inf( )&Inf( )) & Fin( ) [Streett-like 3] S0 0 I->S0 B1814 p0 S0->B1814 S1 1 B1834 p0 S1->B1834 S2 2 S2->B1834 S3 3 S3->B1834 S4 4 S4->B1834 S5 5 S5->B1834 B1833 p1 B1834->B1833 B1827 p1 B1834->B1827 B1813 p1 B1814->B1813 B1807 p1 B1814->B1807 B1812 p2 B1833->B1812 B1832 p2 B1833->B1832 B1813->B1812 B0 0 B1813->B0 B1806 p2 B1807->B1806 B37 0 B1807->B37 B1827->B1806 B1826 p2 B1827->B1826 B34 1 B1812->B34 B204 3 B1812->B204 B1806->B34 B35 2 B1806->B35 B1826->B34 B208 4 B1826->B208 B1832->B34 B391 5 B1832->B391
mtdswa (Inf( )&Inf( )) & Fin( ) [Streett-like 3] S0 0 I->S0 B1814 p0 S0->B1814 S1 1 B1834 p0 S1->B1834 S2 2 S2->B1834 S3 3 S3->B1834 S4 4 S4->B1834 S5 5 S5->B1834 B1833 p1 B1834->B1833 B1827 p1 B1834->B1827 B1813 p1 B1814->B1813 B1807 p1 B1814->B1807 B1812 p2 B1833->B1812 B1832 p2 B1833->B1832 B1813->B1812 B0 0 B1813->B0 B1806 p2 B1807->B1806 B37 0 B1807->B37 B1827->B1806 B1826 p2 B1827->B1826 B34 1 B1812->B34 B204 3 B1812->B204 B1806->B34 B35 2 B1806->B35 B1826->B34 B208 4 B1826->B208 B1832->B34 B391 5 B1832->B391
mtdswa (Inf( )&Inf( )&Inf( )&Inf( )) & Fin( ) & Fin( ) [Streett-like 6] S0 0 I->S0 B1837 p0 S0->B1837 S1 1 B1838 p0 S1->B1838 S2 2 S2->B1838 S3 3 S3->B1838 S4 4 S4->B1838 S5 5 S5->B1838 B1827 p1 B1838->B1827 B1833 p1 B1838->B1833 B1835 p1 B1837->B1835 B1836 p1 B1837->B1836 B1806 p2 B1827->B1806 B1826 p2 B1827->B1826 B1835->B1806 B0 0 B1835->B0 B1812 p2 B1836->B1812 B37 0 B1836->B37 B1833->B1812 B1832 p2 B1833->B1832 B34 1 B1806->B34 B35 2 B1806->B35 B1812->B34 B204 3 B1812->B204 B1832->B34 B391 5 B1832->B391 B1826->B34 B208 4 B1826->B208

Products on automata with names will also combine their names:

In [16]:
m1 = spot.obligation_to_mtdswa("Ga xor b")
m2 = spot.obligation_to_mtdswa("aUb -> Ga")
m12 = spot.product(m1, m2)
display_inline(m1, m2, m12)
mtdswa Inf( ) [Büchi] S0 b xor Ga I->S0 B1839 a S0->B1839 S1 Ga B198 a S1->B198 S2 !Ga B1840 a S2->B1840 B1 1 B1840->B1 B35 !Ga B1840->B35 B0 0 B198->B0 B34 Ga B198->B34 B14 b B1839->B14 B388 b B1839->B388 B14->B1 B14->B0 B388->B35 B388->B34
mtdswa Inf( ) [Büchi] S0 aUb -> Ga I->S0 B1842 a S0->B1842 S1 Ga B198 a S1->B198 B0 0 B198->B0 B34 Ga B198->B34 B44 aUb B1842->B44 B1841 aUb B1842->B1841 B44->B0 B1 1 B44->B1 B1841->B1 B1841->B34
mtdswa Inf( )&Inf( ) [gen. Büchi 2] S0 (b xor Ga) & (aUb -> Ga) I->S0 B1847 a S0->B1847 S1 Ga B198 a S1->B198 S2 Ga B1848 a S2->B1848 S3 !Ga B1849 a S3->B1849 S4 Ga & !Ga B1850 a S4->B1850 B0 0 B1848->B0 B35 Ga B1848->B35 B1843 b B1847->B1843 B1846 b B1847->B1846 B198->B0 B34 Ga B198->B34 B1850->B0 B208 Ga & !Ga B1850->B208 B1 1 B1849->B1 B204 !Ga B1849->B204 B44 aUb B1843->B44 B1843->B0 B1844 aUb B1846->B1844 B1845 aUb B1846->B1845 B44->B1 B44->B0 B1844->B35 B1844->B34 B1845->B208 B1845->B204

In the previous two examples we can see the limitations of our implementation. We could try to avoid duplicating states and colors when possible.

Complement

Additionally, complement() does what's expected: The acceptance condition of the automaton is negated, and bddtrue and bddfalse leaves are swapped. Formula labels are also negated for looks.

In [17]:
m = spot.obligation_to_mtdswa("Ga xor b")
mc = spot.complement(m)
display_inline(m, mc, per_row=2)
mtdswa Inf( ) [Büchi] S0 b xor Ga I->S0 B1839 a S0->B1839 S1 Ga B198 a S1->B198 S2 !Ga B1840 a S2->B1840 B1 1 B1840->B1 B35 !Ga B1840->B35 B0 0 B198->B0 B34 Ga B198->B34 B14 b B1839->B14 B388 b B1839->B388 B14->B1 B14->B0 B388->B35 B388->B34
mtdswa Fin( ) [co-Büchi] S0 !(b xor Ga) I->S0 B1851 a S0->B1851 S1 !Ga B1852 a S1->B1852 S2 Ga B1848 a S2->B1848 B0 0 B1848->B0 B35 Ga B1848->B35 B1 1 B1852->B1 B34 !Ga B1852->B34 B15 b B1851->B15 B388 b B1851->B388 B15->B0 B15->B1 B388->B35 B388->B34

Trimming unnecessary states

The trim() function removes different kinds of unnecessary states.

By default, it only removes states that are unreachable from the initial state.

If the optional trim_useless_sccs_too parameter is set to true, states from which a word will inevitably be accepted/rejected are also replaced with bddtrue/bddfalse respectively. This option is only slightly more expensive because it has to compute the SCCs of the MTDSwA. Note that trim() uses a simple check to detect some SCCs that cannot accept or that cannot reject, but on automata that are not weak, this can fail to detect and remove such SCCs.

Here is an example use of trim:

In [18]:
f = spot.formula("\\forall a: Gd & (Fa U c) & (b xor Xa)")
swa = spot.obligation_to_mtdswa(f)
display_inline(swa)
mtdswa Inf( ) [Büchi] S0 Gd & (Fa U c) & (b xor Xa) I->S0 B1897 c S0->B1897 S1 a & Gd & Fa & (Fa U c) B0 0 S1->B0 S2 a & Gd S2->B0 B1853 d B1897->B1853 B1896 d B1897->B1896 B1853->B0 B34 a & Gd & Fa & (Fa U c) B1853->B34 B1896->B0 B35 a & Gd B1896->B35

Here, states a & Fa & Gd & (Fa U c) and a & Gd lead to bddfalse (a rejecting sink). Any word reaching these states will then be rejected, so we can replace them directly with bddfalse.

In [19]:
spot.trim(swa, True)
display_inline(swa)
mtdswa Inf( ) [Büchi] S0 Gd & (Fa U c) & (b xor Xa) I->S0 B0 0 S0->B0

Here is a hand-built example, showing most of the reductions made by trim:

In [20]:
a = spot.automaton('''
HOA: v1
name: "a"
Start: 0
AP: 1 "a"
acc-name: Buchi
Acceptance: 1 Inf(0)
--BODY--
State: 0      1 10
State: 1      5 8
State: 2  {0} 3 14
State: 3  {0} 2 14
State: 4      [t] 13
State: 5      6 13
State: 6      13 5
State: 7      [t] 13
State: 8  {0} [t] 9
State: 9      [t] 8
State: 10     2 11
State: 11     4 7
State: 12     [t] 4
State: 13     [f] 13
State: 14 {0} [t] 14
--END--
''')
display(a)
m = spot.dtwa_to_mtdswa(a)
print("m:")
display(m.show('s'))
print("trim(m):")
spot.trim(m)
display(m.show('s'))
print("trim(m, True):")
spot.trim(m, True)
display(m.show('s'))
a [Büchi] 0 0 I->0 1 1 0->1 !a 10 10 0->10 a 5 5 1->5 !a 8 8 1->8 a 2 2 10->2 !a 11 11 10->11 a 13 13 5->13 a 6 6 5->6 !a 9 9 8->9 1 3 3 2->3 !a 14 14 2->14 a 3->2 !a 3->14 a 14->14 1 4 4 4->13 1 6->5 a 6->13 !a 7 7 7->13 1 9->8 1 11->4 !a 11->7 a 12 12 12->4 1
m:
mtdswa Inf( ) [Büchi] cluster_3 cluster_4 cluster_5 cluster_1 cluster_6 cluster_2 cluster_8 cluster_7 cluster_0 S0 0 I->S0 B1899 a S0->B1899 S1 1 B1902 a S1->B1902 S2 2 B1904 a S2->B1904 S3 3 B1854 a S3->B1854 S4 4 B1874 13 S4->B1874 S5 5 B1907 a S5->B1907 S6 6 B1910 a S6->B1910 S7 7 S7->B1874 S8 8 B1863 9 S8->B1863 S9 9 B1861 8 S9->B1861 S10 10 B1912 a S10->B1912 S11 11 B1914 a S11->B1914 S12 12 B208 4 S12->B208 S13 13 B0 0 S13->B0 B391 5 B1910->B391 B1910->B1874 B1 1 B1904->B1 B204 3 B1904->B204 B34 1 B1899->B34 B1865 10 B1899->B1865 B394 6 B1907->B394 B1907->B1874 B1860 7 B1914->B1860 B1914->B208 B35 2 B1912->B35 B1868 11 B1912->B1868 B1902->B391 B1902->B1861 B1854->B1 B1854->B35
trim(m):
mtdswa Inf( ) [Büchi] cluster_3 cluster_4 cluster_5 cluster_1 cluster_6 cluster_2 cluster_8 cluster_7 cluster_0 S0 0 I->S0 B1899 a S0->B1899 S1 1 B1902 a S1->B1902 S2 2 B1904 a S2->B1904 S3 3 B1854 a S3->B1854 S4 4 B1870 12 S4->B1870 S5 5 B1915 a S5->B1915 S6 6 B1916 a S6->B1916 S7 7 S7->B1870 S8 8 B1863 9 S8->B1863 S9 9 B1861 8 S9->B1861 S10 10 B1912 a S10->B1912 S11 11 B1914 a S11->B1914 S12 12 B0 0 S12->B0 B391 5 B1916->B391 B1916->B1870 B1 1 B1904->B1 B204 3 B1904->B204 B1865 10 B1899->B1865 B34 1 B1899->B34 B394 6 B1915->B394 B1915->B1870 B208 4 B1914->B208 B1860 7 B1914->B1860 B35 2 B1912->B35 B1868 11 B1912->B1868 B1902->B391 B1902->B1861 B1854->B1 B1854->B35
trim(m, True):
mtdswa Inf( ) [Büchi] cluster_1 cluster_0 cluster_2 S0 0 I->S0 B390 a S0->B390 S1 1 B1848 a S1->B1848 S2 2 B204 3 S2->B204 S3 3 B35 2 S3->B35 S4 4 B5 a S4->B5 B0 0 B5->B0 B1 1 B5->B1 B1848->B0 B1848->B35 B208 4 B390->B208 B34 1 B390->B34

In the original explicit automaton, states 13 and 14 are sinks that have already been converted into bddfalse/bddtrue by the dtwa_to_mtdswa() function. State 12 is the only unreachable state, so calling trim(m) will remove only this state. Calling trim(m, True) will remove much more. In the original automaton:

  • SCC {5,6} is necessarily rejecting (it has no accepting combination of colors), and can only reach a rejecting sink, so it can be replaced by a rejecting sink.
  • SCC {2,3} is the opposite: it is necessarily accepting and can only reach an accepting sink, so it can be replaced by an accepting sink.
  • States 4, 7, and 11 are transient states that can only reach the rejecting sink, so they can be replaced by the rejecting sink.
  • SCC {8,9} is always accepting, but the simple detection code based only on the colors occurring in the SCC does not detect this, so the SCC is kept.
  • States 0, 1, and 10 are kept as well, since they can reach other states or sinks.

Quantifications

Weak MTDSwAs support quantifications of atomic propositions. \ (Beware that at the time of writing, weakness is not asserted; if you use this algorithm on non-weak automata, the result will not make sense.) Existentially quantifying a variable $a$ in a weak MTDSwA representing formula $\varphi$ produces an MTDSwA recognizing $\exists a : \varphi$. Universal quantification recognizes $\forall a : \varphi$.

In [21]:
m = spot.obligation_to_mtdswa("Ga & Fb")
qm = spot.quantify_exists(m, spot.formula.ap('a'))
display_inline(m, qm, per_row=2)
mtdswa Inf( ) [Büchi] S0 Ga & Fb I->S0 B1919 a S0->B1919 S1 Ga B198 a S1->B198 B0 0 B198->B0 B34 Ga B198->B34 B1918 b B1919->B1918 B1919->B0 B1918->B34 B37 Ga & Fb B1918->B37
mtdswa Inf( ) [Büchi] S0 \exists a: (Ga & Fb) I->S0 B1918 b S0->B1918 S1 \exists a: Ga B34 \exists a: Ga S1->B34 B1918->B34 B37 \exists a: (Ga & Fb) B1918->B37

A more visual example. Here, state 1 in the right automaton corresponds to the conjunction of states {1,2} from the left one. State 2 corresponds to states {3, 4, 5, 6}:

In [22]:
t = spot.translate("(a & Xa & XXb) | (a & X!a & XXc) | (!a & Xa & XX(b | c)) | (!a & X!a & XX(b & c))", "deterministic", "complete", "SBAcc")
m = spot.dtwa_to_mtdswa(t)
m.sinks_as_constants()
qm = spot.quantify_forall(m, spot.formula.ap('a'))
display_inline(t, qm, per_row=2)
[Büchi] 0 0 I->0 1 1 0->1 !a 2 2 0->2 a 3 3 1->3 !a 4 4 1->4 a 5 5 2->5 !a 6 6 2->6 a 7 7 3->7 !b | !c 8 8 3->8 b & c 4->7 !b & !c 4->8 b | c 5->7 !c 5->8 c 6->7 !b 6->8 b 7->7 1 8->8 1
mtdswa Inf( ) [Büchi] S0 0 I->S0 B34 1 S0->B34 S1 1 B35 2 S1->B35 S2 2 B1929 c S2->B1929 B14 b B1929->B14 B0 0 B1929->B0 B14->B0 B1 1 B14->B1

During quantification, sets of states need to be combined with OR / AND before being quantified. Unfortunately, this process can create many intermediate states that later become unused. For example, in the previous automaton, states $\{3, 5\}$ and $\{4, 6\}$ are created when computing the conjunction of $1$ and $2$, but are no longer needed after the quantification of $\{1, 2\}$ which creates $\{3, 4, 5, 6 \}$.

By default, quantify_exists and quantify_forall trim these states at the end. This can be disabled by passing False as a third parameter to the function.

In [23]:
print("untrimmed")
display(spot.quantify_forall(m, spot.formula.ap('a'), False))
print("trimmed")
display(spot.quantify_forall(m, spot.formula.ap('a'), True))
untrimmed
mtdswa Inf( ) [Büchi] S0 0 I->S0 B204 3 S0->B204 S1 1 B394 6 S1->B394 S2 2 B1863 9 S2->B1863 S3 3 B1870 12 S3->B1870 S4 4 B1929 c S4->B1929 S5 5 B28 c S5->B28 S6 6 S6->B1929 S7 7 B2 c S7->B2 S8 8 B14 b S8->B14 S9 9 S9->B1929 S10 10 S10->B1929 S11 11 S11->B14 S12 12 S12->B1929 B0 0 B2->B0 B1 1 B2->B1 B28->B14 B28->B1 B1929->B14 B1929->B0 B14->B0 B14->B1
trimmed
mtdswa Inf( ) [Büchi] S0 0 I->S0 B34 1 S0->B34 S1 1 B35 2 S1->B35 S2 2 B1929 c S2->B1929 B14 b B1929->B14 B0 0 B1929->B0 B14->B0 B1 1 B14->B1

SCC computations

The scc_vector assigns each state the index of a maximal SCC ordered topologically (each SCC can only reach an SCC with a smaller index).

The computation is performed in linear time by considering the MTDSwA as an oriented graph. Passing option 's' to show() highlights non-trivial SCCs, including their states and MTBDD nodes.

In [24]:
a2 = spot.translate("GFa <-> c", "generic", "deterministic", "SBAcc")
m2 = spot.dtwa_to_mtdswa(a2)
a3 = spot.translate("Ga | Fb")
m3 = spot.dtwa_to_mtdswa(a3)
In [25]:
spot.scc_vector(m2)
Out[25]:
(2, 1, 1, 0, 0)
In [26]:
spot.scc_vector(m3)
Out[26]:
(1, 0)
In [27]:
m2.show("s")
Out[27]:
mtdswa (Inf( )&Inf( )) | (Fin( ) & Fin( )) cluster_1 cluster_0 S0 0 I->S0 B212 c S0->B212 S1 1 B202 a S1->B202 S2 2 S2->B202 S3 3 B211 a S3->B211 S4 4 S4->B211 B212->B202 B212->B211 B35 2 B202->B35 B34 1 B202->B34 B208 4 B211->B208 B204 3 B211->B204
In [28]:
m3.show("s")
Out[28]:
mtdswa Inf( ) [Büchi] cluster_1 cluster_0 S0 0 I->S0 B358 a S0->B358 S1 1 B352 b S1->B352 B358->B352 B357 b B358->B357 B1 1 B352->B1 B34 1 B352->B34 B357->B1 B37 0 B357->B37
In [29]:
a = spot.translate("(a U b) | XG(c)", "det")
m4 = spot.dtwa_to_mtdswa(a)
print(spot.scc_vector(m4))
m4.show('s')
(3, 0, 2, 1)
Out[29]:
mtdswa Inf( ) [Büchi] cluster_0 cluster_2 cluster_1 S0 0 I->S0 B2273 a S0->B2273 S1 1 B2274 c S1->B2274 S2 2 B2276 c S2->B2276 S3 3 B2275 a S3->B2275 B2276->B2275 B2276->B2273 B0 0 B2274->B0 B34 1 B2274->B34 B14 b B2275->B14 B2269 b B2275->B2269 B1917 b B2273->B1917 B352 b B2273->B352 B14->B0 B1 1 B14->B1 B1917->B1 B35 2 B1917->B35 B2269->B1 B204 3 B2269->B204 B352->B1 B352->B34
In [30]:
a = spot.translate("(a U b) & GFa & GFb & FGc", "det", "sbacc", "gen")
m5 = spot.dtwa_to_mtdswa(a)
m5.show('s')
Out[30]:
mtdswa (Inf( )&Inf( )) & Fin( ) [Streett-like 3] cluster_1 cluster_0 S0 0 I->S0 B3512 c S0->B3512 S1 1 B3528 c S1->B3528 S2 2 S2->B3528 S3 3 S3->B3528 S4 4 S4->B3528 S5 5 S5->B3528 B3527 a B3528->B3527 B34 1 B3528->B34 B3501 a B3512->B3501 B3511 a B3512->B3511 B3526 b B3527->B3526 B3520 b B3527->B3520 B1918 b B3501->B1918 B374 b B3501->B374 B3508 b B3511->B3508 B2955 b B3511->B2955 B37 0 B1918->B37 B1918->B34 B0 0 B3508->B0 B204 3 B3508->B204 B2955->B37 B35 2 B2955->B35 B391 5 B3526->B391 B3526->B204 B374->B0 B374->B34 B208 4 B3520->B208 B3520->B35

TODO: The linking above is not optimal. Since all states 1,2,3,4,5 recognize the same language, it would be better to always jump on the same state when entering this SCC. That way the two transient "b" node above false could be merged. Currently we don't care too much because those nodes were converted from an explicit representation. However, when we start building translating LTL to mtdswa directly, we can probably do better.

The false and true automata don't display very well because they have to introduce a state that points to the constant. But that should be interpreted as a single SCC. (Technically, the blue nodes at the top of the figures do not really belong to the SCCs.)

In [31]:
a = spot.translate("true", "det")
m5 = spot.dtwa_to_mtdswa(a)
print(spot.scc_vector(m5))
m5.show('s')
(0,)
Out[31]:
mtdswa t [all] S0 0 I->S0 B1 1 S0->B1

Translation from obligation formula to Deterministic Weak BA

... represented as an MTDSWA.

More details about this construction can be found in our CAV'26 paper.

The translation rules are even simpler than those for translating LTLf to MTDFA (see the CIAA'25 paper or the ltlf2dfa notebook).

Let us use $\boxed{\varphi}$ to denote a terminal labeled by LTL formula $\varphi$. Let us further assume that applying any Boolean operator $\odot\in\{\land,\lor,\leftarrow,\leftrightarrow,\oplus,...\}$ between two terminals results in a terminal labeled by the result of applying $\odot$ on the two labels, i.e., $\boxed{\alpha}\odot\boxed{\beta}=\boxed{\alpha\odot\beta}$. Using that convention, the standard apply2() operation of BDD can be extended to support Boolean operation on MTBDD labeled by LTL formulas.

Then the translation of an LTL formula to MTBDD with LTL terminals following the standard expansion rules of LTL:

\begin{align} \mathit{tr}(b) &= \text{BDD representation of $b$}& \text{for any Boolean formula $b$} \\ \mathit{tr}(\mathop{\mathsf{X}}\varphi) &= \boxed{\varphi} \\ \mathit{tr}(\lnot\varphi) &= \lnot\mathit{tr}(\varphi) \\ \mathit{tr}(\alpha \odot \beta) &= \mathit{tr}(\alpha)\odot \mathit{tr}(\beta) & \text{for any Boolean operator $\odot\in\{\land,\lor,\leftarrow,\leftrightarrow,\oplus,...\}$}\\ \mathit{tr}(\alpha \mathbin{\mathsf{U}} \beta) &= \mathit{tr}(\beta)\lor(\mathit{tr}(\alpha)\land\boxed{\alpha \mathbin{\mathsf{U}} \beta}) \\ \mathit{tr}(\alpha \mathbin{\mathsf{W}} \beta) &= \mathit{tr}(\beta)\lor(\mathit{tr}(\alpha)\land\boxed{\alpha \mathbin{\mathsf{W}} \beta}) \\ \mathit{tr}(\alpha \mathbin{\mathsf{M}} \beta) &= \mathit{tr}(\beta)\land(\mathit{tr}(\alpha)\lor\boxed{\alpha \mathbin{\mathsf{M}} \beta}) \\ \mathit{tr}(\alpha \mathbin{\mathsf{R}} \beta) &= \mathit{tr}(\beta)\land(\mathit{tr}(\alpha)\lor\boxed{\alpha \mathbin{\mathsf{R}} \beta}) \\ \mathit{tr}(\mathop{\mathsf{F}} \varphi) &= \mathit{tr}(\varphi)\lor\boxed{\mathop{\mathsf{F}}\varphi} \\ \mathit{tr}(\mathop{\mathsf{G}} \varphi) &= \mathit{tr}(\varphi)\land\boxed{\mathop{\mathsf{G}}\varphi} \\ \end{align}

The above rules for $\mathit{tr}(\varphi)$ can be used to obtain the MTBDD representing the outgoing edges of a state $\varphi$. By iterating this for all formulas that label terminals reachable from $\varphi$, we can build the structure of the automaton for $\varphi$.

For termination, each formulas that appear in terminal are assumed to be replaced by one unique representative of their propositional equivalence classes. See the ltlf2dfa notebook for details.

What remains is to decide for each state whether it is accepting. The $\lambda$ function below does that by just checking the top-level temporal operators. That states labeled by strong operators ($\mathsf{F}$, $\mathsf{U}$, $\mathsf{M}$) should not be accepting, and states labeled by weak operators ($\mathsf{G}$, $\mathsf{W}$, $\mathsf{R}$) should be accepting. Combinations of those follow the semantics of the Boolean operators.

\begin{align} \lambda(\top)&=\top\\ \lambda(\bot)&=\bot\\ \lambda(a) &= * & \text{for any atomic proposition}\\ \lambda(\lnot\varphi) &= \lnot\lambda(\varphi)\\ \lambda(\mathop{\mathsf{X}}\varphi) &= \lambda(\varphi) \\ \lambda(\alpha \odot \beta) &= \lambda(\alpha)\odot\lambda(\beta) & \text{for any Boolean operator $\odot\in\{\land,\lor,\leftarrow,\leftrightarrow,\oplus,...\}$} \\ \lambda(\alpha \mathbin{\mathsf{U}} \beta) &= \bot \\ \lambda(\alpha \mathbin{\mathsf{M}} \beta) &= \bot \\ \lambda(\mathop{\mathsf{F}}\varphi) &= \bot \\ \lambda(\alpha \mathbin{\mathsf{W}} \beta) &= \top \\ \lambda(\alpha \mathbin{\mathsf{R}} \beta) &= \top \\ \lambda(\mathop{\mathsf{G}}\varphi) &= \top \\ \end{align}

Here the value $*$ should be interpreted as a joker that can simply be ignored when being combined with other Boolean operations: \begin{align} \top\land &= \top & \bot \land &= \bot & \land &= \ \top\lor &= \top & \bot \lor &= \bot & \lor &= \ \top\rightarrow &= \bot & \bot \rightarrow &= \top & \rightarrow &= * &

  • \rightarrow \top &= \top & \rightarrow \bot &= \bot & \ \top\leftrightarrow &= \top & \bot \leftrightarrow &= \top & \leftrightarrow &= \ \top\oplus &= \top & \bot \oplus &= \top & \oplus &= * \
           &        &              &       &         \lnot * &= * 
    
    \end{align}

By construction, one can see that the only formulas $\varphi$ for which $\lambda(\varphi)=*$ must be obtained by using only atomic propositions, Boolean operators and $\mathop{\mathsf{X}}$ operators, this corresponds to formulas that are both safety and co-safety syntactically. If such a formula labels a state, that state cannot be part of any cycle, i.e., it is a transient state, and its acceptance is irrelevant. Hence the use of a joker.

For the construction, states labeled by a formula $\varphi$ such that $\lambda(\varphi)=\top$ should be tagged as accepting, states where $\lambda(\varphi)=\bot$ must be tagged as rejecting, and states for which $\lambda(\varphi)=*$ can be tagged either way without impact on the language.

In the implementation we do not actually use the above ternary logic. Instead we have $\lambda(a)=\bot$, and the Boolean operators (which are n-ary in the case of $\land$ and $\lor$) are modified to ignore the arguments that are both safety and co-safety syntactically (Spot maintains this class membership as a property bit in each formula, so this can be tested in constant time).

In [32]:
t1 = spot.obligation_to_mtdswa('a W b U c')
t1.show('s')
Out[32]:
mtdswa Inf( ) [Büchi] cluster_1 cluster_0 S0 a W (b U c) I->S0 B3533 c S0->B3533 S1 b U c B3529 c S1->B3529 B374 b B3529->B374 B1 1 B3529->B1 B3532 a B3533->B3532 B3533->B1 B3532->B374 B37 a W (b U c) B3532->B37 B0 0 B374->B0 B34 b U c B374->B34
In [33]:
t1.as_twa(True)
Out[33]:
[Büchi] 0 a W (b U c) I->0 0->0 a & !c 1 b U c 0->1 !a & b & !c 2 2 0->2 c 1->1 b & !c 1->2 c 2->2 1
In [34]:
t2 = spot.obligation_to_mtdswa('Fa & Fb & Fc')
t2.show('s')
Out[34]:
mtdswa Inf( ) [Büchi] cluster_6 cluster_2 cluster_4 cluster_0 cluster_5 cluster_1 cluster_3 S0 Fa & Fb & Fc I->S0 B3547 c S0->B3547 S1 Fa & Fc B3549 c S1->B3549 S2 Fb & Fc B3550 c S2->B3550 S3 Fc B3551 c S3->B3551 S4 Fa & Fb B3546 a S4->B3546 S5 Fa B3548 a S5->B3548 S6 Fb B3545 b S6->B3545 B3550->B3545 B1920 b B3550->B1920 B3547->B3546 B3544 a B3547->B3544 B1 1 B3551->B1 B204 Fc B3551->B204 B3549->B3548 B397 a B3549->B397 B3548->B1 B391 Fa B3548->B391 B3546->B3545 B392 b B3546->B392 B1918 b B3544->B1918 B3544->B1920 B397->B204 B34 Fa & Fc B397->B34 B3545->B1 B394 Fb B3545->B394 B1918->B34 B37 Fa & Fb & Fc B1918->B37 B1920->B204 B35 Fb & Fc B1920->B35 B208 Fa & Fb B392->B208 B392->B391
In [35]:
t2.as_twa(True)
Out[35]:
[Büchi] 0 Fa & Fb & Fc I->0 0->0 !a & !b & !c 1 Fa & Fc 0->1 !a & b & !c 2 Fb & Fc 0->2 a & !b & !c 3 Fc 0->3 a & b & !c 4 Fa & Fb 0->4 !a & !b & c 5 Fa 0->5 !a & b & c 6 Fb 0->6 a & !b & c 7 7 0->7 a & b & c 1->1 !a & !c 1->3 a & !c 1->5 !a & c 1->7 a & c 2->2 !b & !c 2->3 b & !c 2->6 !b & c 2->7 b & c 3->3 !c 3->7 c 4->4 !a & !b 4->5 !a & b 4->6 a & !b 4->7 a & b 5->5 !a 5->7 a 6->6 !b 6->7 b 7->7 1
In [36]:
t3 = spot.obligation_to_mtdswa('G(p1 <-> X!p1) | F(p0 & Xp1)')
t3.show('s')
Out[36]:
mtdswa Inf( ) [Büchi] cluster_2 cluster_1 cluster_0 S0 G(p1 <-> X!p1) | F(p0 & Xp1) I->S0 B3578 p0 S0->B3578 S1 F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) B3581 p0 S1->B3581 S2 F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) B3584 p0 S2->B3584 S3 p1 | F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) B3586 p0 S3->B3586 S4 p1 | F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) B3589 p0 S4->B3589 S5 F(p0 & Xp1) B3590 p0 S5->B3590 S6 p1 | F(p0 & Xp1) S6->B3586 B391 F(p0 & Xp1) B3590->B391 B394 p1 | F(p0 & Xp1) B3590->B394 B3582 p1 B3584->B3582 B3583 p1 B3584->B3583 B1158 p1 B3578->B1158 B3577 p1 B3578->B3577 B3588 p1 B3589->B3588 B3587 p1 B3589->B3587 B3580 p1 B3581->B3580 B3579 p1 B3581->B3579 B3571 p1 B3586->B3571 B3585 p1 B3586->B3585 B34 F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) B3582->B34 B3582->B391 B3580->B394 B208 p1 | F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) B3580->B208 B1 1 B3571->B1 B3571->B394 B3583->B394 B204 p1 | F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) B3583->B204 B3588->B1 B3588->B204 B3585->B1 B3585->B391 B3579->B391 B35 F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) B3579->B35 B1158->B34 B1158->B35 B3587->B1 B3587->B34 B3577->B208 B3577->B204
In [37]:
t3.as_twa(True).show('.s')
Out[37]:
[Büchi] cluster_0 cluster_1 cluster_2 cluster_3 cluster_4 0 G(p1 <-> X!p1) | F(p0 & Xp1) I->0 3 p1 | F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) 0->3 p0 & !p1 1 F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) 0->1 !p0 & !p1 2 F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) 0->2 !p0 & p1 4 p1 | F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) 0->4 p0 & p1 7 7 7->7 1 5 F(p0 & Xp1) 5->5 !p0 6 p1 | F(p0 & Xp1) 5->6 p0 6->7 p1 6->5 !p0 & !p1 6->6 p0 & !p1 3->7 p1 3->5 !p0 & !p1 3->6 p0 & !p1 1->5 !p0 & !p1 1->6 p0 & !p1 1->2 !p0 & p1 1->4 p0 & p1 2->5 !p0 & p1 2->6 p0 & p1 2->3 p0 & !p1 2->1 !p0 & !p1 4->7 p1 4->3 p0 & !p1 4->1 !p0 & !p1

Here is a list of obligation formulas to check that the new translation is equivalent to the old one.

In [38]:
obligations = ['Gp',
               'Fr -> (!p U r)',
               '(!r U (p & !r)) | G!r',
               'Fr -> ((!p & !r) U (r | ((p & !r) U (r | ((!p & !r) U (r | ((p & !r) U (r | (!p U r)))))))))',
               'Fr -> (p U r)',
               'G(q -> Gp)',
               'Fr -> (!p U (r | s))',
               'Fr -> ((p -> (!r U (!r & s))) U r)',
               'Fp -> (!p U (!p & s & X(!p U t)))',
               'Fr -> (!p U (r | (!p & s & X(!p U t))))',
               'F(s & XFt) -> (!s U p)',
               'Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r))',
               'Fr -> ((p -> (!r U (!r & s & X(!r U t)))) U r)',
               'Fr -> ((p -> (!r U (!r & s & !z & X((!r & !z) U t)))) U r)',
               'Fp',
               'G!q | F(q & Fp)',
               '(!p U s) | Gp',
               'Fr -> (((s & X(!r U t)) -> X(!r U (t & Fp))) U r)',
               'G(p1 <-> X!p1) | F(p0 & Xp1)',
               'Ga W Gb', # <- can trigger an infinite loop if propositional equivalence is not done
               ]
for obl in obligations:
    t1 = spot.obligation_to_mtdswa(obl).as_twa(True)    # non-minimized, via MTBDDs
    t2 = spot.translate(obl, 'deterministic', xargs='new-oblig=0') # minimized via historical/explicit construction 
    print(t1.num_states(), t2.num_states(), spot.are_equivalent(t1, t2), obl)
1 1 True Gp
3 3 True Fr -> (!p U r)
2 2 True (!r U (p & !r)) | G!r
7 7 True Fr -> ((!p & !r) U (r | ((p & !r) U (r | ((!p & !r) U (r | ((p & !r) U (r | (!p U r)))))))))
3 3 True Fr -> (p U r)
2 2 True G(q -> Gp)
3 3 True Fr -> (!p U (r | s))
3 3 True Fr -> ((p -> (!r U (!r & s))) U r)
3 3 True Fp -> (!p U (!p & s & X(!p U t)))
4 4 True Fr -> (!p U (r | (!p & s & X(!p U t))))
3 3 True F(s & XFt) -> (!s U p)
5 4 True Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r))
4 4 True Fr -> ((p -> (!r U (!r & s & X(!r U t)))) U r)
4 4 True Fr -> ((p -> (!r U (!r & s & !z & X((!r & !z) U t)))) U r)
2 2 True Fp
3 3 True G!q | F(q & Fp)
4 4 True (!p U s) | Gp
6 6 True Fr -> (((s & X(!r U t)) -> X(!r U (t & Fp))) U r)
8 7 True G(p1 <-> X!p1) | F(p0 & Xp1)
3 3 True Ga W Gb

Let's look at one case where the new translation produces one extra state compared to the reference implementation (which is already minimized).

In [39]:
f = spot.formula('Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r))')
In [40]:
a = spot.obligation_to_mtdswa(f)
a.show('s')
Out[40]:
mtdswa Inf( ) [Büchi] cluster_3 cluster_2 cluster_0 cluster_1 S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) I->S0 B6675 r S0->B6675 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B6677 r S1->B6677 S2 !Fr B3594 r S2->B3594 S3 Fr -> !(!r U (!r & t)) B6678 r S3->B6678 B0 0 B3594->B0 B35 !Fr B3594->B35 B6674 s B6675->B6674 B1 1 B6675->B1 B6676 p B6677->B6676 B6677->B1 B4346 t B6678->B4346 B6678->B1 B6660 p B6674->B6660 B5440 p B6674->B5440 B6676->B4346 B4344 t B6676->B4344 B6660->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) B6660->B37 B5440->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B5440->B34 B204 Fr -> !(!r U (!r & t)) B4346->B204 B4346->B35 B4344->B34 B4344->B35
In [41]:
b1 = a.as_twa(True, False)
b1
Out[41]:
[Büchi] 0 0 I->0 0->0 !p & !r & !s 4 4 0->4 p | r 1 1 0->1 !p & !r & s 4->4 1 1->4 r 1->1 !p & !r & !t 2 2 1->2 !r & t 3 3 1->3 p & !r & !t 2->2 !r 3->4 r 3->2 !r & t 3->3 !r & !t

State 1 and 3 can be merged. Here is the minimized output from the historical translation. (If you see new-oblig=0 appearing in the calls to translate() in this notebook, it is because translate() was also changed to use the new MTBDD-based obligation translation presented later in this notebook. Passing new-oblig=0 forces Spot to use the old non-MTBDD-based obligation translation.)

In [42]:
b2 = f.translate('deterministic', xargs='new-oblig=0')
b2
Out[42]:
t [all] 2 2 I->2 2->2 !p & !r & !s 1 1 2->1 p | r 3 3 2->3 !p & !r & s 0 0 0->0 !r 1->1 1 3->0 !r & t 3->1 r 3->3 !r & !t
In [43]:
spot.are_equivalent(b1, b2)
Out[43]:
True

Moore's Minimization

Moore's DFA minimization algorithm is easy to implement on MTBDD-based ω-automata, just using the color of the states for the initial partition. However, despite its name, it does not guarantee a minimal automaton when applied to ω-automata. (There is a way to fix that for weak deterministic ω-automata, as we will see later.) Moore's minimization algorithm is quadratic (unlike Hopcroft's which does it in $O(n \log n)$), but it is very suited to MTBDD-based representation because to perform one iteration of the algorithm, we just have to replace each terminal by its current class number, and after rewriting the MTBDDs states the new partition can be read by looking which state have the same MTBDD representation.

The minimization currently forces the bddtrue and bddfalse terminals in separate classes. So, for instance, if you build a MTDSWA that contains a bddtrue terminal, and another accepting state that loops over itself with label "true", these two states won't be merged. The minimization for mtdfa used to deal with this case, but that made the code more complex and harder to maintain. In practice, the bddtrue and bddfalse terminals are only useful during the recursive translation, where they help shortcut BDD operations, but they are more annoying to deal with afterwards. Hence this implementation of the minimization does not make any effort regarding those.

In a few sections we will show how to change between constant-based (bddtrue, bddfalse), or state-based representations of sinks. Therefore, for precise minimization, one should call a.sinks_as_states() before spot.minimize_mtdswa(a).

In the meantime, we can still use this algorithm to merge the two equivalent states in automaton a:

In [44]:
am = spot.minimize_mtdswa(a)
display("before:", a, "after:", am)
'before:'
mtdswa Inf( ) [Büchi] S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) I->S0 B6675 r S0->B6675 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B6677 r S1->B6677 S2 !Fr B3594 r S2->B3594 S3 Fr -> !(!r U (!r & t)) B6678 r S3->B6678 B0 0 B3594->B0 B35 !Fr B3594->B35 B6674 s B6675->B6674 B1 1 B6675->B1 B6676 p B6677->B6676 B6677->B1 B4346 t B6678->B4346 B6678->B1 B6660 p B6674->B6660 B5440 p B6674->B5440 B6676->B4346 B4344 t B6676->B4344 B6660->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) B6660->B37 B5440->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B5440->B34 B204 Fr -> !(!r U (!r & t)) B4346->B204 B4346->B35 B4344->B34 B4344->B35
'after:'
mtdswa Inf( ) [Büchi] S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) I->S0 B6675 r S0->B6675 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B6819 r S1->B6819 S2 !Fr B3594 r S2->B3594 B0 0 B3594->B0 B35 !Fr B3594->B35 B4344 t B6819->B4344 B1 1 B6819->B1 B6674 s B6675->B6674 B6675->B1 B6660 p B6674->B6660 B5440 p B6674->B5440 B6660->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) B6660->B37 B5440->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B5440->B34 B4344->B35 B4344->B34
In [45]:
spot.are_equivalent(a.as_twa(), am.as_twa())
Out[45]:
True

Löding's preprocessing for weak DBAs

Let's look at a simple example to understand the problem why DFA minimization does not work immediately on weak deterministic ω-automata.

In [46]:
t4 = spot.obligation_to_mtdswa('a | Ga | F(b & Xa)')
t4.show('s')
Out[46]:
mtdswa Inf( ) [Büchi] cluster_0 S0 a | Ga | F(b & Xa) I->S0 B396 a S0->B396 S1 F(b & Xa) B388 b S1->B388 S2 a | F(b & Xa) S2->B396 B396->B388 B1 1 B396->B1 B34 F(b & Xa) B388->B34 B35 a | F(b & Xa) B388->B35

On this example, we can see that the initial state is labeled by a | Ga | F(b & Xa). Because this disjunction contains the safety formula Ga, the state is marked as accepting. However, this formula is equivalent to a | F(b & Xa), which would be rejecting. The difference in acceptance between those two states does not threaten to the validity of the translation: when the formula a | Ga | F(b & Xa) is translated into MTBDD, the superfluous Ga naturally disappears, so a | Ga | F(b & Xa) only occurs as a transient SCC, and its acceptance has no impact on the language of the automaton. However, the difference in acceptance prevents those two states from being merged by the DFA minimization algorithm.

In [47]:
spot.minimize_mtdswa(t4).show('s')
Out[47]:
mtdswa Inf( ) [Büchi] cluster_0 S0 a | Ga | F(b & Xa) I->S0 B396 a S0->B396 S1 F(b & Xa) B388 b S1->B388 S2 a | F(b & Xa) S2->B396 B396->B388 B1 1 B396->B1 B34 F(b & Xa) B388->B34 B35 a | F(b & Xa) B388->B35

An easy fix here would be to detect that a | Ga | F(b & Xa) and a | F(b & Xa) have the same MTBDD encoding, and because a | Ga | F(b & Xa) is a transient state, decide to replace it by the other state.

Löding's preprocessing00183-6) is a more general solution that decides for each transient state of a weak deterministic ω-automata whether it should be accepting or rejecting, in such a way that applying DFA minimization on the preprocessed ω-automata will ensure minimality of the result. Spot implements a variant of this preprocessing that assigns each SCC a rank such that:

  • SCCs are accepting iff they have odd rank
  • ranks can only decrease along a run of the automaton
  • bddfalse and bddtrue implicitly have rank 0 and 1
  • the rank assigned to each SCC is the minimal one that could be assigned given the above constraint.

The way to compute these ranks is quite straightforward and can be done in linear time: compute an SCC decomposition, then process it bottom-up, assigning 0 or 1 to the bottommost SCC depending on its acceptance. Moving upward, the rank of a transient state is the maximum rank of its successors. If an SCC is not transient, that computed maximum may have to be incremented by one to match the acceptance of the current SCC.

Even if the rank is SCC-based, the return value of loding_weak_ranking is an array giving the rank of each state.

In [48]:
spot.loding_weak_ranking(t4)
Out[48]:
(2, 2, 2)

In this case all states have rank 2, so they should be rejecting. bddtrue has rank 1 implicitly, but this is not indicated in the return.

We can also ask loding_weak_ranking to fix the acceptance of the transient states in the provided automaton:

In [49]:
spot.loding_weak_ranking(t4, True)
Out[49]:
(2, 2, 2)
In [50]:
t4
Out[50]:
mtdswa Inf( ) [Büchi] S0 a | Ga | F(b & Xa) I->S0 B396 a S0->B396 S1 F(b & Xa) B388 b S1->B388 S2 a | F(b & Xa) S2->B396 B396->B388 B1 1 B396->B1 B34 F(b & Xa) B388->B34 B35 a | F(b & Xa) B388->B35

And now we can minimize this like a DFA:

In [51]:
spot.minimize_mtdswa(t4)
Out[51]:
mtdswa Inf( ) [Büchi] S0 a | Ga | F(b & Xa) I->S0 B6822 a S0->B6822 S1 F(b & Xa) B378 b S1->B378 B6822->B378 B1 1 B6822->B1 B34 F(b & Xa) B378->B34 B37 a | Ga | F(b & Xa) B378->B37

Here is a larger example:

In [52]:
t5 = spot.obligation_to_mtdswa('XXFa & ((b & Fc) | XGa)')
display(t5.as_twa(True, False), t5.show('0'))
[Büchi] 0 0 I->0 1 1 0->1 !b 2 2 0->2 b & !c 3 3 0->3 b & c 4 4 1->4 a 5 5 2->5 !a & !c 6 6 2->6 a & !c 7 7 2->7 c 3->7 1 8 8 4->8 a 5->5 !a & !c 5->7 !a & c 9 9 5->9 a & !c 11 11 5->11 a & c 6->5 !a & !c 6->7 !a & c 6->11 a & c 10 10 6->10 a & !c 7->7 !a 7->11 a 8->8 a 9->9 !c 9->11 c 11->11 1 10->9 !a & !c 10->11 c 10->10 a & !c
mtdswa Inf( ) [Büchi] S0 0 I->S0 B6838 c S0->B6838 S1 1 B1850 a S1->B1850 S2 2 B6839 c S2->B6839 S3 3 B1860 7 S3->B1860 S4 4 B1901 a S4->B1901 S5 5 B6841 c S5->B6841 S6 6 B6843 c S6->B6843 S7 7 B6832 a S7->B6832 S8 8 S8->B1901 S9 9 B6844 c S9->B6844 S10 10 B6845 c S10->B6845 B6842 a B6843->B6842 B6843->B6832 B2000 a B6839->B2000 B6839->B1860 B388 b B6838->B388 B6837 b B6838->B6837 B6840 a B6841->B6840 B6841->B6832 B6829 a B6845->B6829 B1 1 B6845->B1 B6844->B1 B1863 9 B6844->B1863 B0 0 B1850->B0 B208 4 B1850->B208 B391 5 B2000->B391 B394 6 B2000->B394 B6842->B391 B1865 10 B6842->B1865 B1901->B0 B1861 8 B1901->B1861 B6840->B391 B6840->B1863 B6829->B1863 B6829->B1865 B6832->B1 B6832->B1860 B34 1 B388->B34 B35 2 B388->B35 B204 3 B6837->B204 B6837->B34

What is visible in the output above is that states 1, 4, and 8, could be merged after changing the acceptance of 1 and 4. Without that, Moore's algorithm will not reduce anything:

In [53]:
print(t5.num_roots(), "roots, minimized to", spot.minimize_mtdswa(t5).num_roots(), "roots.")
11 roots, minimized to 11 roots.
In [54]:
t5rank = spot.loding_weak_ranking(t5, True); t5rank
Out[54]:
(3, 1, 3, 2, 1, 2, 3, 2, 1, 2, 3)
In [55]:
t5.show("0")
Out[55]:
mtdswa Inf( ) [Büchi] S0 0 I->S0 B6838 c S0->B6838 S1 1 B1850 a S1->B1850 S2 2 B6839 c S2->B6839 S3 3 B1860 7 S3->B1860 S4 4 B1901 a S4->B1901 S5 5 B6841 c S5->B6841 S6 6 B6843 c S6->B6843 S7 7 B6832 a S7->B6832 S8 8 S8->B1901 S9 9 B6844 c S9->B6844 S10 10 B6845 c S10->B6845 B6842 a B6843->B6842 B6843->B6832 B2000 a B6839->B2000 B6839->B1860 B388 b B6838->B388 B6837 b B6838->B6837 B6840 a B6841->B6840 B6841->B6832 B6829 a B6845->B6829 B1 1 B6845->B1 B6844->B1 B1863 9 B6844->B1863 B0 0 B1850->B0 B208 4 B1850->B208 B391 5 B2000->B391 B394 6 B2000->B394 B6842->B391 B1865 10 B6842->B1865 B1901->B0 B1861 8 B1901->B1861 B6840->B391 B6840->B1863 B6829->B1863 B6829->B1865 B6832->B1 B6832->B1860 B34 1 B388->B34 B35 2 B388->B35 B204 3 B6837->B204 B6837->B34
In [56]:
t5m = spot.minimize_mtdswa(t5)
print(t5.num_roots(), "roots, minimized to", t5m.num_roots(), "roots.")
display(t5m.show('0'), t5m.as_twa(True, False))
11 roots, minimized to 9 roots.
mtdswa Inf( ) [Büchi] S0 0 I->S0 B6838 c S0->B6838 S1 1 B198 a S1->B198 S2 2 B6865 c S2->B6865 S3 3 B394 6 S3->B394 S4 4 B6867 c S4->B6867 S5 5 B6869 c S5->B6869 S6 6 B6866 a S6->B6866 S7 7 B2022 c S7->B2022 S8 8 B6857 c S8->B6857 B6869->B6866 B6868 a B6869->B6868 B6864 a B6865->B6864 B6865->B394 B388 b B6838->B388 B6837 b B6838->B6837 B6867->B6866 B1914 a B6867->B1914 B6856 a B6857->B6856 B1 1 B6857->B1 B2022->B1 B1860 7 B2022->B1860 B6866->B1 B6866->B394 B1861 8 B6868->B1861 B208 4 B6868->B208 B0 0 B198->B0 B34 1 B198->B34 B1914->B1860 B1914->B208 B6856->B1860 B6856->B1861 B6864->B208 B391 5 B6864->B391 B35 2 B388->B35 B388->B34 B204 3 B6837->B204 B6837->B34
[Büchi] 0 0 I->0 1 1 0->1 !b 2 2 0->2 b & !c 3 3 0->3 b & c 1->1 a 4 4 2->4 !a & !c 5 5 2->5 a & !c 6 6 2->6 c 3->6 1 4->4 !a & !c 4->6 !a & c 7 7 4->7 a & !c 9 9 4->9 a & c 5->4 !a & !c 5->6 !a & c 5->9 a & c 8 8 5->8 a & !c 6->6 !a 6->9 a 7->7 !c 7->9 c 9->9 1 8->7 !a & !c 8->9 c 8->8 a & !c

While this is not mentioned in Löding's paper, the ranking computed by loding_weak_ranking can be used to speed up Moore's minimization by interpreting it as the initial partition of the states. Since the ranking is related both to the acceptance of the state and to the number of alternations between accepting and rejecting SCCs that can be reached from there, two states can only be merged if they have the same rank.

In [57]:
t5m = spot.minimize_mtdswa(t5, t5rank)  # <- initial partition given as second argument.
print(t5.num_roots(), "roots, minimized to", t5m.num_roots(), "roots.")
# The result is the same as above.
11 roots, minimized to 9 roots.

Converting sinks to states or constants

Earlier we said that the implemented minimization does not work well if accepting and rejecting sinks are represented as constants, because the minimization only focuses on merging states. The sinks_as_states() and states_as_sinks() rewrites the MTBDDs to change that representation.

In [58]:
display("before", am)
am.sinks_as_states(); 
display("after", am)
'before'
mtdswa Inf( ) [Büchi] S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) I->S0 B6675 r S0->B6675 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B6819 r S1->B6819 S2 !Fr B3594 r S2->B3594 B0 0 B3594->B0 B35 !Fr B3594->B35 B4344 t B6819->B4344 B1 1 B6819->B1 B6674 s B6675->B6674 B6675->B1 B6660 p B6674->B6660 B5440 p B6674->B5440 B6660->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) B6660->B37 B5440->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B5440->B34 B4344->B35 B4344->B34
'after'
mtdswa Inf( ) [Büchi] S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) I->S0 B6880 r S0->B6880 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B6881 r S1->B6881 S2 !Fr B6882 r S2->B6882 S3 1 B204 1 S3->B204 S4 0 B208 0 S4->B208 B6882->B208 B35 !Fr B6882->B35 B6879 s B6880->B6879 B6880->B204 B4344 t B6881->B4344 B6881->B204 B6877 p B6879->B6877 B6878 p B6879->B6878 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) B6877->B37 B6877->B204 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B6878->B34 B6878->B204 B4344->B34 B4344->B35
In [59]:
am.sinks_as_constants(); am.show('s')
Out[59]:
mtdswa Inf( ) [Büchi] cluster_2 cluster_1 cluster_0 S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) I->S0 B6675 r S0->B6675 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B6819 r S1->B6819 S2 !Fr B3594 r S2->B3594 B0 0 B3594->B0 B35 !Fr B3594->B35 B4344 t B6819->B4344 B1 1 B6819->B1 B6674 s B6675->B6674 B6675->B1 B6660 p B6674->B6660 B5440 p B6674->B5440 B6660->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) B6660->B37 B5440->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) B5440->B34 B4344->B35 B4344->B34

Replacing constants with states may require changing the acceptance condition...

In [60]:
a1 = spot.dtwa_to_mtdswa(spot.translate('Ga', 'det', xargs="new-oblig=0")); a1
Out[60]:
mtdswa t [all] S0 0 I->S0 B380 a S0->B380 B0 0 B380->B0 B37 0 B380->B37
In [61]:
a1.sinks_as_states(); a1
Out[61]:
mtdswa Inf( ) [Büchi] S0 0 I->S0 B382 a S0->B382 S1 1 B34 1 S1->B34 B382->B34 B37 0 B382->B37
In [62]:
# Coming back to constants do not change the acceptance condition back
a1.sinks_as_constants(); a1
Out[62]:
mtdswa Inf( ) [Büchi] S0 0 I->S0 B380 a S0->B380 B0 0 B380->B0 B37 0 B380->B37
In [63]:
a2 = spot.obligation_to_mtdswa('Fa'); a2
Out[63]:
mtdswa Inf( ) [Büchi] S0 Fa I->S0 B6821 a S0->B6821 B1 1 B6821->B1 B37 Fa B6821->B37
In [64]:
a2.sinks_as_states(); a2
Out[64]:
mtdswa Inf( ) [Büchi] S0 Fa I->S0 B6847 a S0->B6847 S1 1 B34 1 S1->B34 B6847->B34 B37 Fa B6847->B37
In [65]:
# The optional argument asks to uses constants, but without removing the original sink states.
a2.sinks_as_constants(True); a2
Out[65]:
mtdswa Inf( ) [Büchi] S0 Fa I->S0 B6821 a S0->B6821 S1 1 B1 1 S1->B1 B6821->B1 B37 Fa B6821->B37
In [66]:
a2.sinks_as_constants(); a2
Out[66]:
mtdswa Inf( ) [Büchi] S0 Fa I->S0 B6821 a S0->B6821 B1 1 B6821->B1 B37 Fa B6821->B37

Here is a case where the minimization fails because the sinks are constants instead of states. (Using loding_weak_ranking would not help here, as there are no transient states.)

In [67]:
o1 = spot.obligation_to_mtdswa('Ga | F!a')
o1m = spot.minimize_mtdswa(o1)
display_inline(o1, o1m)
mtdswa Inf( ) [Büchi] S0 Ga | F!a I->S0 B6883 a S0->B6883 B1 1 B6883->B1 B37 Ga | F!a B6883->B37
mtdswa Inf( ) [Büchi] S0 Ga | F!a I->S0 B6883 a S0->B6883 B1 1 B6883->B1 B37 Ga | F!a B6883->B37
In [68]:
# Switch to sinks to minimize
o1.sinks_as_states()
o1m = spot.minimize_mtdswa(o1)
display_inline(o1, o1m)
# Then back to constants if desired
o1.sinks_as_constants()
o1m.sinks_as_constants()
display_inline(o1, o1m)
mtdswa Inf( ) [Büchi] S0 Ga | F!a I->S0 B382 a S0->B382 S1 1 B34 1 S1->B34 B382->B34 B37 Ga | F!a B382->B37
mtdswa Inf( ) [Büchi] S0 Ga | F!a I->S0 B37 Ga | F!a S0->B37
mtdswa Inf( ) [Büchi] S0 Ga | F!a I->S0 B6883 a S0->B6883 B1 1 B6883->B1 B37 Ga | F!a B6883->B37
mtdswa Inf( ) [Büchi] S0 Ga | F!a I->S0 B1 1 S0->B1

Comparing obligation->minimal WDBA pipelines

New version of the pipeline, using the data structures and algorithms developed above:

  • translate the obligation to MTDSWA
  • convert sinks to states
  • compute Löding ranking and fix the acceptance of the transient states
  • minimize using the ranking as initial partition.
In [69]:
def oblig2minwdba_mtdswa(f):
    aut = spot.obligation_to_mtdswa(f)
    aut.sinks_as_states()
    ranks = spot.loding_weak_ranking(aut, True)
    aut = spot.minimize_mtdswa(aut, ranks)
    return aut
In [70]:
oblig2minwdba_mtdswa('Ga & X(Fb) | F(d & X!a)')
Out[70]:
mtdswa Inf( ) [Büchi] S0 F(d & X!a) | (Ga & XFb) I->S0 B6904 a S0->B6904 S1 F(d & X!a) B6902 d S1->B6902 S2 !a | F(d & X!a) B6912 a S2->B6912 S3 F(d & X!a) | (Ga & Fb) B6908 a S3->B6908 S4 !a | F(d & X!a) | (Ga & Fb) B6913 a S4->B6913 S5 Ga | F(d & X!a) B6910 a S5->B6910 S6 !a | Ga | F(d & X!a) B6914 a S6->B6914 S7 1 B1860 1 S7->B1860 B6906 d B6914->B6906 B6914->B1860 B6912->B6902 B6912->B1860 B6904->B6902 B6903 d B6904->B6903 B6910->B6906 B6910->B6902 B6907 b B6913->B6907 B6913->B1860 B6908->B6907 B6908->B6902 B6907->B6906 B6907->B6903 B391 Ga | F(d & X!a) B6906->B391 B394 !a | Ga | F(d & X!a) B6906->B394 B35 !a | F(d & X!a) B6902->B35 B34 F(d & X!a) B6902->B34 B204 F(d & X!a) | (Ga & Fb) B6903->B204 B208 !a | F(d & X!a) | (Ga & Fb) B6903->B208

The old pipeline does a bit more:

  • cheap simplification of the LTL formula
  • convert to non-deterministic TGBA using a single color (different automaton representation, different translation algorithm)
  • determinization by the powerset construction (using a product with the original automaton to recover accepting states)
  • fix color of transient states using Löding's algorithm (different implementation)
  • Moore minimization (different implementation, and not using the ranking as initial partition)

Keep in mind that the two pipelines construct equivalent automata with different representations.

In [71]:
def oblig2minwdba_old(f):
    return spot.translate(f, 'det', 'complete', 'low',
                          # Disable some specific preprocessings for fairer comparisons.
                          # Eventually, the new translation will benefit from those as well.
                          xargs='relabel-bool=0,relabel-overlap=0,ltl-split=0,new-oblig=0')
In [72]:
oblig2minwdba_old('Ga & X(Fb) | F(d & X!a)')
Out[72]:
[Büchi] 7 7 I->7 1 1 7->1 !a & d 6 6 7->6 !a & !d 2 2 7->2 a & !d 3 3 7->3 a & d 0 0 0->0 a & !d 0->1 !a & d 5 5 0->5 a & d 0->6 !a & !d 1->1 a & d 1->6 a & !d 4 4 1->4 !a 5->0 a & !d 5->5 a & d 5->4 !a 6->1 d 6->6 !d 4->4 1 2->0 a & b & !d 2->1 !a & d 2->5 a & b & d 2->6 !a & !d 2->2 a & !b & !d 2->3 a & !b & d 3->0 a & b & !d 3->5 a & b & d 3->4 !a 3->2 a & !b & !d 3->3 a & !b & d

The new-oblig=1 option, which is actually the default, allows translate() to return a TwA that has been translated using the MTDSWA-based construction. The minimized MTDSWA is then converted into the TwA class. So the overhead of oblig2minwdba_new() over oblig2minwdba_mtdswa() is just the translation to MTDSWA.

In [73]:
def oblig2minwdba_new(f):
    return spot.translate(f, 'det', 'complete', 'low',
                          # Disable some specific preprocessings for fairer comparisons.
                          # Eventually, the new translation will benefit from those as well.
                          xargs='relabel-bool=0,relabel-overlap=0,ltl-split=0,new-oblig=1')
In [74]:
oblig2minwdba_new('Ga & X(Fb) | F(d & X!a)')
Out[74]:
[Büchi] 0 0 I->0 1 1 0->1 !a & !d 2 2 0->2 !a & d 3 3 0->3 a & !d 4 4 0->4 a & d 1->1 !d 1->2 d 2->1 a & !d 2->2 a & d 7 7 2->7 !a 3->1 !a & !d 3->2 !a & d 3->3 a & !b & !d 3->4 a & !b & d 5 5 3->5 a & b & !d 6 6 3->6 a & b & d 4->3 a & !b & !d 4->4 a & !b & d 4->7 !a 4->5 a & b & !d 4->6 a & b & d 7->7 1 5->1 !a & !d 5->2 !a & d 5->5 a & !d 5->6 a & d 6->7 !a 6->5 a & !d 6->6 a & d

⚠️ BIG WARNING ⚠️ The comparison below cannot be taken very seriously because all translations share the same BDD cache. Thus, when testing scalable patterns, some BDD operations are very likely to be cached between patterns. A more serious benchmark (still showing trends comparable to those seen here) is included in a submitted paper.

In [75]:
from timeit import default_timer as timer

def compare(f):
    f = spot.formula(f) # parse it once
    f2 = str(f)
    t1 = timer()
    a_new = oblig2minwdba_mtdswa(f)
    t2 = timer()
    a_new2 = oblig2minwdba_new(f)
    t3 = timer()
    a_old = oblig2minwdba_old(f)
    t4 = timer()
    if len(f2) > 60: f2 = f2[:59] + "…"
    n_new = a_new.num_states()
    n_new2 = a_new2.num_states()
    n_old = a_old.num_states()
    print(f"{t2-t1:6.3f}s {t3-t2:6.3f}s {t4-t3:6.3f}s  {n_new:6}   {f2}")
    assert n_old == n_new 
    assert n_new == n_new2
    if n_old < 2000: # Don't verify the automata big automata to save time.
        assert spot.are_equivalent(a_new.as_twa(True), a_old)
        assert spot.are_equivalent(a_new2, a_old)
In [76]:
import spot.gen as gen

print("-------- time -------- |        |")
print("   new     new     old |        |")
print("(MTDSWA)  (TwA)   (TwA)| states | formula")
for pat in ((gen.LTL_AND_F, 6, 10), 
            (gen.LTL_CCJ_ALPHA, 4, 7),
            (gen.LTL_CCJ_BETA, 16, 20),
            (gen.LTL_R_LEFT, 9, 12),
            (gen.LTL_R_RIGHT, 11, 15),
            (gen.LTL_U_LEFT, 6, 9),
            (gen.LTL_U_RIGHT, 10, 13),
            (gen.LTL_TV_F1, 5, 9),
            (gen.LTL_TV_G1, 5, 9),
            (gen.LTL_TV_F2, 5, 9),
            (gen.LTL_TV_G2, 5, 9),
            (gen.LTLF_CHOMP_MEALY, 1, 1),
            (gen.LTLF_CHOMP_MEALY, 2, 2),
            (gen.LTLF_CHOMP_MEALY, 3, 2),  # 3x3 is too big for 32-bit architectures
            gen.LTL_DAC_PATTERNS,
            (gen.LTL_KR_N_DELTA1, 1, 3),
            (gen.LTL_KR_NLOGN_DELTA1, 1, 3),
            ):
    if type(pat) is tuple:
        name=f"{gen.ltl_pattern_name(pat[0])} {pat[1]}{'…' if pat[1] < pat[2] else ','}{pat[2]}"
    else:
        name=f"{gen.ltl_pattern_name(pat)}"
    print(f"                               -- {name}")
    for f in gen.ltl_patterns(pat):
        if f.is_syntactic_obligation(): # to filter the DAC patterns
            compare(f)
-------- time -------- |        |
   new     new     old |        |
(MTDSWA)  (TwA)   (TwA)| states | formula
                               -- and-f 6…10
 0.000s  0.000s  0.007s      64   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6
 0.001s  0.001s  0.016s     128   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6 & Fp7
 0.001s  0.002s  0.072s     256   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6 & Fp7 & Fp8
 0.003s  0.005s  0.374s     512   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6 & Fp7 & Fp8 & Fp9
 0.009s  0.019s  4.863s    1024   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6 & Fp7 & Fp8 & Fp9 & Fp10
                               -- ccj-alpha 4…7
 0.000s  0.000s  0.006s      25   F(p1 & F(p2 & F(p3 & Fp4))) & F(q1 & F(q2 & F(q3 & Fq4)))
 0.000s  0.000s  0.039s      36   F(p1 & F(p2 & F(p3 & F(p4 & Fp5)))) & F(q1 & F(q2 & F(q3 & …
 0.000s  0.000s  0.222s      49   F(p1 & F(p2 & F(p3 & F(p4 & F(p5 & Fp6))))) & F(q1 & F(q2 &…
 0.001s  0.001s  1.612s      64   F(p1 & F(p2 & F(p3 & F(p4 & F(p5 & F(p6 & Fp7)))))) & F(q1 …
                               -- ccj-beta 16…20
 0.002s  0.001s  0.009s     289   F(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p &…
 0.002s  0.001s  0.011s     324   F(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p &…
 0.002s  0.002s  0.014s     361   F(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p &…
 0.002s  0.002s  0.017s     400   F(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p &…
 0.003s  0.002s  0.021s     441   F(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p &…
                               -- r-left 9…12
 0.003s  0.013s  0.047s     257   (((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p9
 0.007s  0.058s  0.186s     513   ((((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p9) …
 0.022s  0.228s  0.921s    1025   (((((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p9)…
 0.049s  1.270s  4.813s    2049   ((((((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p9…
                               -- r-right 11…15
 0.005s  0.003s  0.034s      12   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
 0.005s  0.004s  0.060s      13   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
 0.013s  0.011s  0.159s      14   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
 0.028s  0.024s  0.374s      15   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
 0.126s  0.081s  0.841s      16   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
                               -- u-left 6…9
 0.000s  0.001s  0.007s      33   ((((p1 U p2) U p3) U p4) U p5) U p6
 0.001s  0.001s  0.119s      65   (((((p1 U p2) U p3) U p4) U p5) U p6) U p7
 0.001s  0.004s  0.262s     129   ((((((p1 U p2) U p3) U p4) U p5) U p6) U p7) U p8
 0.004s  0.014s  2.247s     257   (((((((p1 U p2) U p3) U p4) U p5) U p6) U p7) U p8) U p9
                               -- u-right 10…13
 0.002s  0.001s  0.054s      11   p1 U (p2 U (p3 U (p4 U (p5 U (p6 U (p7 U (p8 U (p9 U p10)))…
 0.004s  0.002s  0.278s      12   p1 U (p2 U (p3 U (p4 U (p5 U (p6 U (p7 U (p8 U (p9 U (p10 U…
 0.007s  0.005s  1.267s      13   p1 U (p2 U (p3 U (p4 U (p5 U (p6 U (p7 U (p8 U (p9 U (p10 U…
 0.013s  0.012s  6.153s      14   p1 U (p2 U (p3 U (p4 U (p5 U (p6 U (p7 U (p8 U (p9 U (p10 U…
                               -- tv-f1 5…9
 0.000s  0.000s  0.001s       6   G(p -> (q | Xq | XXq | XXXq | XXXXq))
 0.000s  0.000s  0.002s       7   G(p -> (q | Xq | XXq | XXXq | XXXXq | XXXXXq))
 0.000s  0.000s  0.006s       8   G(p -> (q | Xq | XXq | XXXq | XXXXq | XXXXXq | XXXXXXq))
 0.000s  0.000s  0.025s       9   G(p -> (q | Xq | XXq | XXXq | XXXXq | XXXXXq | XXXXXXq | XX…
 0.000s  0.000s  0.137s      10   G(p -> (q | Xq | XXq | XXXq | XXXXq | XXXXXq | XXXXXXq | XX…
                               -- tv-g1 5…9
 0.000s  0.000s  0.000s       6   G(p -> (q & Xq & XXq & XXXq & XXXXq))
 0.000s  0.000s  0.000s       7   G(p -> (q & Xq & XXq & XXXq & XXXXq & XXXXXq))
 0.000s  0.000s  0.001s       8   G(p -> (q & Xq & XXq & XXXq & XXXXq & XXXXXq & XXXXXXq))
 0.000s  0.000s  0.001s       9   G(p -> (q & Xq & XXq & XXXq & XXXXq & XXXXXq & XXXXXXq & XX…
 0.000s  0.000s  0.002s      10   G(p -> (q & Xq & XXq & XXXq & XXXXq & XXXXXq & XXXXXXq & XX…
                               -- tv-f2 5…9
 0.000s  0.000s  0.001s       6   G(p -> (q | X(q | X(q | X(q | Xq)))))
 0.000s  0.000s  0.002s       7   G(p -> (q | X(q | X(q | X(q | X(q | Xq))))))
 0.000s  0.000s  0.005s       8   G(p -> (q | X(q | X(q | X(q | X(q | X(q | Xq)))))))
 0.000s  0.000s  0.023s       9   G(p -> (q | X(q | X(q | X(q | X(q | X(q | X(q | Xq))))))))
 0.000s  0.000s  0.126s      10   G(p -> (q | X(q | X(q | X(q | X(q | X(q | X(q | X(q | Xq)))…
                               -- tv-g2 5…9
 0.000s  0.000s  0.000s       6   G(p -> (q & X(q & X(q & X(q & Xq)))))
 0.000s  0.000s  0.000s       7   G(p -> (q & X(q & X(q & X(q & X(q & Xq))))))
 0.000s  0.000s  0.001s       8   G(p -> (q & X(q & X(q & X(q & X(q & X(q & Xq)))))))
 0.000s  0.000s  0.001s       9   G(p -> (q & X(q & X(q & X(q & X(q & X(q & X(q & Xq))))))))
 0.000s  0.000s  0.002s      10   G(p -> (q & X(q & X(q & X(q & X(q & X(q & X(q & X(q & Xq)))…
                               -- chomp-mealy 1,1
 0.000s  0.000s  0.001s       7   !oti & oto & ox0 & oy0 & (o0b0 xor (ox0 & oy0)) & G(oti xor…
                               -- chomp-mealy 2,2
 0.001s  0.002s  0.008s      19   !oti & oto & (o0b0 xor (ox0 & oy0)) & (o1b0 xor (ox1 & oy0)…
                               -- chomp-mealy 3,2
 0.002s  0.005s  0.069s      31   !oti & oto & (oy0 | oy1) & (o0b0 xor (ox0 & oy0)) & (o1b0 x…
                               -- dac-patterns
 0.000s  0.001s  0.000s       2   G!p0
 0.000s  0.000s  0.000s       4   Fp0 -> (!p1 U p0)
 0.000s  0.000s  0.000s       3   G(p0 -> G!p1)
 0.000s  0.000s  0.000s       3   G((p0 & !p1) -> (!p2 W p1))
 0.000s  0.000s  0.000s       2   Fp0
 0.000s  0.000s  0.000s       3   !p0 W (!p0 & p1)
 0.000s  0.000s  0.000s       3   G!p0 | F(p0 & Fp1)
 0.000s  0.000s  0.000s       3   G((p0 & !p1) -> (!p1 W (!p1 & p2)))
 0.000s  0.000s  0.000s       6   !p0 W (p0 W (!p0 W (p0 W G!p0)))
 0.000s  0.000s  0.000s       8   Fp0 -> ((!p0 & !p1) U (p0 | ((!p0 & p1) U (p0 | ((!p0 & !p1…
 0.000s  0.000s  0.000s       2   Gp0
 0.000s  0.000s  0.000s       4   Fp0 -> (p1 U p0)
 0.000s  0.000s  0.000s       3   G(p0 -> Gp1)
 0.000s  0.000s  0.000s       3   G((p0 & !p1) -> (p2 W p1))
 0.000s  0.000s  0.000s       3   !p0 W p1
 0.000s  0.000s  0.000s       4   Fp0 -> (!p1 U (p0 | p2))
 0.000s  0.000s  0.000s       3   G((p0 & !p1) -> (!p2 W (p1 | p3)))
 0.000s  0.000s  0.000s       4   Fp0 -> ((p1 -> (!p0 U (!p0 & p2))) U p0)
 0.000s  0.000s  0.000s       4   Fp0 -> (!p0 U (!p0 & p1 & X(!p0 U p2)))
 0.000s  0.000s  0.000s       5   Fp0 -> (!p1 U (p0 | (!p1 & p2 & X(!p1 U p3))))
 0.000s  0.000s  0.000s       4   F(p0 & XFp1) -> (!p0 U p2)
 0.000s  0.000s  0.000s       5   Fp0 -> (!(!p0 & p1 & X(!p0 U (!p0 & p2))) U (p0 | p3))
 0.000s  0.000s  0.001s       6   Fp0 -> (((p1 & X(!p0 U p2)) -> X(!p0 U (p2 & Fp3))) U p0)
 0.000s  0.000s  0.000s       5   Fp0 -> ((p1 -> (!p0 U (!p0 & p2 & X(!p0 U p3)))) U p0)
 0.000s  0.000s  0.001s       5   Fp0 -> ((p1 -> (!p0 U (!p0 & p2 & !p3 & X((!p0 & !p3) U p4)…
                               -- kr-n-delta1 1…3
 0.001s  0.001s  0.001s      13   c & X(a1 | b1 | d) & G((a1 | b1) -> X(c & X(a1 | b1 | d | G…
 0.006s  0.005s  0.004s      83   c & X(a1 | b1 | d) & Fd & G((a1 | b1) -> X(a2 | b2)) & G((a…
 7.247s  3.296s  0.133s    2241   c & X(a1 | b1 | d) & Fd & G(((a1 | b1) -> X(a2 | b2)) & ((a…
                               -- kr-nlogn-delta1 1…3
 0.012s  0.004s  0.002s      20   c & Fd & X(d | y) & G(y -> X((a | b) & X(c & X(d | y | Gc))…
 0.012s  0.010s  0.006s     148   c & Fd & X(d | y) & G(y -> X((a | b) & Xz)) & G(z -> X((a |…
 0.388s  0.307s  0.062s    6207   c & Fd & X(d | (y & Xy)) & G(((y & Xy) -> XX((a | b) & X(z …

The difference between the two NEW versions is mostly due to the automaton representation. In the second column, the MTDSWA has to be converted into a TwA, and this conversion (which requires enumerating the paths in the MTBDDs and creating labels for each of them) has significant overhead. However, even with this overhead, the new approach outperforms the old one most of the time.

It is interesting to see that there are two families where it is slower, namely, kr-n-delta1 and kr-nlogn-delta1. These are linear and quasilinear formulas for which the minimal DBA is at least doubly exponential; i.e., the worst case of the translation.

I investigated the behavior of the old and new pipelines on kr-nlogn-delta1=3:

  • The old pipeline builds a 1381-state NBA that is then determinized into a 6454-state DBA, and minimized to 6207 states.
  • The new pipeline builds a 7559-state DBA that is then minimized to 6207 states.

Seeing that the new pipeline was spending a lot of time performing propositional-equivalence checks on all the LTL formulas labeling BDD leaves, I worked on that a bit. I added some missing caches to propositional equivalence, moved the prop.eq. into a second pass after computing $\mathit{tr}(\cdot)$ instead of performing it during the computation on each intermediate terminal, and added a few shortcuts. This significantly reduced the runtime, but, as seen above, it is still very long. Currently, propositional equivalence in the new pipeline (to MTDSWA) amounts for 65% of the time spent translating kr-nlogn-delta1=3. The translation of the old pipeline (Couvreur's) also performs some kind of propositional equivalence, but I think it is helped by two factors:

  • it is building an NBA with 1381 states, so the set of LTL formulas on which propositional equivalence is run is much smaller
  • it builds BDDs that are similar to the MTBDD we build, except that Couvreur replaces the LTL-labeled terminals by the BDDs that we use for propositional encoding. This means that the LTL formulas that label destination states need only be reconstructed at the end of the translation of the BDD representing each state, not for each intermediate computation...

Synthesis

If we want to solve synthesis with Mealy semantics, we can declare input variables before output variables, and mark output variables as "controllable". This way the automaton can be interpreted as a game:

In [77]:
f = 'G(a <-> X!a) | F(b & Xa)'
with spot.bdd_dict_preorder("b") as d:
    aut = spot.obligation_to_mtdswa(f, dict=d)
    aut.set_controllable_variables(['a'])
    display(aut.show('s'))
mtdswa Inf( ) [Büchi] cluster_2 cluster_1 cluster_0 S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702572 b S0->B1702572 S1 F(b & Xa) | (a & G(a <-> X!a)) B1702575 b S1->B1702575 S2 F(b & Xa) | (!a & G(a <-> X!a)) B1702578 b S2->B1702578 S3 a | F(b & Xa) | (a & G(a <-> X!a)) B1702580 b S3->B1702580 S4 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702568 b S4->B1702568 S5 F(b & Xa) B1702581 b S5->B1702581 S6 a | F(b & Xa) S6->B1702580 B391 F(b & Xa) B1702581->B391 B394 a | F(b & Xa) B1702581->B394 B1702576 a B1702578->B1702576 B1702577 a B1702578->B1702577 B1998 a B1702572->B1998 B1999 a B1702572->B1999 B1702567 a B1702568->B1702567 B1702566 a B1702568->B1702566 B1702574 a B1702575->B1702574 B1702573 a B1702575->B1702573 B6866 a B1702580->B6866 B1702579 a B1702580->B1702579 B34 F(b & Xa) | (a & G(a <-> X!a)) B1702576->B34 B1702576->B391 B1702574->B394 B208 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702574->B208 B1 1 B6866->B1 B6866->B394 B1702577->B394 B204 a | F(b & Xa) | (a & G(a <-> X!a)) B1702577->B204 B1702567->B1 B1702567->B204 B1702579->B1 B1702579->B391 B1702573->B391 B35 F(b & Xa) | (!a & G(a <-> X!a)) B1702573->B35 B1998->B34 B1998->B35 B1702566->B1 B1702566->B34 B1999->B208 B1999->B204

The synthesis algorithm actually solves the game while building the automaton, hopefully avoiding the computation of some states. The following example shows some of the intermediate steps of the construction.

The constructed automaton is explored using a DFS that tracks strongly connected components. When backtracking from an SCC any state that is undetermined (i.e., not yet marked as winning for either player) is marked according to the nature of the SCC: if the SCC is accepting, any cycle it contains is winning for the output player, if the SCC is rejecting, any cycle it contains is winning for the input player. The algorithm terminates as soon as the initial state is determined.

In the following intermediate outputs,

  • dashed states haven't been translated yet,
  • green nodes are winning for the output player,
  • red nodes are winning for the input player,
  • the yellow state is the next one to work on.

States in the debug output are also marked with or when their corresponding terminals are winning for the input or output player. This is a workaround to a limitation of our Graphviz printer that we can only color BDD nodes (including terminals) but the "state labels" that point to the roots of those BDDs. All this cosmetic work is of course performed only when the debug option is given.

Note also that, contrary to the output above, the acceptance of each state is not computed during the on-the-fly construction. We only need to compute the acceptance of one state per SCC when we remove it. Ultimately, all losing states will be removed from the automaton, and any infinite path in the remaining automaton will be a valid controller.

In [78]:
with spot.bdd_dict_preorder("b") as d:
    for step in (0,1,2,3,4,5,7,9,10,11,14):
        print("step", step)
        display(spot.obligation_synthesis(f, ['a'], dict=d, debug=step))
step 0
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0
step 1
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 B1702550 a B1702552->B1702550 B1702551 a B1702552->B1702551 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702551->B1865 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273
step 2
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 B1702569 a B1702571->B1702569 B1702570 a B1702571->B1702570 B1702550 a B1702552->B1702550 B1702551 a B1702552->B1702551 B1 1 B1702569->B1 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702569->B1861 B1702550->B1861 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702551->B1865 B1702570->B1 B1702570->B1865
step 3
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702568 b S2->B1702568 B1702566 a B1702568->B1702566 B1702567 a B1702568->B1702567 B1702570 a B1702571->B1702570 B1702569 a B1702571->B1702569 B1702551 a B1702552->B1702551 B1702550 a B1702552->B1702550 B1 1 B1702566->B1 B34 F(b & Xa) B1702566->B34 B1702570->B1 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702570->B1865 B1702551->B1865 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1702567->B1 B204 a | F(b & Xa) B1702567->B204 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1702569->B1 B1702569->B1861
step 4
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702568 b S2->B1702568 S3 a | F(b & Xa) S3->B1702568 B1702566 a B1702568->B1702566 B1702567 a B1702568->B1702567 B1702570 a B1702571->B1702570 B1702569 a B1702571->B1702569 B1702551 a B1702552->B1702551 B1702550 a B1702552->B1702550 B1 1 B1702566->B1 B34 F(b & Xa) B1702566->B34 B1702570->B1 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702570->B1865 B1702551->B1865 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1702567->B1 B204 a | F(b & Xa) B1702567->B204 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1702569->B1 B1702569->B1861
step 5
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702568 b S2->B1702568 S3 a | F(b & Xa) S3->B1702568 S4 F(b & Xa) B1702547 b S4->B1702547 B1702567 a B1702568->B1702567 B1702566 a B1702568->B1702566 B1702550 a B1702552->B1702550 B1702551 a B1702552->B1702551 B1702569 a B1702571->B1702569 B1702570 a B1702571->B1702570 B34 F(b & Xa) B1702547->B34 B204 a | F(b & Xa) B1702547->B204 B1 1 B1702567->B1 B1702567->B204 B1702569->B1 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702569->B1861 B1702570->B1 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702570->B1865 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1702550->B1861 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1702551->B1865 B1702566->B1 B1702566->B34
step 7
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702568 b S2->B1702568 S3 a | F(b & Xa) S3->B1702568 S4 F(b & Xa) B1702547 b S4->B1702547 B1702567 a B1702568->B1702567 B1702566 a B1702568->B1702566 B1702550 a B1702552->B1702550 B1702551 a B1702552->B1702551 B1702569 a B1702571->B1702569 B1702570 a B1702571->B1702570 B34 F(b & Xa) B1702547->B34 B204 a | F(b & Xa) B1702547->B204 B1 1 B1702567->B1 B1702567->B204 B1702569->B1 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702569->B1861 B1702570->B1 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702570->B1865 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1702550->B1861 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1702551->B1865 B1702566->B1 B1702566->B34
step 9
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702568 b S2->B1702568 S3 a | F(b & Xa) S3->B1702568 S4 F(b & Xa) B1702547 b S4->B1702547 B1702567 a B1702568->B1702567 B1702566 a B1702568->B1702566 B1702550 a B1702552->B1702550 B1702551 a B1702552->B1702551 B1702569 a B1702571->B1702569 B1702570 a B1702571->B1702570 B34 F(b & Xa) B1702547->B34 B204 a | F(b & Xa) B1702547->B204 B1 1 B1702567->B1 B1702567->B204 B1702569->B1 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702569->B1861 B1702570->B1 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702570->B1865 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1702550->B1861 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1702551->B1865 B1702566->B1 B1702566->B34
step 10
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702568 b S2->B1702568 S3 a | F(b & Xa) S3->B1702568 S4 F(b & Xa) B1702547 b S4->B1702547 S5 F(b & Xa) | (a & G(a <-> X!a)) B1702561 b S5->B1702561 B1702567 a B1702568->B1702567 B1702566 a B1702568->B1702566 B1702550 a B1702552->B1702550 B1702551 a B1702552->B1702551 B1702569 a B1702571->B1702569 B1702570 a B1702571->B1702570 B1702560 a B1702561->B1702560 B1702559 a B1702561->B1702559 B204 a | F(b & Xa) B1702547->B204 B34 F(b & Xa) B1702547->B34 B1 1 B1702567->B1 B1702567->B204 B1702569->B1 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702569->B1861 B1702560->B204 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702560->B7240273 B1702550->B1861 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1702566->B1 B1702566->B34 B1702570->B1 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702570->B1865 B1702551->B1865 B1702551->B7240273 B1702559->B34 B1702559->B1863
step 11
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702568 b S2->B1702568 S3 a | F(b & Xa) S3->B1702568 S4 F(b & Xa) B1702547 b S4->B1702547 S5 F(b & Xa) | (a & G(a <-> X!a)) B1702561 b S5->B1702561 S6 F(b & Xa) | (!a & G(a <-> X!a)) B1702565 b S6->B1702565 B1702563 a B1702565->B1702563 B1702564 a B1702565->B1702564 B1702566 a B1702568->B1702566 B1702567 a B1702568->B1702567 B1702550 a B1702552->B1702550 B1702551 a B1702552->B1702551 B1702560 a B1702561->B1702560 B1702559 a B1702561->B1702559 B1702570 a B1702571->B1702570 B1702569 a B1702571->B1702569 B204 a | F(b & Xa) B1702547->B204 B34 F(b & Xa) B1702547->B34 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702563->B1861 B1702563->B34 B1 1 B1702566->B1 B1702566->B34 B1702570->B1 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702570->B1865 B1702564->B204 B1702564->B1865 B1702567->B1 B1702567->B204 B1702560->B204 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702560->B7240273 B1702569->B1 B1702569->B1861 B1702550->B1861 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1702559->B34 B1702559->B1863 B1702551->B1865 B1702551->B7240273
step 14
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 b S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702571 b S1->B1702571 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702568 b S2->B1702568 S3 a | F(b & Xa) S3->B1702568 S4 F(b & Xa) B1702547 b S4->B1702547 S5 F(b & Xa) | (a & G(a <-> X!a)) B1702561 b S5->B1702561 S6 F(b & Xa) | (!a & G(a <-> X!a)) B1702565 b S6->B1702565 B1702563 a B1702565->B1702563 B1702564 a B1702565->B1702564 B1702566 a B1702568->B1702566 B1702567 a B1702568->B1702567 B1702550 a B1702552->B1702550 B1702551 a B1702552->B1702551 B1702560 a B1702561->B1702560 B1702559 a B1702561->B1702559 B1702570 a B1702571->B1702570 B1702569 a B1702571->B1702569 B204 a | F(b & Xa) B1702547->B204 B34 F(b & Xa) B1702547->B34 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702563->B1861 B1702563->B34 B1 1 B1702566->B1 B1702566->B34 B1702570->B1 B1865 a | F(b & Xa) | (a & G(a <-> X!a)) B1702570->B1865 B1702564->B204 B1702564->B1865 B1702567->B1 B1702567->B204 B1702560->B204 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702560->B7240273 B1702569->B1 B1702569->B1861 B1702550->B1861 B1863 F(b & Xa) | (!a & G(a <-> X!a)) B1702550->B1863 B1702559->B34 B1702559->B1863 B1702551->B1865 B1702551->B7240273

Each step above represents one iteration of the DFS loop: either considering one possible successor for the current state (if a state was highlighted in yellow at step N, it will be the state developed at step N+1) or backtracking in the DFS (if no state is highlighted at step N, the next step is a backtrack). The entire process is described above.

Currently the output of spot.obligation_synthesis, without the debug option, is a crude output obtained by replacing all non-winning nodes (not green) by bddfalse, and for each output node (diamond) that has two winning successors, selecting one successor by redirecting the other to bddfalse (the backpropagation code simply remembers the edge that contributed to marking a node as winning). Probably we could improve that.

In [79]:
with spot.bdd_dict_preorder("b") as d:
    aut = spot.obligation_synthesis(f, ['a'], dict=d)
display(aut)
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702583 b S0->B1702583 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B4 a S1->B4 S2 a | F(b & Xa) | (a & G(a <-> X!a)) S2->B4 S3 a | F(b & Xa) S3->B4 S4 F(b & Xa) B0 0 S4->B0 S5 F(b & Xa) | (a & G(a <-> X!a)) B1702585 b S5->B1702585 S6 F(b & Xa) | (!a & G(a <-> X!a)) B1702588 b S6->B1702588 B1702587 a B1702588->B1702587 B1702586 a B1702588->B1702586 B1702582 a B1702585->B1702582 B1702584 a B1702585->B1702584 B1702583->B1702582 B198 a B1702583->B198 B1702587->B0 B35 a | F(b & Xa) | (a & G(a <-> X!a)) B1702587->B35 B1702582->B0 B394 F(b & Xa) | (!a & G(a <-> X!a)) B1702582->B394 B1702584->B0 B204 a | F(b & Xa) B1702584->B204 B198->B0 B34 a | F(b & Xa) | (!a & G(a <-> X!a)) B198->B34 B1702586->B0 B391 F(b & Xa) | (a & G(a <-> X!a)) B1702586->B391 B4->B0 B1 1 B4->B1
In [80]:
m = spot.mtdswa_strategy_to_mealy(aut)
display(m, spot.reduce_mealy(m))
0 F(b & Xa) | G(a <-> X!a) I->0 1 F(b & Xa) | (!a & G(a <-> X!a)) 0->1 !b / a 2 a | F(b & Xa) | (!a & G(a <-> X!a)) 0->2 b / a 1->2 b / !a 3 F(b & Xa) | (a & G(a <-> X!a)) 1->3 !b / !a 4 1 2->4 1 / a 3->1 !b / a 3->2 b / !a 4->4 1 / 1
0 0 I->0 0->0 b / a 1 1 0->1 !b / a 1->0 b / !a 2 2 1->2 !b / !a 2->0 b / !a 2->1 !b / a

Here is the same specification, but with input and output swapped, so that it becomes unrealizable. In this case, we can see the algorithm abort before visiting all states.

In [81]:
with spot.bdd_dict_preorder("a") as d:
    for step in (0,1,2,3,4,5,7,9,10):
        print("step", step)
        display(spot.obligation_synthesis(f, ['b'], dict=d, debug=step))
step 0
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0
step 1
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 a S0->B1702552 B1702550 b B1702552->B1702550 B1702551 b B1702552->B1702551 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1863 a | F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1863 B1865 F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B1865 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273
step 2
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 a S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702633 a S1->B1702633 B1702550 b B1702633->B1702550 B1 1 B1702633->B1 B1702552->B1702550 B1702551 b B1702552->B1702551 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1863 a | F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1863 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1865 F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B1865
step 3
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 a S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702633 a S1->B1702633 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702631 a S2->B1702631 B1702624 b B1702631->B1702624 B1 1 B1702631->B1 B1702550 b B1702633->B1702550 B1702633->B1 B1702552->B1702550 B1702551 b B1702552->B1702551 B34 F(b & Xa) B1702624->B34 B204 a | F(b & Xa) B1702624->B204 B1863 a | F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1863 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1865 F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B1865 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273
step 4
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 a S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702633 a S1->B1702633 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702631 a S2->B1702631 S3 a | F(b & Xa) S3->B1702631 B1702624 b B1702631->B1702624 B1 1 B1702631->B1 B1702550 b B1702633->B1702550 B1702633->B1 B1702552->B1702550 B1702551 b B1702552->B1702551 B34 F(b & Xa) B1702624->B34 B204 a | F(b & Xa) B1702624->B204 B1863 a | F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1863 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1865 F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B1865 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273
step 5
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 a S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702633 a S1->B1702633 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702631 a S2->B1702631 S3 a | F(b & Xa) S3->B1702631 S4 F(b & Xa) B1702624 b S4->B1702624 B1702631->B1702624 B1 1 B1702631->B1 B1702551 b B1702552->B1702551 B1702550 b B1702552->B1702550 B1702633->B1702550 B1702633->B1 B34 F(b & Xa) B1702624->B34 B204 a | F(b & Xa) B1702624->B204 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1865 F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B1865 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1863 a | F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1863
step 7
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 a S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702633 a S1->B1702633 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702631 a S2->B1702631 S3 a | F(b & Xa) S3->B1702631 S4 F(b & Xa) B1702624 b S4->B1702624 B1702631->B1702624 B1 1 B1702631->B1 B1702551 b B1702552->B1702551 B1702550 b B1702552->B1702550 B1702633->B1702550 B1702633->B1 B34 F(b & Xa) B1702624->B34 B204 a | F(b & Xa) B1702624->B204 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1865 F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B1865 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1863 a | F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1863
step 9
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 a S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702633 a S1->B1702633 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702631 a S2->B1702631 S3 a | F(b & Xa) S3->B1702631 S4 F(b & Xa) B1702624 b S4->B1702624 B1702631->B1702624 B1 1 B1702631->B1 B1702551 b B1702552->B1702551 B1702550 b B1702552->B1702550 B1702633->B1702550 B1702633->B1 B34 F(b & Xa) B1702624->B34 B204 a | F(b & Xa) B1702624->B204 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1865 F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B1865 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B1863 a | F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1863
step 10
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1702552 a S0->B1702552 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702633 a S1->B1702633 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1702631 a S2->B1702631 S3 a | F(b & Xa) S3->B1702631 S4 F(b & Xa) B1702624 b S4->B1702624 S5 F(b & Xa) | (a & G(a <-> X!a)) B1702635 a S5->B1702635 B1702631->B1702624 B1 1 B1702631->B1 B1702551 b B1702552->B1702551 B1702550 b B1702552->B1702550 B1702633->B1702550 B1702633->B1 B1702635->B1702551 B1702635->B1702624 B1865 F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B1865 B7240273 a | F(b & Xa) | (!a & G(a <-> X!a)) B1702551->B7240273 B1863 a | F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1863 B1861 F(b & Xa) | (a & G(a <-> X!a)) B1702550->B1861 B34 F(b & Xa) B1702624->B34 B204 a | F(b & Xa) B1702624->B204

The full algorithm without debug mode simply returns an automaton with a single state equal to bddfalse (and acceptance f) in case the specification is not realizable.

In [82]:
with spot.bdd_dict_preorder("a") as d:
    aut = spot.obligation_synthesis(f, ['b'], dict=d)
display(aut)
mtdswa f [none] S0 0 I->S0 B0 0 S0->B0

Here is another example, taken from SyntComp's tsl_paper/OneCounterInRangeA2.tlsf.

In [83]:
g = 'G!((o1 & !(o0 | o2)) <-> (!o1 & !((!o0 & o2) <-> (o0 & !o2)))) & ((i2 & G!(i0 & i1) & G((i2 & o0) -> Xi2) & G((i4 & o2) -> Xi2)) -> (G(o2 <-> (i1 & i4)) & G(o1 <-> (i0 & i3)) & Gi2))'
with spot.bdd_dict_preorder("i0", "i1", "i2", "i3", "i4") as d:
    for step in (0,1,2,3,4,5,6,9):
        print("step", step)
        aut = spot.obligation_synthesis(g, ['o0', 'o1', 'o2'], dict=d, debug=step)
        display(aut.show('0'))
    print("output")
    aut = spot.obligation_synthesis(g, ['o0', 'o1', 'o2'], dict=d)
    display(aut)
step 0
mtdswa t [all] S0 0 I->S0
step 1
mtdswa t [all] S0 0 I->S0 B1702790 i0 S0->B1702790 B1702779 i1 B1702790->B1702779 B1702789 i1 B1702790->B1702789 B1702772 i2 B1702779->B1702772 B1702778 i2 B1702779->B1702778 B1702788 i2 B1702789->B1702788 B1702666 o1 B1702789->B1702666 B1702771 i4 B1702772->B1702771 B1702772->B1702666 B1702787 i3 B1702788->B1702787 B1702788->B1702666 B1702777 i4 B1702778->B1702777 B1702778->B1702666 B1702787->B1702771 B1702786 i4 B1702787->B1702786 B1702767 o1 B1702771->B1702767 B1702770 o1 B1702771->B1702770 B1702776 o1 B1702777->B1702776 B1702777->B1702767 B1702785 o1 B1702786->B1702785 B1702783 o1 B1702786->B1702783 B1702664 o0 B1702666->B1702664 B1702665 o0 B1702666->B1702665 B1702775 o0 B1702776->B1702775 B1702766 o0 B1702776->B1702766 B1702784 o0 B1702785->B1702784 B1702782 o0 B1702785->B1702782 B1702767->B1702766 B1702764 o0 B1702767->B1702764 B1702780 o0 B1702783->B1702780 B1702783->B1702782 B1702769 o0 B1702770->B1702769 B1702770->B1702766 B1702663 o2 B1702664->B1702663 B1702662 o2 B1702664->B1702662 B1702774 o2 B1702775->B1702774 B1702773 o2 B1702775->B1702773 B1702762 o2 B1702780->B1702762 B1702780->B1702774 B1702763 o2 B1702769->B1702763 B1702768 o2 B1702769->B1702768 B1702665->B1702663 B0 0 B1702665->B0 B1702765 o2 B1702766->B1702765 B1702766->B0 B1702764->B1702762 B1702764->B1702763 B1702784->B1702774 B1702784->B1702768 B1702781 o2 B1702782->B1702781 B1702782->B0 B1702663->B0 B34 1 B1702663->B34 B1702762->B0 B635823 2 B1702762->B635823 B1702763->B0 B37 0 B1702763->B37 B1702781->B0 B7240353 4 B1702781->B7240353 B1702774->B0 B7240351 3 B1702774->B7240351 B1702768->B0 B1702768->B7240351 B1702773->B0 B1702773->B37 B1702662->B0 B1702662->B34 B1702765->B0 B1702765->B635823
step 2
mtdswa t [all] S0 0 I->S0 B1702790 i0 S0->B1702790 S1 1 B1702820 i0 S1->B1702820 B1702817 i1 B1702820->B1702817 B1702819 i1 B1702820->B1702819 B1702779 i1 B1702790->B1702779 B1702789 i1 B1702790->B1702789 B1702816 i2 B1702817->B1702816 B1702815 i2 B1702817->B1702815 B1702772 i2 B1702779->B1702772 B1702778 i2 B1702779->B1702778 B1702788 i2 B1702789->B1702788 B1702666 o1 B1702789->B1702666 B1702818 i2 B1702819->B1702818 B1702819->B1702666 B1702814 i4 B1702816->B1702814 B1702777 i4 B1702816->B1702777 B1702787 i3 B1702788->B1702787 B1702788->B1702666 B1702818->B1702787 B1702818->B1702814 B1702771 i4 B1702772->B1702771 B1702772->B1702666 B1702778->B1702777 B1702778->B1702666 B1702815->B1702814 B1702815->B1702771 B1702787->B1702771 B1702786 i4 B1702787->B1702786 B1702811 o1 B1702814->B1702811 B1702813 o1 B1702814->B1702813 B1702767 o1 B1702771->B1702767 B1702770 o1 B1702771->B1702770 B1702777->B1702767 B1702776 o1 B1702777->B1702776 B1702783 o1 B1702786->B1702783 B1702785 o1 B1702786->B1702785 B1702665 o0 B1702666->B1702665 B1702664 o0 B1702666->B1702664 B1702766 o0 B1702767->B1702766 B1702764 o0 B1702767->B1702764 B1702782 o0 B1702783->B1702782 B1702780 o0 B1702783->B1702780 B1702785->B1702782 B1702784 o0 B1702785->B1702784 B1702776->B1702766 B1702775 o0 B1702776->B1702775 B1702811->B1702766 B1702810 o0 B1702811->B1702810 B1702769 o0 B1702770->B1702769 B1702770->B1702766 B1702812 o0 B1702813->B1702812 B1702813->B1702766 B1702663 o2 B1702665->B1702663 B0 0 B1702665->B0 B1702781 o2 B1702782->B1702781 B1702782->B0 B1702768 o2 B1702769->B1702768 B1702763 o2 B1702769->B1702763 B1702812->B1702768 B1702765 o2 B1702812->B1702765 B1702766->B1702765 B1702766->B0 B1702774 o2 B1702780->B1702774 B1702762 o2 B1702780->B1702762 B1702784->B1702768 B1702784->B1702774 B1702810->B1702765 B1702810->B1702762 B1702764->B1702763 B1702764->B1702762 B1702773 o2 B1702775->B1702773 B1702775->B1702774 B1702662 o2 B1702664->B1702662 B1702664->B1702663 B1702781->B0 B7240353 1 B1702781->B7240353 B1702768->B0 B7240351 4 B1702768->B7240351 B1702773->B0 B37 0 B1702773->B37 B1702662->B0 B34 2 B1702662->B34 B1702663->B0 B1702663->B34 B1702765->B0 B635823 3 B1702765->B635823 B1702774->B0 B1702774->B7240351 B1702763->B0 B1702763->B37 B1702762->B0 B1702762->B635823
step 3
mtdswa t [all] S0 0 I->S0 B1702790 i0 S0->B1702790 S1 1 B1702820 i0 S1->B1702820 S2 2 B1702830 i0 S2->B1702830 B1702829 i1 B1702830->B1702829 B1702828 i2 B1702830->B1702828 B1702817 i1 B1702820->B1702817 B1702819 i1 B1702820->B1702819 B1702789 i1 B1702790->B1702789 B1702779 i1 B1702790->B1702779 B1702829->B1702828 B1702666 o1 B1702829->B1702666 B1702788 i2 B1702789->B1702788 B1702789->B1702666 B1702815 i2 B1702817->B1702815 B1702816 i2 B1702817->B1702816 B1702772 i2 B1702779->B1702772 B1702778 i2 B1702779->B1702778 B1702818 i2 B1702819->B1702818 B1702819->B1702666 B1702827 i4 B1702828->B1702827 B1702828->B1702666 B1702771 i4 B1702772->B1702771 B1702772->B1702666 B1702777 i4 B1702778->B1702777 B1702778->B1702666 B1702787 i3 B1702818->B1702787 B1702814 i4 B1702818->B1702814 B1702815->B1702771 B1702815->B1702814 B1702788->B1702787 B1702788->B1702666 B1702816->B1702814 B1702816->B1702777 B1702787->B1702771 B1702786 i4 B1702787->B1702786 B1702825 o1 B1702827->B1702825 B1702826 o1 B1702827->B1702826 B1702767 o1 B1702771->B1702767 B1702770 o1 B1702771->B1702770 B1702785 o1 B1702786->B1702785 B1702783 o1 B1702786->B1702783 B1702813 o1 B1702814->B1702813 B1702811 o1 B1702814->B1702811 B1702777->B1702767 B1702776 o1 B1702777->B1702776 B1702766 o0 B1702825->B1702766 B1702780 o0 B1702825->B1702780 B1702767->B1702766 B1702764 o0 B1702767->B1702764 B1702813->B1702766 B1702812 o0 B1702813->B1702812 B1702776->B1702766 B1702775 o0 B1702776->B1702775 B1702826->B1702766 B1702784 o0 B1702826->B1702784 B1702664 o0 B1702666->B1702664 B1702665 o0 B1702666->B1702665 B1702785->B1702784 B1702782 o0 B1702785->B1702782 B1702770->B1702766 B1702769 o0 B1702770->B1702769 B1702811->B1702766 B1702810 o0 B1702811->B1702810 B1702783->B1702780 B1702783->B1702782 B1702765 o2 B1702766->B1702765 B0 0 B1702766->B0 B1702768 o2 B1702812->B1702768 B1702812->B1702765 B1702762 o2 B1702810->B1702762 B1702810->B1702765 B1702769->B1702768 B1702763 o2 B1702769->B1702763 B1702764->B1702762 B1702764->B1702763 B1702780->B1702762 B1702774 o2 B1702780->B1702774 B1702662 o2 B1702664->B1702662 B1702663 o2 B1702664->B1702663 B1702775->B1702774 B1702773 o2 B1702775->B1702773 B1702784->B1702768 B1702784->B1702774 B1702781 o2 B1702782->B1702781 B1702782->B0 B1702665->B1702663 B1702665->B0 B1702768->B0 B7240351 2 B1702768->B7240351 B1702762->B0 B635823 4 B1702762->B635823 B1702781->B0 B7240353 1 B1702781->B7240353 B1702662->B0 B34 3 B1702662->B34 B1702763->B0 B37 0 B1702763->B37 B1702765->B0 B1702765->B635823 B1702774->B0 B1702774->B7240351 B1702773->B0 B1702773->B37 B1702663->B0 B1702663->B34
step 4
mtdswa t [all] S0 0 I->S0 B1702790 i0 S0->B1702790 S1 1 B1702820 i0 S1->B1702820 S2 2 B1702830 i0 S2->B1702830 S3 3 B1702836 i0 S3->B1702836 B1702829 i1 B1702830->B1702829 B1702828 i2 B1702830->B1702828 B1702779 i1 B1702790->B1702779 B1702789 i1 B1702790->B1702789 B1702817 i1 B1702820->B1702817 B1702819 i1 B1702820->B1702819 B1702835 i1 B1702836->B1702835 B1702834 i2 B1702836->B1702834 B1702829->B1702828 B1702666 o1 B1702829->B1702666 B1702816 i2 B1702817->B1702816 B1702815 i2 B1702817->B1702815 B1702818 i2 B1702819->B1702818 B1702819->B1702666 B1702772 i2 B1702779->B1702772 B1702778 i2 B1702779->B1702778 B1702788 i2 B1702789->B1702788 B1702789->B1702666 B1702835->B1702834 B1702835->B1702666 B1702827 i4 B1702834->B1702827 B1702814 i4 B1702834->B1702814 B1702828->B1702827 B1702828->B1702666 B1702787 i3 B1702788->B1702787 B1702788->B1702666 B1702771 i4 B1702772->B1702771 B1702772->B1702666 B1702816->B1702814 B1702777 i4 B1702816->B1702777 B1702778->B1702777 B1702778->B1702666 B1702815->B1702814 B1702815->B1702771 B1702818->B1702787 B1702818->B1702814 B1702786 i4 B1702787->B1702786 B1702787->B1702771 B1702825 o1 B1702827->B1702825 B1702826 o1 B1702827->B1702826 B1702811 o1 B1702814->B1702811 B1702813 o1 B1702814->B1702813 B1702785 o1 B1702786->B1702785 B1702783 o1 B1702786->B1702783 B1702776 o1 B1702777->B1702776 B1702767 o1 B1702777->B1702767 B1702770 o1 B1702771->B1702770 B1702771->B1702767 B1702766 o0 B1702825->B1702766 B1702780 o0 B1702825->B1702780 B1702811->B1702766 B1702810 o0 B1702811->B1702810 B1702776->B1702766 B1702775 o0 B1702776->B1702775 B1702770->B1702766 B1702769 o0 B1702770->B1702769 B1702826->B1702766 B1702784 o0 B1702826->B1702784 B1702664 o0 B1702666->B1702664 B1702665 o0 B1702666->B1702665 B1702785->B1702784 B1702782 o0 B1702785->B1702782 B1702813->B1702766 B1702812 o0 B1702813->B1702812 B1702767->B1702766 B1702764 o0 B1702767->B1702764 B1702783->B1702780 B1702783->B1702782 B1702765 o2 B1702766->B1702765 B0 0 B1702766->B0 B1702773 o2 B1702775->B1702773 B1702774 o2 B1702775->B1702774 B1702763 o2 B1702764->B1702763 B1702762 o2 B1702764->B1702762 B1702768 o2 B1702812->B1702768 B1702812->B1702765 B1702810->B1702765 B1702810->B1702762 B1702780->B1702762 B1702780->B1702774 B1702663 o2 B1702664->B1702663 B1702662 o2 B1702664->B1702662 B1702769->B1702763 B1702769->B1702768 B1702784->B1702768 B1702784->B1702774 B1702781 o2 B1702782->B1702781 B1702782->B0 B1702665->B1702663 B1702665->B0 B1702763->B0 B37 0 B1702763->B37 B1702768->B0 B7240351 2 B1702768->B7240351 B1702781->B0 B7240353 1 B1702781->B7240353 B1702773->B0 B1702773->B37 B1702765->B0 B635823 3 B1702765->B635823 B1702762->B0 B1702762->B635823 B1702774->B0 B1702774->B7240351 B1702663->B0 B34 4 B1702663->B34 B1702662->B0 B1702662->B34
step 5
mtdswa t [all] S0 0 I->S0 B1702790 i0 S0->B1702790 S1 1 B1702820 i0 S1->B1702820 S2 2 B1702830 i0 S2->B1702830 S3 3 B1702836 i0 S3->B1702836 S4 4 B1702666 o1 S4->B1702666 B1702829 i1 B1702830->B1702829 B1702828 i2 B1702830->B1702828 B1702779 i1 B1702790->B1702779 B1702789 i1 B1702790->B1702789 B1702835 i1 B1702836->B1702835 B1702834 i2 B1702836->B1702834 B1702819 i1 B1702820->B1702819 B1702817 i1 B1702820->B1702817 B1702829->B1702828 B1702829->B1702666 B1702778 i2 B1702779->B1702778 B1702772 i2 B1702779->B1702772 B1702818 i2 B1702819->B1702818 B1702819->B1702666 B1702815 i2 B1702817->B1702815 B1702816 i2 B1702817->B1702816 B1702788 i2 B1702789->B1702788 B1702789->B1702666 B1702835->B1702834 B1702835->B1702666 B1702827 i4 B1702834->B1702827 B1702814 i4 B1702834->B1702814 B1702777 i4 B1702778->B1702777 B1702778->B1702666 B1702787 i3 B1702788->B1702787 B1702788->B1702666 B1702815->B1702814 B1702771 i4 B1702815->B1702771 B1702828->B1702827 B1702828->B1702666 B1702816->B1702814 B1702816->B1702777 B1702772->B1702771 B1702772->B1702666 B1702818->B1702787 B1702818->B1702814 B1702786 i4 B1702787->B1702786 B1702787->B1702771 B1702825 o1 B1702827->B1702825 B1702826 o1 B1702827->B1702826 B1702783 o1 B1702786->B1702783 B1702785 o1 B1702786->B1702785 B1702813 o1 B1702814->B1702813 B1702811 o1 B1702814->B1702811 B1702776 o1 B1702777->B1702776 B1702767 o1 B1702777->B1702767 B1702770 o1 B1702771->B1702770 B1702771->B1702767 B1702665 o0 B1702666->B1702665 B1702664 o0 B1702666->B1702664 B1702782 o0 B1702783->B1702782 B1702780 o0 B1702783->B1702780 B1702766 o0 B1702776->B1702766 B1702775 o0 B1702776->B1702775 B1702770->B1702766 B1702769 o0 B1702770->B1702769 B1702825->B1702766 B1702825->B1702780 B1702826->B1702766 B1702784 o0 B1702826->B1702784 B1702813->B1702766 B1702812 o0 B1702813->B1702812 B1702785->B1702782 B1702785->B1702784 B1702767->B1702766 B1702764 o0 B1702767->B1702764 B1702811->B1702766 B1702810 o0 B1702811->B1702810 B1702663 o2 B1702665->B1702663 B0 0 B1702665->B0 B1702765 o2 B1702766->B1702765 B1702766->B0 B1702762 o2 B1702764->B1702762 B1702763 o2 B1702764->B1702763 B1702810->B1702765 B1702810->B1702762 B1702781 o2 B1702782->B1702781 B1702782->B0 B1702780->B1702762 B1702774 o2 B1702780->B1702774 B1702664->B1702663 B1702662 o2 B1702664->B1702662 B1702769->B1702763 B1702768 o2 B1702769->B1702768 B1702773 o2 B1702775->B1702773 B1702775->B1702774 B1702812->B1702765 B1702812->B1702768 B1702784->B1702768 B1702784->B1702774 B1702765->B0 B635823 3 B1702765->B635823 B1702762->B0 B1702762->B635823 B1702773->B0 B37 0 B1702773->B37 B1702763->B0 B1702763->B37 B1702768->B0 B7240351 2 B1702768->B7240351 B1702663->B0 B34 4 B1702663->B34 B1702781->B0 B7240353 1 B1702781->B7240353 B1702662->B0 B1702662->B34 B1702774->B0 B1702774->B7240351
step 6
mtdswa t [all] S0 0 I->S0 B1702790 i0 S0->B1702790 S1 1 B1702820 i0 S1->B1702820 S2 2 B1702830 i0 S2->B1702830 S3 3 B1702836 i0 S3->B1702836 S4 4 B1702666 o1 S4->B1702666 B1702829 i1 B1702830->B1702829 B1702828 i2 B1702830->B1702828 B1702779 i1 B1702790->B1702779 B1702789 i1 B1702790->B1702789 B1702835 i1 B1702836->B1702835 B1702834 i2 B1702836->B1702834 B1702819 i1 B1702820->B1702819 B1702817 i1 B1702820->B1702817 B1702829->B1702828 B1702829->B1702666 B1702778 i2 B1702779->B1702778 B1702772 i2 B1702779->B1702772 B1702818 i2 B1702819->B1702818 B1702819->B1702666 B1702815 i2 B1702817->B1702815 B1702816 i2 B1702817->B1702816 B1702788 i2 B1702789->B1702788 B1702789->B1702666 B1702835->B1702834 B1702835->B1702666 B1702827 i4 B1702834->B1702827 B1702814 i4 B1702834->B1702814 B1702777 i4 B1702778->B1702777 B1702778->B1702666 B1702787 i3 B1702788->B1702787 B1702788->B1702666 B1702815->B1702814 B1702771 i4 B1702815->B1702771 B1702828->B1702827 B1702828->B1702666 B1702816->B1702814 B1702816->B1702777 B1702772->B1702771 B1702772->B1702666 B1702818->B1702787 B1702818->B1702814 B1702786 i4 B1702787->B1702786 B1702787->B1702771 B1702825 o1 B1702827->B1702825 B1702826 o1 B1702827->B1702826 B1702783 o1 B1702786->B1702783 B1702785 o1 B1702786->B1702785 B1702813 o1 B1702814->B1702813 B1702811 o1 B1702814->B1702811 B1702776 o1 B1702777->B1702776 B1702767 o1 B1702777->B1702767 B1702770 o1 B1702771->B1702770 B1702771->B1702767 B1702665 o0 B1702666->B1702665 B1702664 o0 B1702666->B1702664 B1702782 o0 B1702783->B1702782 B1702780 o0 B1702783->B1702780 B1702766 o0 B1702776->B1702766 B1702775 o0 B1702776->B1702775 B1702770->B1702766 B1702769 o0 B1702770->B1702769 B1702825->B1702766 B1702825->B1702780 B1702826->B1702766 B1702784 o0 B1702826->B1702784 B1702813->B1702766 B1702812 o0 B1702813->B1702812 B1702785->B1702782 B1702785->B1702784 B1702767->B1702766 B1702764 o0 B1702767->B1702764 B1702811->B1702766 B1702810 o0 B1702811->B1702810 B1702663 o2 B1702665->B1702663 B0 0 B1702665->B0 B1702765 o2 B1702766->B1702765 B1702766->B0 B1702762 o2 B1702764->B1702762 B1702763 o2 B1702764->B1702763 B1702810->B1702765 B1702810->B1702762 B1702781 o2 B1702782->B1702781 B1702782->B0 B1702780->B1702762 B1702774 o2 B1702780->B1702774 B1702664->B1702663 B1702662 o2 B1702664->B1702662 B1702769->B1702763 B1702768 o2 B1702769->B1702768 B1702773 o2 B1702775->B1702773 B1702775->B1702774 B1702812->B1702765 B1702812->B1702768 B1702784->B1702768 B1702784->B1702774 B1702765->B0 B635823 3 B1702765->B635823 B1702762->B0 B1702762->B635823 B1702773->B0 B37 0 B1702773->B37 B1702763->B0 B1702763->B37 B1702768->B0 B7240351 2 B1702768->B7240351 B1702663->B0 B34 4 B1702663->B34 B1702781->B0 B7240353 1 B1702781->B7240353 B1702662->B0 B1702662->B34 B1702774->B0 B1702774->B7240351
step 9
mtdswa t [all] S0 0 I->S0 B1702790 i0 S0->B1702790 S1 1 B1702820 i0 S1->B1702820 S2 2 B1702830 i0 S2->B1702830 S3 3 B1702836 i0 S3->B1702836 S4 4 B1702666 o1 S4->B1702666 B1702829 i1 B1702830->B1702829 B1702828 i2 B1702830->B1702828 B1702779 i1 B1702790->B1702779 B1702789 i1 B1702790->B1702789 B1702835 i1 B1702836->B1702835 B1702834 i2 B1702836->B1702834 B1702819 i1 B1702820->B1702819 B1702817 i1 B1702820->B1702817 B1702829->B1702828 B1702829->B1702666 B1702778 i2 B1702779->B1702778 B1702772 i2 B1702779->B1702772 B1702818 i2 B1702819->B1702818 B1702819->B1702666 B1702815 i2 B1702817->B1702815 B1702816 i2 B1702817->B1702816 B1702788 i2 B1702789->B1702788 B1702789->B1702666 B1702835->B1702834 B1702835->B1702666 B1702827 i4 B1702834->B1702827 B1702814 i4 B1702834->B1702814 B1702777 i4 B1702778->B1702777 B1702778->B1702666 B1702787 i3 B1702788->B1702787 B1702788->B1702666 B1702815->B1702814 B1702771 i4 B1702815->B1702771 B1702828->B1702827 B1702828->B1702666 B1702816->B1702814 B1702816->B1702777 B1702772->B1702771 B1702772->B1702666 B1702818->B1702787 B1702818->B1702814 B1702786 i4 B1702787->B1702786 B1702787->B1702771 B1702825 o1 B1702827->B1702825 B1702826 o1 B1702827->B1702826 B1702783 o1 B1702786->B1702783 B1702785 o1 B1702786->B1702785 B1702813 o1 B1702814->B1702813 B1702811 o1 B1702814->B1702811 B1702776 o1 B1702777->B1702776 B1702767 o1 B1702777->B1702767 B1702770 o1 B1702771->B1702770 B1702771->B1702767 B1702665 o0 B1702666->B1702665 B1702664 o0 B1702666->B1702664 B1702782 o0 B1702783->B1702782 B1702780 o0 B1702783->B1702780 B1702766 o0 B1702776->B1702766 B1702775 o0 B1702776->B1702775 B1702770->B1702766 B1702769 o0 B1702770->B1702769 B1702825->B1702766 B1702825->B1702780 B1702826->B1702766 B1702784 o0 B1702826->B1702784 B1702813->B1702766 B1702812 o0 B1702813->B1702812 B1702785->B1702782 B1702785->B1702784 B1702767->B1702766 B1702764 o0 B1702767->B1702764 B1702811->B1702766 B1702810 o0 B1702811->B1702810 B1702663 o2 B1702665->B1702663 B0 0 B1702665->B0 B1702765 o2 B1702766->B1702765 B1702766->B0 B1702762 o2 B1702764->B1702762 B1702763 o2 B1702764->B1702763 B1702810->B1702765 B1702810->B1702762 B1702781 o2 B1702782->B1702781 B1702782->B0 B1702780->B1702762 B1702774 o2 B1702780->B1702774 B1702664->B1702663 B1702662 o2 B1702664->B1702662 B1702769->B1702763 B1702768 o2 B1702769->B1702768 B1702773 o2 B1702775->B1702773 B1702775->B1702774 B1702812->B1702765 B1702812->B1702768 B1702784->B1702768 B1702784->B1702774 B1702765->B0 B635823 3 B1702765->B635823 B1702762->B0 B1702762->B635823 B1702773->B0 B37 0 B1702773->B37 B1702763->B0 B1702763->B37 B1702768->B0 B7240351 2 B1702768->B7240351 B1702663->B0 B34 4 B1702663->B34 B1702781->B0 B7240353 1 B1702781->B7240353 B1702662->B0 B1702662->B34 B1702774->B0 B1702774->B7240351
output
mtdswa f [none] S0 0 I->S0 B0 0 S0->B0