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

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

We initially implemented a transition-based variant, before realizing that 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.

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

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 just a pair of arrays indexed by state numbers. The value of states[i] is an MTBDD encoding the successors states of state i, with terminal containing state indices. The value of colors[i] gives the set of colors associated to state i.

Compared to mtdtwa, the set of MTBDD nodes needed to represent a mtdswa is not bigger than the one used to represent an mtdtwa as states that have different colors but behave the same will 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 allow to chose between incomplete (rejecting sinks are removed) and complete (rejecting sinks are added) TwA. Adding sinks may require to change 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

SCC computations¶

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

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

In [12]:
spot.scc_vector(m2)
Out[12]:
(2, 1, 1, 0, 0)
In [13]:
spot.scc_vector(m3)
Out[13]:
(1, 0)
In [14]:
m2.show("s")
Out[14]:
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 [15]:
m3.show("s")
Out[15]:
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 [16]:
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[16]:
mtdswa Inf( ⓿ ) [Büchi] cluster_0 cluster_2 cluster_1 S0 0 ⓿ I->S0 B390 a S0->B390 S1 1 ⓿ B391 c S1->B391 S2 2 ⓿ B393 c S2->B393 S3 3 B392 a S3->B392 B393->B392 B393->B390 B0 0 B391->B0 B34 1 ⓿ B391->B34 B14 b B392->B14 B385 b B392->B385 B384 b B390->B384 B352 b B390->B352 B14->B0 B1 1 B14->B1 B384->B1 B35 2 ⓿ B384->B35 B385->B1 B204 3 B385->B204 B352->B1 B352->B34
In [17]:
a = spot.translate("(a U b) & GFa & GFb & FGc", "det", "sbacc", "gen")
m5 = spot.dtwa_to_mtdswa(a)
m5.show('s')
Out[17]:
mtdswa (Inf( ⓿ )&Inf( ❶ )) & Fin( ❷ ) [Streett-like 3] cluster_1 cluster_0 S0 0 I->S0 B1731 c S0->B1731 S1 1 ❷ B1747 c S1->B1747 S2 2 ⓿ ❶ S2->B1747 S3 3 ❶ S3->B1747 S4 4 ⓿ S4->B1747 S5 5 S5->B1747 B1746 a B1747->B1746 B34 1 ❷ B1747->B34 B1720 a B1731->B1720 B1730 a B1731->B1730 B1745 b B1746->B1745 B1739 b B1746->B1739 B403 b B1720->B403 B1718 b B1720->B1718 B1727 b B1730->B1727 B1127 b B1730->B1127 B37 0 B403->B37 B403->B34 B0 0 B1727->B0 B204 3 ❶ B1727->B204 B1127->B37 B35 2 ⓿ ❶ B1127->B35 B397 5 B1745->B397 B1745->B204 B1718->B0 B1718->B34 B208 4 ⓿ B1739->B208 B1739->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 [18]:
a = spot.translate("true", "det")
m5 = spot.dtwa_to_mtdswa(a)
print(spot.scc_vector(m5))
m5.show('s')
(0,)
Out[18]:
mtdswa t [all] S0 0 I->S0 B1 1 S0->B1

Translation from obligation formula to Deterministic Weak BA¶

... represented as MTDSWA.

The translation rules are even simpler than those of 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 [19]:
t1 = spot.obligation_to_mtdswa('a W b U c')
t1.show('s')
Out[19]:
mtdswa Inf( ⓿ ) [Büchi] cluster_1 cluster_0 S0 a W (b U c) ⓿ I->S0 B1752 c S0->B1752 S1 b U c B1748 c S1->B1748 B1718 b B1748->B1718 B1 1 B1748->B1 B1751 a B1752->B1751 B1752->B1 B1751->B1718 B37 a W (b U c) ⓿ B1751->B37 B0 0 B1718->B0 B34 b U c B1718->B34
In [20]:
t1.as_twa(True)
Out[20]:
[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 [21]:
t2 = spot.obligation_to_mtdswa('Fa & Fb & Fc')
t2.show('s')
Out[21]:
mtdswa Inf( ⓿ ) [Büchi] cluster_6 cluster_2 cluster_4 cluster_0 cluster_5 cluster_1 cluster_3 S0 Fa & Fb & Fc I->S0 B1769 c S0->B1769 S1 Fa & Fc B1772 c S1->B1772 S2 Fb & Fc B1773 c S2->B1773 S3 Fc B1774 c S3->B1774 S4 Fa & Fb B1768 a S4->B1768 S5 Fa B1771 a S5->B1771 S6 Fb B1767 b S6->B1767 B1773->B1767 B1764 b B1773->B1764 B1769->B1768 B1765 a B1769->B1765 B1 1 B1774->B1 B204 Fc B1774->B204 B1772->B1771 B1770 a B1772->B1770 B1771->B1 B397 Fa B1771->B397 B1768->B1767 B1766 b B1768->B1766 B403 b B1765->B403 B1765->B1764 B1770->B204 B34 Fa & Fc B1770->B34 B1767->B1 B1757 Fb B1767->B1757 B403->B34 B37 Fa & Fb & Fc B403->B37 B1764->B204 B35 Fb & Fc B1764->B35 B208 Fa & Fb B1766->B208 B1766->B397
In [22]:
t2.as_twa(True)
Out[22]:
[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 [23]:
t3 = spot.obligation_to_mtdswa('G(p1 <-> X!p1) | F(p0 & Xp1)')
t3.show('s')
Out[23]:
mtdswa Inf( ⓿ ) [Büchi] cluster_2 cluster_1 cluster_0 S0 G(p1 <-> X!p1) | F(p0 & Xp1) ⓿ I->S0 B1799 p1 S0->B1799 S1 F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) ⓿ B1801 p1 S1->B1801 S2 p1 | F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) ⓿ B1802 p1 S2->B1802 S3 F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) ⓿ B1803 p1 S3->B1803 S4 p1 | F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) ⓿ B1804 p1 S4->B1804 S5 F(p0 & Xp1) B1800 p0 S5->B1800 S6 p1 | F(p0 & Xp1) S6->B1802 B1802->B1800 B1 1 B1802->B1 B1798 p0 B1799->B1798 B1797 p0 B1799->B1797 B1804->B1797 B1804->B1 B1801->B1800 B1801->B1798 B1803->B1800 B1803->B1797 B397 F(p0 & Xp1) B1800->B397 B1757 p1 | F(p0 & Xp1) B1800->B1757 B208 p1 | F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) ⓿ B1798->B208 B204 F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) ⓿ B1798->B204 B34 F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) ⓿ B1797->B34 B35 p1 | F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) ⓿ B1797->B35
In [24]:
t3.as_twa(True).show('.s')
Out[24]:
[Büchi] cluster_0 cluster_1 cluster_2 cluster_3 cluster_4 0 G(p1 <-> X!p1) | F(p0 & Xp1) I->0 2 p1 | F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) 0->2 p0 & !p1 1 F(p0 & Xp1) | (p1 & G(p1 <-> X!p1)) 0->1 !p0 & !p1 3 F(p0 & Xp1) | (!p1 & G(p1 <-> X!p1)) 0->3 !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 2->7 p1 2->5 !p0 & !p1 2->6 p0 & !p1 1->5 !p0 & !p1 1->6 p0 & !p1 1->3 !p0 & p1 1->4 p0 & p1 3->5 !p0 & p1 3->6 p0 & p1 3->2 p0 & !p1 3->1 !p0 & !p1 4->7 p1 4->2 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 [25]:
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 [26]:
f = spot.formula('Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r))')
In [27]:
a = spot.obligation_to_mtdswa(f)
a.show('s')
Out[27]:
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 B5010 r S0->B5010 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B5012 r S1->B5012 S2 !Fr ⓿ B1811 r S2->B1811 S3 Fr -> !(!r U (!r & t)) ⓿ B5013 r S3->B5013 B0 0 B1811->B0 B35 !Fr ⓿ B1811->B35 B5009 s B5010->B5009 B1 1 B5010->B1 B5011 p B5012->B5011 B5012->B1 B2638 t B5013->B2638 B5013->B1 B4995 p B5009->B4995 B3752 p B5009->B3752 B5011->B2638 B2636 t B5011->B2636 B4995->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ B4995->B37 B3752->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B3752->B34 B204 Fr -> !(!r U (!r & t)) ⓿ B2638->B204 B2638->B35 B2636->B34 B2636->B35
In [28]:
b1 = a.as_twa(True, False)
b1
Out[28]:
[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 [29]:
b2 = f.translate('deterministic', xargs='new-oblig=0')
b2
Out[29]:
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 [30]:
spot.are_equivalent(b1, b2)
Out[30]:
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 [31]:
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 B5010 r S0->B5010 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B5012 r S1->B5012 S2 !Fr ⓿ B1811 r S2->B1811 S3 Fr -> !(!r U (!r & t)) ⓿ B5013 r S3->B5013 B0 0 B1811->B0 B35 !Fr ⓿ B1811->B35 B5009 s B5010->B5009 B1 1 B5010->B1 B5011 p B5012->B5011 B5012->B1 B2638 t B5013->B2638 B5013->B1 B4995 p B5009->B4995 B3752 p B5009->B3752 B5011->B2638 B2636 t B5011->B2636 B4995->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ B4995->B37 B3752->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B3752->B34 B204 Fr -> !(!r U (!r & t)) ⓿ B2638->B204 B2638->B35 B2636->B34 B2636->B35
'after:'
mtdswa Inf( ⓿ ) [Büchi] S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ I->S0 B5010 r S0->B5010 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B5158 r S1->B5158 S2 !Fr ⓿ B1811 r S2->B1811 B0 0 B1811->B0 B35 !Fr ⓿ B1811->B35 B2636 t B5158->B2636 B1 1 B5158->B1 B5009 s B5010->B5009 B5010->B1 B4995 p B5009->B4995 B3752 p B5009->B3752 B4995->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ B4995->B37 B3752->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B3752->B34 B2636->B35 B2636->B34
In [32]:
spot.are_equivalent(a.as_twa(), am.as_twa())
Out[32]:
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 [33]:
t4 = spot.obligation_to_mtdswa('a | Ga | F(b & Xa)')
t4.show('s')
Out[33]:
mtdswa Inf( ⓿ ) [Büchi] cluster_0 S0 a | Ga | F(b & Xa) ⓿ I->S0 B5161 a S0->B5161 S1 F(b & Xa) B1126 b S1->B1126 S2 a | F(b & Xa) S2->B5161 B5161->B1126 B1 1 B5161->B1 B34 F(b & Xa) B1126->B34 B35 a | F(b & Xa) B1126->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 different acceptance for those two states is not a threat to the validity of the translation: when the formula a | Ga | F(b & Xa) is translated into MTBDD, the superfluous Ga naturally disappear, 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 of acceptance prevent those two states from being merged by the DFA minimization algorithm.

In [34]:
spot.minimize_mtdswa(t4).show('s')
Out[34]:
mtdswa Inf( ⓿ ) [Büchi] cluster_0 S0 a | Ga | F(b & Xa) ⓿ I->S0 B5161 a S0->B5161 S1 F(b & Xa) B1126 b S1->B1126 S2 a | F(b & Xa) S2->B5161 B5161->B1126 B1 1 B5161->B1 B34 F(b & Xa) B1126->B34 B35 a | F(b & Xa) B1126->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 assign to 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 those ranks is quite straight forward and can be done in linear time: compute an SCC decomposition, then process it in a bottom-up way, assigning 0 or 1 to the bottommost depending on its acceptance. Moving up, the rank of a transient state is the maximum of the 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 [35]:
spot.loding_weak_ranking(t4)
Out[35]:
(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 [36]:
spot.loding_weak_ranking(t4, True)
Out[36]:
(2, 2, 2)
In [37]:
t4
Out[37]:
mtdswa Inf( ⓿ ) [Büchi] S0 a | Ga | F(b & Xa) I->S0 B5161 a S0->B5161 S1 F(b & Xa) B1126 b S1->B1126 S2 a | F(b & Xa) S2->B5161 B5161->B1126 B1 1 B5161->B1 B34 F(b & Xa) B1126->B34 B35 a | F(b & Xa) B1126->B35

And now we can minimize this like a DFA:

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

Here is a larger example:

In [39]:
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 B5182 c S0->B5182 S1 1 B5183 a S1->B5183 S2 2 B5185 c S2->B5185 S3 3 B1778 7 S3->B1778 S4 4 B5170 a S4->B5170 S5 5 B5187 c S5->B5187 S6 6 B5189 c S6->B5189 S7 7 B5176 a S7->B5176 S8 8 ⓿ S8->B5170 S9 9 B5190 c S9->B5190 S10 10 ⓿ B5191 c S10->B5191 B5188 a B5189->B5188 B5189->B5176 B5184 a B5185->B5184 B5185->B1778 B1126 b B5182->B1126 B5181 b B5182->B5181 B5186 a B5187->B5186 B5187->B5176 B5173 a B5191->B5173 B1 1 B5191->B1 B5190->B1 B1781 9 B5190->B1781 B0 0 B5183->B0 B208 4 B5183->B208 B397 5 B5184->B397 B1757 6 B5184->B1757 B5188->B397 B1783 10 ⓿ B5188->B1783 B5170->B0 B1780 8 ⓿ B5170->B1780 B5186->B397 B5186->B1781 B5173->B1781 B5173->B1783 B5176->B1 B5176->B1778 B34 1 B1126->B34 B35 2 B1126->B35 B204 3 B5181->B204 B5181->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 [40]:
print(t5.num_roots(), "roots, minimized to", spot.minimize_mtdswa(t5).num_roots(), "roots.")
11 roots, minimized to 11 roots.
In [41]:
t5rank = spot.loding_weak_ranking(t5, True); t5rank
Out[41]:
(3, 1, 3, 2, 1, 2, 3, 2, 1, 2, 3)
In [42]:
t5.show("0")
Out[42]:
mtdswa Inf( ⓿ ) [Büchi] S0 0 ⓿ I->S0 B5182 c S0->B5182 S1 1 ⓿ B5183 a S1->B5183 S2 2 ⓿ B5185 c S2->B5185 S3 3 B1778 7 S3->B1778 S4 4 ⓿ B5170 a S4->B5170 S5 5 B5187 c S5->B5187 S6 6 ⓿ B5189 c S6->B5189 S7 7 B5176 a S7->B5176 S8 8 ⓿ S8->B5170 S9 9 B5190 c S9->B5190 S10 10 ⓿ B5191 c S10->B5191 B5188 a B5189->B5188 B5189->B5176 B5184 a B5185->B5184 B5185->B1778 B1126 b B5182->B1126 B5181 b B5182->B5181 B5186 a B5187->B5186 B5187->B5176 B5173 a B5191->B5173 B1 1 B5191->B1 B5190->B1 B1781 9 B5190->B1781 B0 0 B5183->B0 B208 4 ⓿ B5183->B208 B397 5 B5184->B397 B1757 6 ⓿ B5184->B1757 B5188->B397 B1783 10 ⓿ B5188->B1783 B5170->B0 B1780 8 ⓿ B5170->B1780 B5186->B397 B5186->B1781 B5173->B1781 B5173->B1783 B5176->B1 B5176->B1778 B34 1 ⓿ B1126->B34 B35 2 ⓿ B1126->B35 B204 3 B5181->B204 B5181->B34
In [43]:
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 B5182 c S0->B5182 S1 1 ⓿ B198 a S1->B198 S2 2 ⓿ B5214 c S2->B5214 S3 3 B1757 6 S3->B1757 S4 4 B5217 c S4->B5217 S5 5 ⓿ B5219 c S5->B5219 S6 6 B5216 a S6->B5216 S7 7 B5203 c S7->B5203 S8 8 ⓿ B5205 c S8->B5205 B5219->B5216 B5218 a B5219->B5218 B5213 a B5214->B5213 B5214->B1757 B1126 b B5182->B1126 B5181 b B5182->B5181 B5217->B5216 B5215 a B5217->B5215 B5204 a B5205->B5204 B1 1 B5205->B1 B5203->B1 B1778 7 B5203->B1778 B5216->B1 B5216->B1757 B1780 8 ⓿ B5218->B1780 B208 4 B5218->B208 B0 0 B198->B0 B34 1 ⓿ B198->B34 B5215->B1778 B5215->B208 B5204->B1778 B5204->B1780 B5213->B208 B397 5 ⓿ B5213->B397 B35 2 ⓿ B1126->B35 B1126->B34 B204 3 B5181->B204 B5181->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 mentionned 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 alternation between accepting and rejecting SCCs that be reached from there, two states can only be merged if they have the same rank.

In [44]:
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 [45]:
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 B5010 r S0->B5010 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B5158 r S1->B5158 S2 !Fr ⓿ B1811 r S2->B1811 B0 0 B1811->B0 B35 !Fr ⓿ B1811->B35 B2636 t B5158->B2636 B1 1 B5158->B1 B5009 s B5010->B5009 B5010->B1 B4995 p B5009->B4995 B3752 p B5009->B3752 B4995->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ B4995->B37 B3752->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B3752->B34 B2636->B35 B2636->B34
'after'
mtdswa Inf( ⓿ ) [Büchi] S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ I->S0 B5231 r S0->B5231 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B5232 r S1->B5232 S2 !Fr ⓿ B5233 r S2->B5233 S3 1 ⓿ B204 1 ⓿ S3->B204 S4 0 B208 0 S4->B208 B5233->B208 B35 !Fr ⓿ B5233->B35 B5230 s B5231->B5230 B5231->B204 B2636 t B5232->B2636 B5232->B204 B5228 p B5230->B5228 B5229 p B5230->B5229 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ B5228->B37 B5228->B204 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B5229->B34 B5229->B204 B2636->B34 B2636->B35
In [46]:
am.sinks_as_constants(); am.show('s')
Out[46]:
mtdswa Inf( ⓿ ) [Büchi] cluster_2 cluster_1 cluster_0 S0 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ I->S0 B5010 r S0->B5010 S1 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B5158 r S1->B5158 S2 !Fr ⓿ B1811 r S2->B1811 B0 0 B1811->B0 B35 !Fr ⓿ B1811->B35 B2636 t B5158->B2636 B1 1 B5158->B1 B5009 s B5010->B5009 B5010->B1 B4995 p B5009->B4995 B3752 p B5009->B3752 B4995->B1 B37 Fr -> (!(!r & s & X(!r U (!r & t))) U (p | r)) ⓿ B4995->B37 B3752->B1 B34 Fr -> ((!(!r & s & X(!r U (!r & t))) U (p | r)) & !(!r U (!r & t))) ⓿ B3752->B34 B2636->B35 B2636->B34

Replacing constants by states my require changing the acceptance condition...

In [47]:
a1 = spot.dtwa_to_mtdswa(spot.translate('Ga', 'det', xargs="new-oblig=0")); a1
Out[47]:
mtdswa t [all] S0 0 I->S0 B1124 a S0->B1124 B0 0 B1124->B0 B37 0 B1124->B37
In [48]:
a1.sinks_as_states(); a1
Out[48]:
mtdswa Inf( ⓿ ) [Büchi] S0 0 ⓿ I->S0 B5207 a S0->B5207 S1 1 B34 1 S1->B34 B5207->B34 B37 0 ⓿ B5207->B37
In [49]:
# Coming back to constants do not change the acceptance condition back
a1.sinks_as_constants(); a1
Out[49]:
mtdswa Inf( ⓿ ) [Büchi] S0 0 ⓿ I->S0 B1124 a S0->B1124 B0 0 B1124->B0 B37 0 ⓿ B1124->B37
In [50]:
a2 = spot.obligation_to_mtdswa('Fa'); a2
Out[50]:
mtdswa Inf( ⓿ ) [Büchi] S0 Fa I->S0 B5162 a S0->B5162 B1 1 B5162->B1 B37 Fa B5162->B37
In [51]:
a2.sinks_as_states(); a2
Out[51]:
mtdswa Inf( ⓿ ) [Büchi] S0 Fa I->S0 B5193 a S0->B5193 S1 1 ⓿ B34 1 ⓿ S1->B34 B5193->B34 B37 Fa B5193->B37
In [52]:
# The optional argument asks to uses constants, but without removing the original sink states.
a2.sinks_as_constants(True); a2
Out[52]:
mtdswa Inf( ⓿ ) [Büchi] S0 Fa I->S0 B5162 a S0->B5162 S1 1 ⓿ B1 1 S1->B1 B5162->B1 B37 Fa B5162->B37
In [53]:
a2.sinks_as_constants(); a2
Out[53]:
mtdswa Inf( ⓿ ) [Büchi] S0 Fa I->S0 B5162 a S0->B5162 B1 1 B5162->B1 B37 Fa B5162->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 [54]:
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 B5238 a S0->B5238 B1 1 B5238->B1 B37 Ga | F!a ⓿ B5238->B37
mtdswa Inf( ⓿ ) [Büchi] S0 Ga | F!a ⓿ I->S0 B5238 a S0->B5238 B1 1 B5238->B1 B37 Ga | F!a ⓿ B5238->B37
In [55]:
# 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 B5207 a S0->B5207 S1 1 ⓿ B34 1 ⓿ S1->B34 B5207->B34 B37 Ga | F!a ⓿ B5207->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 B5238 a S0->B5238 B1 1 B5238->B1 B37 Ga | F!a ⓿ B5238->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 data structures and algorithms developped above:

  • translate obligation to MTSWA
  • convert sinks to states
  • compute Löding ranking and fix the acceptance of the transient states
  • minimize using the ranking as initial partition.
In [56]:
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 [57]:
oblig2minwdba_mtdswa('Ga & X(Fb) | F(d & X!a)')
Out[57]:
mtdswa Inf( ⓿ ) [Büchi] S0 F(d & X!a) | (Ga & XFb) I->S0 B5255 a S0->B5255 S1 F(d & X!a) B2774 d S1->B2774 S2 !a | F(d & X!a) B5263 a S2->B5263 S3 F(d & X!a) | (Ga & Fb) B5259 a S3->B5259 S4 !a | F(d & X!a) | (Ga & Fb) B5264 a S4->B5264 S5 Ga | F(d & X!a) ⓿ B5261 a S5->B5261 S6 !a | Ga | F(d & X!a) ⓿ B5265 a S6->B5265 S7 1 ⓿ B1778 1 ⓿ S7->B1778 B5257 d B5265->B5257 B5265->B1778 B5263->B2774 B5263->B1778 B5255->B2774 B2385 d B5255->B2385 B5261->B5257 B5261->B2774 B5258 b B5264->B5258 B5264->B1778 B5259->B5258 B5259->B2774 B5258->B5257 B5258->B2385 B397 Ga | F(d & X!a) ⓿ B5257->B397 B1757 !a | Ga | F(d & X!a) ⓿ B5257->B1757 B35 !a | F(d & X!a) B2774->B35 B34 F(d & X!a) B2774->B34 B204 F(d & X!a) | (Ga & Fb) B2385->B204 B208 !a | F(d & X!a) | (Ga & Fb) B2385->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 [58]:
def oblig2minwdba_old(f):
    return spot.translate(f, 'det', 'complete', 'low',
                          # Disable some specific preprocessings for fairer comparions.
                          # Eventually, the new translation will benefit from those as well.
                          xargs='relabel-bool=0,relabel-overlap=0,ltl-split=0,new-oblig=0')
In [59]:
oblig2minwdba_old('Ga & X(Fb) | F(d & X!a)')
Out[59]:
[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, allors translate() to return a TwA that has been translated using the MTDSWA-based contruction. 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 [60]:
def oblig2minwdba_new(f):
    return spot.translate(f, 'det', 'complete', 'low',
                          # Disable some specific preprocessings for fairer comparions.
                          # Eventually, the new translation will benefit from those as well.
                          xargs='relabel-bool=0,relabel-overlap=0,ltl-split=0,new-oblig=1')
In [61]:
oblig2minwdba_new('Ga & X(Fb) | F(d & X!a)')
Out[61]:
[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 will share the same BDD cache. So when testing scalable patterns it is very likely that some BDD operation can be cached between patterns. Some more serious benchmark (still showing trends comparable to those seen here) is included in a submitted paper.

In [62]:
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 [63]:
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, 8),
            (gen.LTL_CCJ_BETA, 16, 20),
            (gen.LTL_R_LEFT, 9, 13),
            (gen.LTL_R_RIGHT, 11, 15),
            (gen.LTL_U_LEFT, 6, 10),
            (gen.LTL_U_RIGHT, 10, 14),
            (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.005s      64   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6
 0.001s  0.001s  0.013s     128   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6 & Fp7
 0.001s  0.001s  0.063s     256   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6 & Fp7 & Fp8
 0.002s  0.004s  0.311s     512   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6 & Fp7 & Fp8 & Fp9
 0.007s  0.016s  4.206s    1024   Fp1 & Fp2 & Fp3 & Fp4 & Fp5 & Fp6 & Fp7 & Fp8 & Fp9 & Fp10
                               -- ccj-alpha 4…8
 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.035s      36   F(p1 & F(p2 & F(p3 & F(p4 & Fp5)))) & F(q1 & F(q2 & F(q3 & …
 0.000s  0.000s  0.207s      49   F(p1 & F(p2 & F(p3 & F(p4 & F(p5 & Fp6))))) & F(q1 & F(q2 &…
 0.000s  0.001s  1.506s      64   F(p1 & F(p2 & F(p3 & F(p4 & F(p5 & F(p6 & Fp7)))))) & F(q1 …
 0.001s  0.001s 10.064s      81   F(p1 & F(p2 & F(p3 & F(p4 & F(p5 & F(p6 & F(p7 & Fp8)))))))…
                               -- ccj-beta 16…20
 0.003s  0.001s  0.008s     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.009s     324   F(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p &…
 0.001s  0.001s  0.010s     361   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.012s     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.015s     441   F(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p & X(p &…
                               -- r-left 9…13
 0.002s  0.009s  0.039s     257   (((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p9
 0.006s  0.052s  0.176s     513   ((((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p9) …
 0.017s  0.199s  0.857s    1025   (((((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p9)…
 0.039s  0.954s  4.090s    2049   ((((((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p9…
 0.121s  5.038s 19.357s    4097   (((((((((((p1 R p2) R p3) R p4) R p5) R p6) R p7) R p8) R p…
                               -- r-right 11…15
 0.004s  0.002s  0.026s      12   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
 0.004s  0.004s  0.047s      13   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
 0.012s  0.009s  0.135s      14   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
 0.024s  0.026s  0.263s      15   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
 0.067s  0.068s  0.659s      16   p1 R (p2 R (p3 R (p4 R (p5 R (p6 R (p7 R (p8 R (p9 R (p10 R…
                               -- u-left 6…10
 0.000s  0.000s  0.005s      33   ((((p1 U p2) U p3) U p4) U p5) U p6
 0.001s  0.001s  0.102s      65   (((((p1 U p2) U p3) U p4) U p5) U p6) U p7
 0.001s  0.003s  0.197s     129   ((((((p1 U p2) U p3) U p4) U p5) U p6) U p7) U p8
 0.003s  0.011s  1.759s     257   (((((((p1 U p2) U p3) U p4) U p5) U p6) U p7) U p8) U p9
 0.007s  0.048s 19.936s     513   ((((((((p1 U p2) U p3) U p4) U p5) U p6) U p7) U p8) U p9) …
                               -- u-right 10…14
 0.002s  0.001s  0.041s      11   p1 U (p2 U (p3 U (p4 U (p5 U (p6 U (p7 U (p8 U (p9 U p10)))…
 0.003s  0.002s  0.216s      12   p1 U (p2 U (p3 U (p4 U (p5 U (p6 U (p7 U (p8 U (p9 U (p10 U…
 0.005s  0.004s  0.997s      13   p1 U (p2 U (p3 U (p4 U (p5 U (p6 U (p7 U (p8 U (p9 U (p10 U…
 0.010s  0.008s  5.552s      14   p1 U (p2 U (p3 U (p4 U (p5 U (p6 U (p7 U (p8 U (p9 U (p10 U…
 0.024s  0.019s 25.796s      15   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.001s  0.001s       6   G(p -> (q | Xq | XXq | XXXq | XXXXq))
 0.000s  0.001s  0.002s       7   G(p -> (q | Xq | XXq | XXXq | XXXXq | XXXXXq))
 0.001s  0.001s  0.005s       8   G(p -> (q | Xq | XXq | XXXq | XXXXq | XXXXXq | XXXXXXq))
 0.000s  0.001s  0.020s       9   G(p -> (q | Xq | XXq | XXXq | XXXXq | XXXXXq | XXXXXXq | XX…
 0.001s  0.001s  0.114s      10   G(p -> (q | Xq | XXq | XXXq | XXXXq | XXXXXq | XXXXXXq | XX…
                               -- tv-g1 5…9
 0.000s  0.001s  0.001s       6   G(p -> (q & Xq & XXq & XXXq & XXXXq))
 0.000s  0.001s  0.001s       7   G(p -> (q & Xq & XXq & XXXq & XXXXq & XXXXXq))
 0.000s  0.001s  0.001s       8   G(p -> (q & Xq & XXq & XXXq & XXXXq & XXXXXq & XXXXXXq))
 0.000s  0.001s  0.001s       9   G(p -> (q & Xq & XXq & XXXq & XXXXq & XXXXXq & XXXXXXq & XX…
 0.000s  0.001s  0.002s      10   G(p -> (q & Xq & XXq & XXXq & XXXXq & XXXXXq & XXXXXXq & XX…
                               -- tv-f2 5…9
 0.000s  0.001s  0.001s       6   G(p -> (q | X(q | X(q | X(q | Xq)))))
 0.000s  0.001s  0.002s       7   G(p -> (q | X(q | X(q | X(q | X(q | Xq))))))
 0.000s  0.001s  0.005s       8   G(p -> (q | X(q | X(q | X(q | X(q | X(q | Xq)))))))
 0.000s  0.001s  0.020s       9   G(p -> (q | X(q | X(q | X(q | X(q | X(q | X(q | Xq))))))))
 0.000s  0.001s  0.108s      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.001s  0.001s       6   G(p -> (q & X(q & X(q & X(q & Xq)))))
 0.001s  0.001s  0.001s       7   G(p -> (q & X(q & X(q & X(q & X(q & Xq))))))
 0.000s  0.001s  0.001s       8   G(p -> (q & X(q & X(q & X(q & X(q & X(q & Xq)))))))
 0.000s  0.001s  0.001s       9   G(p -> (q & X(q & X(q & X(q & X(q & X(q & X(q & Xq))))))))
 0.000s  0.001s  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.001s  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.004s  0.010s  0.085s      31   !oti & oto & (oy0 | oy1) & (o0b0 xor (ox0 & oy0)) & (o1b0 x…
                               -- dac-patterns
 0.000s  0.001s  0.001s       2   G!p0
 0.000s  0.000s  0.001s       4   Fp0 -> (!p1 U p0)
 0.000s  0.000s  0.001s       3   G(p0 -> G!p1)
 0.000s  0.000s  0.001s       3   G((p0 & !p1) -> (!p2 W p1))
 0.000s  0.000s  0.001s       2   Fp0
 0.000s  0.000s  0.001s       3   !p0 W (!p0 & p1)
 0.000s  0.000s  0.001s       3   G!p0 | F(p0 & Fp1)
 0.000s  0.001s  0.001s       3   G((p0 & !p1) -> (!p1 W (!p1 & p2)))
 0.000s  0.000s  0.001s       6   !p0 W (p0 W (!p0 W (p0 W G!p0)))
 0.000s  0.001s  0.001s       8   Fp0 -> ((!p0 & !p1) U (p0 | ((!p0 & p1) U (p0 | ((!p0 & !p1…
 0.000s  0.001s  0.001s       2   Gp0
 0.000s  0.000s  0.001s       4   Fp0 -> (p1 U p0)
 0.000s  0.000s  0.001s       3   G(p0 -> Gp1)
 0.000s  0.000s  0.001s       3   G((p0 & !p1) -> (p2 W p1))
 0.000s  0.001s  0.001s       3   !p0 W p1
 0.000s  0.000s  0.001s       4   Fp0 -> (!p1 U (p0 | p2))
 0.000s  0.000s  0.001s       3   G((p0 & !p1) -> (!p2 W (p1 | p3)))
 0.000s  0.001s  0.001s       4   Fp0 -> ((p1 -> (!p0 U (!p0 & p2))) U p0)
 0.000s  0.000s  0.001s       4   Fp0 -> (!p0 U (!p0 & p1 & X(!p0 U p2)))
 0.000s  0.001s  0.001s       5   Fp0 -> (!p1 U (p0 | (!p1 & p2 & X(!p1 U p3))))
 0.000s  0.000s  0.001s       4   F(p0 & XFp1) -> (!p0 U p2)
 0.000s  0.001s  0.001s       5   Fp0 -> (!(!p0 & p1 & X(!p0 U (!p0 & p2))) U (p0 | p3))
 0.000s  0.001s  0.001s       6   Fp0 -> (((p1 & X(!p0 U p2)) -> X(!p0 U (p2 & Fp3))) U p0)
 0.000s  0.001s  0.001s       5   Fp0 -> ((p1 -> (!p0 U (!p0 & p2 & X(!p0 U p3)))) U p0)
 0.000s  0.001s  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.005s  0.005s  0.005s      83   c & X(a1 | b1 | d) & Fd & G((a1 | b1) -> X(a2 | b2)) & G((a…
 7.018s  3.372s  0.101s    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.010s  0.010s  0.006s     148   c & Fd & X(d | y) & G(y -> X((a | b) & Xz)) & G(z -> X((a |…
 0.361s  0.292s  0.053s    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 those) has a 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 a 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've investigated the behavior of the old a 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 running propositional equivalence on all the LTL formulas that label BDD leaves, I've worked on that a bit. Adding some missing caches to propositional equivalence, as well as doing the prop.eq. in a second pass after the computation of $\mathit{tr}(\cdot)$ instead of doing it during the computation on each intermediate terminal and a few shortcuts. This actually reduced the runtime significantly, but as seen above, this is still very large. 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 has 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 BDD 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 the 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 [64]:
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 B1832979 b S0->B1832979 S1 F(b & Xa) | (a & G(a <-> X!a)) ⓿ B1832982 b S1->B1832982 S2 F(b & Xa) | (!a & G(a <-> X!a)) ⓿ B1832985 b S2->B1832985 S3 a | F(b & Xa) | (a & G(a <-> X!a)) ⓿ B1832987 b S3->B1832987 S4 a | F(b & Xa) | (!a & G(a <-> X!a)) ⓿ B1832973 b S4->B1832973 S5 F(b & Xa) B1832988 b S5->B1832988 S6 a | F(b & Xa) S6->B1832987 B397 F(b & Xa) B1832988->B397 B1757 a | F(b & Xa) B1832988->B1757 B1832983 a B1832985->B1832983 B1832984 a B1832985->B1832984 B1832977 a B1832979->B1832977 B1832978 a B1832979->B1832978 B1832972 a B1832973->B1832972 B1832971 a B1832973->B1832971 B1832981 a B1832982->B1832981 B1832980 a B1832982->B1832980 B5216 a B1832987->B5216 B1832986 a B1832987->B1832986 B34 F(b & Xa) | (a & G(a <-> X!a)) ⓿ B1832983->B34 B1832983->B397 B1832981->B1757 B208 a | F(b & Xa) | (!a & G(a <-> X!a)) ⓿ B1832981->B208 B1 1 B5216->B1 B5216->B1757 B1832984->B1757 B204 a | F(b & Xa) | (a & G(a <-> X!a)) ⓿ B1832984->B204 B1832972->B1 B1832972->B204 B1832986->B1 B1832986->B397 B1832980->B397 B35 F(b & Xa) | (!a & G(a <-> X!a)) ⓿ B1832980->B35 B1832977->B34 B1832977->B35 B1832971->B1 B1832971->B34 B1832978->B208 B1832978->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 step of the construction.

The constructed automaton is explored using a DFS that track 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 the 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 input or output. 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. We only need to compute the acceptance of one state per SCC at the moment where 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 [65]:
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 B1832957 b S0->B1832957 B1832955 a B1832957->B1832955 B1832956 a B1832957->B1832956 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) B1832956->B1783 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B7273401
step 2
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 B1832974 a B1832976->B1832974 B1832975 a B1832976->B1832975 B1832955 a B1832957->B1832955 B1832956 a B1832957->B1832956 B1 1 B1832974->B1 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832974->B1780 B1832955->B1780 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832956->B7273401 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) B1832956->B1783 B1832975->B1 B1832975->B1783
step 3
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832973 b S2->B1832973 B1832971 a B1832973->B1832971 B1832972 a B1832973->B1832972 B1832975 a B1832976->B1832975 B1832974 a B1832976->B1832974 B1832956 a B1832957->B1832956 B1832955 a B1832957->B1832955 B1 1 B1832971->B1 B34 F(b & Xa) B1832971->B34 B1832975->B1 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832975->B1783 B1832956->B1783 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832956->B7273401 B1832972->B1 B204 a | F(b & Xa) B1832972->B204 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B1832974->B1 B1832974->B1780
step 4
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832973 b S2->B1832973 S3 a | F(b & Xa) ❹ S3->B1832973 B1832971 a B1832973->B1832971 B1832972 a B1832973->B1832972 B1832975 a B1832976->B1832975 B1832974 a B1832976->B1832974 B1832956 a B1832957->B1832956 B1832955 a B1832957->B1832955 B1 1 B1832971->B1 B34 F(b & Xa) B1832971->B34 B1832975->B1 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832975->B1783 B1832956->B1783 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832956->B7273401 B1832972->B1 B204 a | F(b & Xa) ❹ B1832972->B204 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B1832974->B1 B1832974->B1780
step 5
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832973 b S2->B1832973 S3 a | F(b & Xa) ❹ S3->B1832973 S4 F(b & Xa) B1832952 b S4->B1832952 B1832972 a B1832973->B1832972 B1832971 a B1832973->B1832971 B1832955 a B1832957->B1832955 B1832956 a B1832957->B1832956 B1832974 a B1832976->B1832974 B1832975 a B1832976->B1832975 B34 F(b & Xa) B1832952->B34 B204 a | F(b & Xa) ❹ B1832952->B204 B1 1 B1832972->B1 B1832972->B204 B1832974->B1 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832974->B1780 B1832975->B1 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832975->B1783 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B1832955->B1780 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832956->B7273401 B1832956->B1783 B1832971->B1 B1832971->B34
step 7
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832973 b S2->B1832973 S3 a | F(b & Xa) ❹ S3->B1832973 S4 F(b & Xa) ❺ B1832952 b S4->B1832952 B1832972 a B1832973->B1832972 B1832971 a B1832973->B1832971 B1832955 a B1832957->B1832955 B1832956 a B1832957->B1832956 B1832974 a B1832976->B1832974 B1832975 a B1832976->B1832975 B34 F(b & Xa) ❺ B1832952->B34 B204 a | F(b & Xa) ❹ B1832952->B204 B1 1 B1832972->B1 B1832972->B204 B1832974->B1 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832974->B1780 B1832975->B1 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832975->B1783 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B1832955->B1780 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832956->B7273401 B1832956->B1783 B1832971->B1 B1832971->B34
step 9
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832973 b S2->B1832973 S3 a | F(b & Xa) ❹ S3->B1832973 S4 F(b & Xa) ❺ B1832952 b S4->B1832952 B1832972 a B1832973->B1832972 B1832971 a B1832973->B1832971 B1832955 a B1832957->B1832955 B1832956 a B1832957->B1832956 B1832974 a B1832976->B1832974 B1832975 a B1832976->B1832975 B34 F(b & Xa) ❺ B1832952->B34 B204 a | F(b & Xa) ❹ B1832952->B204 B1 1 B1832972->B1 B1832972->B204 B1832974->B1 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832974->B1780 B1832975->B1 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832975->B1783 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B1832955->B1780 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832956->B7273401 B1832956->B1783 B1832971->B1 B1832971->B34
step 10
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832973 b S2->B1832973 S3 a | F(b & Xa) ❹ S3->B1832973 S4 F(b & Xa) ❺ B1832952 b S4->B1832952 S5 F(b & Xa) | (a & G(a <-> X!a)) B1832966 b S5->B1832966 B1832972 a B1832973->B1832972 B1832971 a B1832973->B1832971 B1832955 a B1832957->B1832955 B1832956 a B1832957->B1832956 B1832974 a B1832976->B1832974 B1832975 a B1832976->B1832975 B1832965 a B1832966->B1832965 B1832964 a B1832966->B1832964 B204 a | F(b & Xa) ❹ B1832952->B204 B34 F(b & Xa) ❺ B1832952->B34 B1 1 B1832972->B1 B1832972->B204 B1832974->B1 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832974->B1780 B1832965->B204 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832965->B7273401 B1832955->B1780 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B1832971->B1 B1832971->B34 B1832975->B1 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832975->B1783 B1832956->B1783 B1832956->B7273401 B1832964->B34 B1832964->B1781
step 11
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832973 b S2->B1832973 S3 a | F(b & Xa) ❹ S3->B1832973 S4 F(b & Xa) ❺ B1832952 b S4->B1832952 S5 F(b & Xa) | (a & G(a <-> X!a)) B1832966 b S5->B1832966 S6 F(b & Xa) | (!a & G(a <-> X!a)) B1832970 b S6->B1832970 B1832968 a B1832970->B1832968 B1832969 a B1832970->B1832969 B1832971 a B1832973->B1832971 B1832972 a B1832973->B1832972 B1832955 a B1832957->B1832955 B1832956 a B1832957->B1832956 B1832965 a B1832966->B1832965 B1832964 a B1832966->B1832964 B1832975 a B1832976->B1832975 B1832974 a B1832976->B1832974 B204 a | F(b & Xa) ❹ B1832952->B204 B34 F(b & Xa) ❺ B1832952->B34 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832968->B1780 B1832968->B34 B1 1 B1832971->B1 B1832971->B34 B1832975->B1 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832975->B1783 B1832969->B204 B1832969->B1783 B1832972->B1 B1832972->B204 B1832965->B204 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832965->B7273401 B1832974->B1 B1832974->B1780 B1832955->B1780 B1781 F(b & Xa) | (!a & G(a <-> X!a)) B1832955->B1781 B1832964->B34 B1832964->B1781 B1832956->B1783 B1832956->B7273401
step 14
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) ❹ I->S0 B1832957 b S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832976 b S1->B1832976 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832973 b S2->B1832973 S3 a | F(b & Xa) ❹ S3->B1832973 S4 F(b & Xa) ❺ B1832952 b S4->B1832952 S5 F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832966 b S5->B1832966 S6 F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832970 b S6->B1832970 B1832968 a B1832970->B1832968 B1832969 a B1832970->B1832969 B1832971 a B1832973->B1832971 B1832972 a B1832973->B1832972 B1832955 a B1832957->B1832955 B1832956 a B1832957->B1832956 B1832965 a B1832966->B1832965 B1832964 a B1832966->B1832964 B1832975 a B1832976->B1832975 B1832974 a B1832976->B1832974 B204 a | F(b & Xa) ❹ B1832952->B204 B34 F(b & Xa) ❺ B1832952->B34 B1780 F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832968->B1780 B1832968->B34 B1 1 B1832971->B1 B1832971->B34 B1832975->B1 B1783 a | F(b & Xa) | (a & G(a <-> X!a)) ❹ B1832975->B1783 B1832969->B204 B1832969->B1783 B1832972->B1 B1832972->B204 B1832965->B204 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832965->B7273401 B1832974->B1 B1832974->B1780 B1832955->B1780 B1781 F(b & Xa) | (!a & G(a <-> X!a)) ❹ B1832955->B1781 B1832964->B34 B1832964->B1781 B1832956->B1783 B1832956->B7273401

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

Currently the output of spot.obligation_synthesis, without the debug option, is a crude output obtained remplacing all non-winning nodes (not green) by bddfalse, and for each output node (diamonds) 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 [66]:
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 B1832990 b S0->B1832990 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)) B1832992 b S5->B1832992 S6 F(b & Xa) | (!a & G(a <-> X!a)) B1832995 b S6->B1832995 B1832994 a B1832995->B1832994 B1832993 a B1832995->B1832993 B1832989 a B1832992->B1832989 B1832991 a B1832992->B1832991 B1832990->B1832989 B198 a B1832990->B198 B1832994->B0 B35 a | F(b & Xa) | (a & G(a <-> X!a)) B1832994->B35 B1832989->B0 B1757 F(b & Xa) | (!a & G(a <-> X!a)) B1832989->B1757 B1832991->B0 B204 a | F(b & Xa) B1832991->B204 B198->B0 B34 a | F(b & Xa) | (!a & G(a <-> X!a)) B198->B34 B1832993->B0 B397 F(b & Xa) | (a & G(a <-> X!a)) B1832993->B397 B4->B0 B1 1 B4->B1
In [67]:
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 [68]:
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 B1832957 a S0->B1832957 B1832955 b B1832957->B1832955 B1832956 b B1832957->B1832956 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1781 a | F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1781 B1783 F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B1783 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B7273401
step 2
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 a S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1833045 a S1->B1833045 B1832955 b B1833045->B1832955 B1 1 B1833045->B1 B1832957->B1832955 B1832956 b B1832957->B1832956 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1781 a | F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1781 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B7273401 B1783 F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B1783
step 3
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 a S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1833045 a S1->B1833045 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1833043 a S2->B1833043 B1833036 b B1833043->B1833036 B1 1 B1833043->B1 B1832955 b B1833045->B1832955 B1833045->B1 B1832957->B1832955 B1832956 b B1832957->B1832956 B34 F(b & Xa) B1833036->B34 B204 a | F(b & Xa) B1833036->B204 B1781 a | F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1781 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1783 F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B1783 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B7273401
step 4
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 a S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1833045 a S1->B1833045 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1833043 a S2->B1833043 S3 a | F(b & Xa) S3->B1833043 B1833036 b B1833043->B1833036 B1 1 B1833043->B1 B1832955 b B1833045->B1832955 B1833045->B1 B1832957->B1832955 B1832956 b B1832957->B1832956 B34 F(b & Xa) B1833036->B34 B204 a | F(b & Xa) B1833036->B204 B1781 a | F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1781 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1783 F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B1783 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B7273401
step 5
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 a S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1833045 a S1->B1833045 S2 a | F(b & Xa) | (a & G(a <-> X!a)) B1833043 a S2->B1833043 S3 a | F(b & Xa) S3->B1833043 S4 F(b & Xa) B1833036 b S4->B1833036 B1833043->B1833036 B1 1 B1833043->B1 B1832956 b B1832957->B1832956 B1832955 b B1832957->B1832955 B1833045->B1832955 B1833045->B1 B34 F(b & Xa) B1833036->B34 B204 a | F(b & Xa) B1833036->B204 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B7273401 B1783 F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B1783 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1781 a | F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1781
step 7
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 a S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1833045 a S1->B1833045 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❺ B1833043 a S2->B1833043 S3 a | F(b & Xa) ❺ S3->B1833043 S4 F(b & Xa) ❺ B1833036 b S4->B1833036 B1833043->B1833036 B1 1 B1833043->B1 B1832956 b B1832957->B1832956 B1832955 b B1832957->B1832955 B1833045->B1832955 B1833045->B1 B34 F(b & Xa) ❺ B1833036->B34 B204 a | F(b & Xa) ❺ B1833036->B204 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B7273401 B1783 F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B1783 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1781 a | F(b & Xa) | (a & G(a <-> X!a)) ❺ B1832955->B1781
step 9
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) I->S0 B1832957 a S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) B1833045 a S1->B1833045 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❺ B1833043 a S2->B1833043 S3 a | F(b & Xa) ❺ S3->B1833043 S4 F(b & Xa) ❺ B1833036 b S4->B1833036 B1833043->B1833036 B1 1 B1833043->B1 B1832956 b B1832957->B1832956 B1832955 b B1832957->B1832955 B1833045->B1832955 B1833045->B1 B34 F(b & Xa) ❺ B1833036->B34 B204 a | F(b & Xa) ❺ B1833036->B204 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B7273401 B1783 F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B1783 B1780 F(b & Xa) | (a & G(a <-> X!a)) B1832955->B1780 B1781 a | F(b & Xa) | (a & G(a <-> X!a)) ❺ B1832955->B1781
step 10
mtdswa t [all] S0 F(b & Xa) | G(a <-> X!a) ❺ I->S0 B1832957 a S0->B1832957 S1 a | F(b & Xa) | (!a & G(a <-> X!a)) ❺ B1833045 a S1->B1833045 S2 a | F(b & Xa) | (a & G(a <-> X!a)) ❺ B1833043 a S2->B1833043 S3 a | F(b & Xa) ❺ S3->B1833043 S4 F(b & Xa) ❺ B1833036 b S4->B1833036 S5 F(b & Xa) | (a & G(a <-> X!a)) ❺ B1833047 a S5->B1833047 B1833043->B1833036 B1 1 B1833043->B1 B1832956 b B1832957->B1832956 B1832955 b B1832957->B1832955 B1833045->B1832955 B1833045->B1 B1833047->B1832956 B1833047->B1833036 B1783 F(b & Xa) | (!a & G(a <-> X!a)) B1832956->B1783 B7273401 a | F(b & Xa) | (!a & G(a <-> X!a)) ❺ B1832956->B7273401 B1781 a | F(b & Xa) | (a & G(a <-> X!a)) ❺ B1832955->B1781 B1780 F(b & Xa) | (a & G(a <-> X!a)) ❺ B1832955->B1780 B34 F(b & Xa) ❺ B1833036->B34 B204 a | F(b & Xa) ❺ B1833036->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 [69]:
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 [70]:
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 B1833195 i0 S0->B1833195 B1833184 i1 B1833195->B1833184 B1833194 i1 B1833195->B1833194 B1833177 i2 B1833184->B1833177 B1833183 i2 B1833184->B1833183 B1833193 i2 B1833194->B1833193 B1833071 o1 B1833194->B1833071 B1833176 i4 B1833177->B1833176 B1833177->B1833071 B1833192 i3 B1833193->B1833192 B1833193->B1833071 B1833182 i4 B1833183->B1833182 B1833183->B1833071 B1833192->B1833176 B1833191 i4 B1833192->B1833191 B1833172 o1 B1833176->B1833172 B1833175 o1 B1833176->B1833175 B1833181 o1 B1833182->B1833181 B1833182->B1833172 B1833190 o1 B1833191->B1833190 B1833188 o1 B1833191->B1833188 B1833069 o0 B1833071->B1833069 B1833070 o0 B1833071->B1833070 B1833180 o0 B1833181->B1833180 B1833171 o0 B1833181->B1833171 B1833189 o0 B1833190->B1833189 B1833187 o0 B1833190->B1833187 B1833172->B1833171 B1833169 o0 B1833172->B1833169 B1833185 o0 B1833188->B1833185 B1833188->B1833187 B1833174 o0 B1833175->B1833174 B1833175->B1833171 B1833068 o2 B1833069->B1833068 B1833067 o2 B1833069->B1833067 B1833179 o2 B1833180->B1833179 B1833178 o2 B1833180->B1833178 B1833167 o2 B1833185->B1833167 B1833185->B1833179 B1833168 o2 B1833174->B1833168 B1833173 o2 B1833174->B1833173 B1833070->B1833068 B0 0 B1833070->B0 B1833170 o2 B1833171->B1833170 B1833171->B0 B1833169->B1833167 B1833169->B1833168 B1833189->B1833179 B1833189->B1833173 B1833186 o2 B1833187->B1833186 B1833187->B0 B1833068->B0 B34 1 B1833068->B34 B1833167->B0 B751875 2 B1833167->B751875 B1833168->B0 B37 0 B1833168->B37 B1833186->B0 B7273477 4 B1833186->B7273477 B1833179->B0 B7273475 3 B1833179->B7273475 B1833173->B0 B1833173->B7273475 B1833178->B0 B1833178->B37 B1833067->B0 B1833067->B34 B1833170->B0 B1833170->B751875
step 2
mtdswa t [all] S0 0 I->S0 B1833195 i0 S0->B1833195 S1 1 B1833225 i0 S1->B1833225 B1833222 i1 B1833225->B1833222 B1833224 i1 B1833225->B1833224 B1833184 i1 B1833195->B1833184 B1833194 i1 B1833195->B1833194 B1833221 i2 B1833222->B1833221 B1833220 i2 B1833222->B1833220 B1833177 i2 B1833184->B1833177 B1833183 i2 B1833184->B1833183 B1833193 i2 B1833194->B1833193 B1833071 o1 B1833194->B1833071 B1833223 i2 B1833224->B1833223 B1833224->B1833071 B1833219 i4 B1833221->B1833219 B1833182 i4 B1833221->B1833182 B1833192 i3 B1833193->B1833192 B1833193->B1833071 B1833223->B1833192 B1833223->B1833219 B1833176 i4 B1833177->B1833176 B1833177->B1833071 B1833183->B1833182 B1833183->B1833071 B1833220->B1833219 B1833220->B1833176 B1833192->B1833176 B1833191 i4 B1833192->B1833191 B1833216 o1 B1833219->B1833216 B1833218 o1 B1833219->B1833218 B1833172 o1 B1833176->B1833172 B1833175 o1 B1833176->B1833175 B1833182->B1833172 B1833181 o1 B1833182->B1833181 B1833188 o1 B1833191->B1833188 B1833190 o1 B1833191->B1833190 B1833070 o0 B1833071->B1833070 B1833069 o0 B1833071->B1833069 B1833171 o0 B1833172->B1833171 B1833169 o0 B1833172->B1833169 B1833187 o0 B1833188->B1833187 B1833185 o0 B1833188->B1833185 B1833190->B1833187 B1833189 o0 B1833190->B1833189 B1833181->B1833171 B1833180 o0 B1833181->B1833180 B1833216->B1833171 B1833215 o0 B1833216->B1833215 B1833174 o0 B1833175->B1833174 B1833175->B1833171 B1833217 o0 B1833218->B1833217 B1833218->B1833171 B1833068 o2 B1833070->B1833068 B0 0 B1833070->B0 B1833186 o2 B1833187->B1833186 B1833187->B0 B1833173 o2 B1833174->B1833173 B1833168 o2 B1833174->B1833168 B1833217->B1833173 B1833170 o2 B1833217->B1833170 B1833171->B1833170 B1833171->B0 B1833179 o2 B1833185->B1833179 B1833167 o2 B1833185->B1833167 B1833189->B1833173 B1833189->B1833179 B1833215->B1833170 B1833215->B1833167 B1833169->B1833168 B1833169->B1833167 B1833178 o2 B1833180->B1833178 B1833180->B1833179 B1833067 o2 B1833069->B1833067 B1833069->B1833068 B1833186->B0 B7273477 1 B1833186->B7273477 B1833173->B0 B7273475 4 B1833173->B7273475 B1833178->B0 B37 0 B1833178->B37 B1833067->B0 B34 2 B1833067->B34 B1833068->B0 B1833068->B34 B1833170->B0 B751875 3 B1833170->B751875 B1833179->B0 B1833179->B7273475 B1833168->B0 B1833168->B37 B1833167->B0 B1833167->B751875
step 3
mtdswa t [all] S0 0 I->S0 B1833195 i0 S0->B1833195 S1 1 B1833225 i0 S1->B1833225 S2 2 B1833235 i0 S2->B1833235 B1833234 i1 B1833235->B1833234 B1833233 i2 B1833235->B1833233 B1833222 i1 B1833225->B1833222 B1833224 i1 B1833225->B1833224 B1833194 i1 B1833195->B1833194 B1833184 i1 B1833195->B1833184 B1833234->B1833233 B1833071 o1 B1833234->B1833071 B1833193 i2 B1833194->B1833193 B1833194->B1833071 B1833220 i2 B1833222->B1833220 B1833221 i2 B1833222->B1833221 B1833177 i2 B1833184->B1833177 B1833183 i2 B1833184->B1833183 B1833223 i2 B1833224->B1833223 B1833224->B1833071 B1833232 i4 B1833233->B1833232 B1833233->B1833071 B1833176 i4 B1833177->B1833176 B1833177->B1833071 B1833182 i4 B1833183->B1833182 B1833183->B1833071 B1833192 i3 B1833223->B1833192 B1833219 i4 B1833223->B1833219 B1833220->B1833176 B1833220->B1833219 B1833193->B1833192 B1833193->B1833071 B1833221->B1833219 B1833221->B1833182 B1833192->B1833176 B1833191 i4 B1833192->B1833191 B1833230 o1 B1833232->B1833230 B1833231 o1 B1833232->B1833231 B1833172 o1 B1833176->B1833172 B1833175 o1 B1833176->B1833175 B1833190 o1 B1833191->B1833190 B1833188 o1 B1833191->B1833188 B1833218 o1 B1833219->B1833218 B1833216 o1 B1833219->B1833216 B1833182->B1833172 B1833181 o1 B1833182->B1833181 B1833171 o0 B1833230->B1833171 B1833185 o0 B1833230->B1833185 B1833172->B1833171 B1833169 o0 B1833172->B1833169 B1833218->B1833171 B1833217 o0 B1833218->B1833217 B1833181->B1833171 B1833180 o0 B1833181->B1833180 B1833231->B1833171 B1833189 o0 B1833231->B1833189 B1833069 o0 B1833071->B1833069 B1833070 o0 B1833071->B1833070 B1833190->B1833189 B1833187 o0 B1833190->B1833187 B1833175->B1833171 B1833174 o0 B1833175->B1833174 B1833216->B1833171 B1833215 o0 B1833216->B1833215 B1833188->B1833185 B1833188->B1833187 B1833170 o2 B1833171->B1833170 B0 0 B1833171->B0 B1833173 o2 B1833217->B1833173 B1833217->B1833170 B1833167 o2 B1833215->B1833167 B1833215->B1833170 B1833174->B1833173 B1833168 o2 B1833174->B1833168 B1833169->B1833167 B1833169->B1833168 B1833185->B1833167 B1833179 o2 B1833185->B1833179 B1833067 o2 B1833069->B1833067 B1833068 o2 B1833069->B1833068 B1833180->B1833179 B1833178 o2 B1833180->B1833178 B1833189->B1833173 B1833189->B1833179 B1833186 o2 B1833187->B1833186 B1833187->B0 B1833070->B1833068 B1833070->B0 B1833173->B0 B7273475 2 B1833173->B7273475 B1833167->B0 B751875 4 B1833167->B751875 B1833186->B0 B7273477 1 B1833186->B7273477 B1833067->B0 B34 3 B1833067->B34 B1833168->B0 B37 0 B1833168->B37 B1833170->B0 B1833170->B751875 B1833179->B0 B1833179->B7273475 B1833178->B0 B1833178->B37 B1833068->B0 B1833068->B34
step 4
mtdswa t [all] S0 0 I->S0 B1833195 i0 S0->B1833195 S1 1 B1833225 i0 S1->B1833225 S2 2 B1833235 i0 S2->B1833235 S3 3 B1833241 i0 S3->B1833241 B1833234 i1 B1833235->B1833234 B1833233 i2 B1833235->B1833233 B1833184 i1 B1833195->B1833184 B1833194 i1 B1833195->B1833194 B1833222 i1 B1833225->B1833222 B1833224 i1 B1833225->B1833224 B1833240 i1 B1833241->B1833240 B1833239 i2 B1833241->B1833239 B1833234->B1833233 B1833071 o1 B1833234->B1833071 B1833221 i2 B1833222->B1833221 B1833220 i2 B1833222->B1833220 B1833223 i2 B1833224->B1833223 B1833224->B1833071 B1833177 i2 B1833184->B1833177 B1833183 i2 B1833184->B1833183 B1833193 i2 B1833194->B1833193 B1833194->B1833071 B1833240->B1833239 B1833240->B1833071 B1833232 i4 B1833239->B1833232 B1833219 i4 B1833239->B1833219 B1833233->B1833232 B1833233->B1833071 B1833192 i3 B1833193->B1833192 B1833193->B1833071 B1833176 i4 B1833177->B1833176 B1833177->B1833071 B1833221->B1833219 B1833182 i4 B1833221->B1833182 B1833183->B1833182 B1833183->B1833071 B1833220->B1833219 B1833220->B1833176 B1833223->B1833192 B1833223->B1833219 B1833191 i4 B1833192->B1833191 B1833192->B1833176 B1833230 o1 B1833232->B1833230 B1833231 o1 B1833232->B1833231 B1833216 o1 B1833219->B1833216 B1833218 o1 B1833219->B1833218 B1833190 o1 B1833191->B1833190 B1833188 o1 B1833191->B1833188 B1833181 o1 B1833182->B1833181 B1833172 o1 B1833182->B1833172 B1833175 o1 B1833176->B1833175 B1833176->B1833172 B1833171 o0 B1833230->B1833171 B1833185 o0 B1833230->B1833185 B1833216->B1833171 B1833215 o0 B1833216->B1833215 B1833181->B1833171 B1833180 o0 B1833181->B1833180 B1833175->B1833171 B1833174 o0 B1833175->B1833174 B1833231->B1833171 B1833189 o0 B1833231->B1833189 B1833069 o0 B1833071->B1833069 B1833070 o0 B1833071->B1833070 B1833190->B1833189 B1833187 o0 B1833190->B1833187 B1833218->B1833171 B1833217 o0 B1833218->B1833217 B1833172->B1833171 B1833169 o0 B1833172->B1833169 B1833188->B1833185 B1833188->B1833187 B1833170 o2 B1833171->B1833170 B0 0 B1833171->B0 B1833178 o2 B1833180->B1833178 B1833179 o2 B1833180->B1833179 B1833168 o2 B1833169->B1833168 B1833167 o2 B1833169->B1833167 B1833173 o2 B1833217->B1833173 B1833217->B1833170 B1833215->B1833170 B1833215->B1833167 B1833185->B1833167 B1833185->B1833179 B1833068 o2 B1833069->B1833068 B1833067 o2 B1833069->B1833067 B1833174->B1833168 B1833174->B1833173 B1833189->B1833173 B1833189->B1833179 B1833186 o2 B1833187->B1833186 B1833187->B0 B1833070->B1833068 B1833070->B0 B1833168->B0 B37 0 B1833168->B37 B1833173->B0 B7273475 2 B1833173->B7273475 B1833186->B0 B7273477 1 B1833186->B7273477 B1833178->B0 B1833178->B37 B1833170->B0 B751875 3 B1833170->B751875 B1833167->B0 B1833167->B751875 B1833179->B0 B1833179->B7273475 B1833068->B0 B34 4 B1833068->B34 B1833067->B0 B1833067->B34
step 5
mtdswa t [all] S0 0 I->S0 B1833195 i0 S0->B1833195 S1 1 B1833225 i0 S1->B1833225 S2 2 B1833235 i0 S2->B1833235 S3 3 B1833241 i0 S3->B1833241 S4 4 B1833071 o1 S4->B1833071 B1833234 i1 B1833235->B1833234 B1833233 i2 B1833235->B1833233 B1833184 i1 B1833195->B1833184 B1833194 i1 B1833195->B1833194 B1833240 i1 B1833241->B1833240 B1833239 i2 B1833241->B1833239 B1833224 i1 B1833225->B1833224 B1833222 i1 B1833225->B1833222 B1833234->B1833233 B1833234->B1833071 B1833183 i2 B1833184->B1833183 B1833177 i2 B1833184->B1833177 B1833223 i2 B1833224->B1833223 B1833224->B1833071 B1833220 i2 B1833222->B1833220 B1833221 i2 B1833222->B1833221 B1833193 i2 B1833194->B1833193 B1833194->B1833071 B1833240->B1833239 B1833240->B1833071 B1833232 i4 B1833239->B1833232 B1833219 i4 B1833239->B1833219 B1833182 i4 B1833183->B1833182 B1833183->B1833071 B1833192 i3 B1833193->B1833192 B1833193->B1833071 B1833220->B1833219 B1833176 i4 B1833220->B1833176 B1833233->B1833232 B1833233->B1833071 B1833221->B1833219 B1833221->B1833182 B1833177->B1833176 B1833177->B1833071 B1833223->B1833192 B1833223->B1833219 B1833191 i4 B1833192->B1833191 B1833192->B1833176 B1833230 o1 B1833232->B1833230 B1833231 o1 B1833232->B1833231 B1833188 o1 B1833191->B1833188 B1833190 o1 B1833191->B1833190 B1833218 o1 B1833219->B1833218 B1833216 o1 B1833219->B1833216 B1833181 o1 B1833182->B1833181 B1833172 o1 B1833182->B1833172 B1833175 o1 B1833176->B1833175 B1833176->B1833172 B1833070 o0 B1833071->B1833070 B1833069 o0 B1833071->B1833069 B1833187 o0 B1833188->B1833187 B1833185 o0 B1833188->B1833185 B1833171 o0 B1833181->B1833171 B1833180 o0 B1833181->B1833180 B1833175->B1833171 B1833174 o0 B1833175->B1833174 B1833230->B1833171 B1833230->B1833185 B1833231->B1833171 B1833189 o0 B1833231->B1833189 B1833218->B1833171 B1833217 o0 B1833218->B1833217 B1833190->B1833187 B1833190->B1833189 B1833172->B1833171 B1833169 o0 B1833172->B1833169 B1833216->B1833171 B1833215 o0 B1833216->B1833215 B1833068 o2 B1833070->B1833068 B0 0 B1833070->B0 B1833170 o2 B1833171->B1833170 B1833171->B0 B1833167 o2 B1833169->B1833167 B1833168 o2 B1833169->B1833168 B1833215->B1833170 B1833215->B1833167 B1833186 o2 B1833187->B1833186 B1833187->B0 B1833185->B1833167 B1833179 o2 B1833185->B1833179 B1833069->B1833068 B1833067 o2 B1833069->B1833067 B1833174->B1833168 B1833173 o2 B1833174->B1833173 B1833178 o2 B1833180->B1833178 B1833180->B1833179 B1833217->B1833170 B1833217->B1833173 B1833189->B1833173 B1833189->B1833179 B1833170->B0 B751875 3 B1833170->B751875 B1833167->B0 B1833167->B751875 B1833178->B0 B37 0 B1833178->B37 B1833168->B0 B1833168->B37 B1833173->B0 B7273475 2 B1833173->B7273475 B1833068->B0 B34 4 B1833068->B34 B1833186->B0 B7273477 1 B1833186->B7273477 B1833067->B0 B1833067->B34 B1833179->B0 B1833179->B7273475
step 6
mtdswa t [all] S0 0 I->S0 B1833195 i0 S0->B1833195 S1 1 B1833225 i0 S1->B1833225 S2 2 B1833235 i0 S2->B1833235 S3 3 B1833241 i0 S3->B1833241 S4 4 ❹ B1833071 o1 S4->B1833071 B1833234 i1 B1833235->B1833234 B1833233 i2 B1833235->B1833233 B1833184 i1 B1833195->B1833184 B1833194 i1 B1833195->B1833194 B1833240 i1 B1833241->B1833240 B1833239 i2 B1833241->B1833239 B1833224 i1 B1833225->B1833224 B1833222 i1 B1833225->B1833222 B1833234->B1833233 B1833234->B1833071 B1833183 i2 B1833184->B1833183 B1833177 i2 B1833184->B1833177 B1833223 i2 B1833224->B1833223 B1833224->B1833071 B1833220 i2 B1833222->B1833220 B1833221 i2 B1833222->B1833221 B1833193 i2 B1833194->B1833193 B1833194->B1833071 B1833240->B1833239 B1833240->B1833071 B1833232 i4 B1833239->B1833232 B1833219 i4 B1833239->B1833219 B1833182 i4 B1833183->B1833182 B1833183->B1833071 B1833192 i3 B1833193->B1833192 B1833193->B1833071 B1833220->B1833219 B1833176 i4 B1833220->B1833176 B1833233->B1833232 B1833233->B1833071 B1833221->B1833219 B1833221->B1833182 B1833177->B1833176 B1833177->B1833071 B1833223->B1833192 B1833223->B1833219 B1833191 i4 B1833192->B1833191 B1833192->B1833176 B1833230 o1 B1833232->B1833230 B1833231 o1 B1833232->B1833231 B1833188 o1 B1833191->B1833188 B1833190 o1 B1833191->B1833190 B1833218 o1 B1833219->B1833218 B1833216 o1 B1833219->B1833216 B1833181 o1 B1833182->B1833181 B1833172 o1 B1833182->B1833172 B1833175 o1 B1833176->B1833175 B1833176->B1833172 B1833070 o0 B1833071->B1833070 B1833069 o0 B1833071->B1833069 B1833187 o0 B1833188->B1833187 B1833185 o0 B1833188->B1833185 B1833171 o0 B1833181->B1833171 B1833180 o0 B1833181->B1833180 B1833175->B1833171 B1833174 o0 B1833175->B1833174 B1833230->B1833171 B1833230->B1833185 B1833231->B1833171 B1833189 o0 B1833231->B1833189 B1833218->B1833171 B1833217 o0 B1833218->B1833217 B1833190->B1833187 B1833190->B1833189 B1833172->B1833171 B1833169 o0 B1833172->B1833169 B1833216->B1833171 B1833215 o0 B1833216->B1833215 B1833068 o2 B1833070->B1833068 B0 0 B1833070->B0 B1833170 o2 B1833171->B1833170 B1833171->B0 B1833167 o2 B1833169->B1833167 B1833168 o2 B1833169->B1833168 B1833215->B1833170 B1833215->B1833167 B1833186 o2 B1833187->B1833186 B1833187->B0 B1833185->B1833167 B1833179 o2 B1833185->B1833179 B1833069->B1833068 B1833067 o2 B1833069->B1833067 B1833174->B1833168 B1833173 o2 B1833174->B1833173 B1833178 o2 B1833180->B1833178 B1833180->B1833179 B1833217->B1833170 B1833217->B1833173 B1833189->B1833173 B1833189->B1833179 B1833170->B0 B751875 3 B1833170->B751875 B1833167->B0 B1833167->B751875 B1833178->B0 B37 0 B1833178->B37 B1833168->B0 B1833168->B37 B1833173->B0 B7273475 2 B1833173->B7273475 B1833068->B0 B34 4 ❹ B1833068->B34 B1833186->B0 B7273477 1 B1833186->B7273477 B1833067->B0 B1833067->B34 B1833179->B0 B1833179->B7273475
step 9
mtdswa t [all] S0 0 ❺ I->S0 B1833195 i0 S0->B1833195 S1 1 ❺ B1833225 i0 S1->B1833225 S2 2 ❺ B1833235 i0 S2->B1833235 S3 3 ❺ B1833241 i0 S3->B1833241 S4 4 ❹ B1833071 o1 S4->B1833071 B1833234 i1 B1833235->B1833234 B1833233 i2 B1833235->B1833233 B1833184 i1 B1833195->B1833184 B1833194 i1 B1833195->B1833194 B1833240 i1 B1833241->B1833240 B1833239 i2 B1833241->B1833239 B1833224 i1 B1833225->B1833224 B1833222 i1 B1833225->B1833222 B1833234->B1833233 B1833234->B1833071 B1833183 i2 B1833184->B1833183 B1833177 i2 B1833184->B1833177 B1833223 i2 B1833224->B1833223 B1833224->B1833071 B1833220 i2 B1833222->B1833220 B1833221 i2 B1833222->B1833221 B1833193 i2 B1833194->B1833193 B1833194->B1833071 B1833240->B1833239 B1833240->B1833071 B1833232 i4 B1833239->B1833232 B1833219 i4 B1833239->B1833219 B1833182 i4 B1833183->B1833182 B1833183->B1833071 B1833192 i3 B1833193->B1833192 B1833193->B1833071 B1833220->B1833219 B1833176 i4 B1833220->B1833176 B1833233->B1833232 B1833233->B1833071 B1833221->B1833219 B1833221->B1833182 B1833177->B1833176 B1833177->B1833071 B1833223->B1833192 B1833223->B1833219 B1833191 i4 B1833192->B1833191 B1833192->B1833176 B1833230 o1 B1833232->B1833230 B1833231 o1 B1833232->B1833231 B1833188 o1 B1833191->B1833188 B1833190 o1 B1833191->B1833190 B1833218 o1 B1833219->B1833218 B1833216 o1 B1833219->B1833216 B1833181 o1 B1833182->B1833181 B1833172 o1 B1833182->B1833172 B1833175 o1 B1833176->B1833175 B1833176->B1833172 B1833070 o0 B1833071->B1833070 B1833069 o0 B1833071->B1833069 B1833187 o0 B1833188->B1833187 B1833185 o0 B1833188->B1833185 B1833171 o0 B1833181->B1833171 B1833180 o0 B1833181->B1833180 B1833175->B1833171 B1833174 o0 B1833175->B1833174 B1833230->B1833171 B1833230->B1833185 B1833231->B1833171 B1833189 o0 B1833231->B1833189 B1833218->B1833171 B1833217 o0 B1833218->B1833217 B1833190->B1833187 B1833190->B1833189 B1833172->B1833171 B1833169 o0 B1833172->B1833169 B1833216->B1833171 B1833215 o0 B1833216->B1833215 B1833068 o2 B1833070->B1833068 B0 0 B1833070->B0 B1833170 o2 B1833171->B1833170 B1833171->B0 B1833167 o2 B1833169->B1833167 B1833168 o2 B1833169->B1833168 B1833215->B1833170 B1833215->B1833167 B1833186 o2 B1833187->B1833186 B1833187->B0 B1833185->B1833167 B1833179 o2 B1833185->B1833179 B1833069->B1833068 B1833067 o2 B1833069->B1833067 B1833174->B1833168 B1833173 o2 B1833174->B1833173 B1833178 o2 B1833180->B1833178 B1833180->B1833179 B1833217->B1833170 B1833217->B1833173 B1833189->B1833173 B1833189->B1833179 B1833170->B0 B751875 3 ❺ B1833170->B751875 B1833167->B0 B1833167->B751875 B1833178->B0 B37 0 ❺ B1833178->B37 B1833168->B0 B1833168->B37 B1833173->B0 B7273475 2 ❺ B1833173->B7273475 B1833068->B0 B34 4 ❹ B1833068->B34 B1833186->B0 B7273477 1 ❺ B1833186->B7273477 B1833067->B0 B1833067->B34 B1833179->B0 B1833179->B7273475
output
mtdswa f [none] S0 0 I->S0 B0 0 S0->B0
In [ ]: