{
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.4.3+"
},
"name": "",
"signature": "sha256:3d73c80c329ce7b32e9edaa2470fce5e27744462f43431affbbb771deb92aff2"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": true,
"input": [
"from IPython.display import display\n",
"import spot\n",
"spot.setup()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook demonstrates how to use the `decompose_strength()` function to split an automaton in up to three automata capturing different behaviors. This is based on the paper [Strength-based decomposition of the property B\u00fcchi automaton for faster model checking](https://www.lrde.epita.fr/~adl/dl/adl/renault.13.tacas.pdf) (TACAS'13).\n",
"\n",
"This page uses the Python bindings, but the same decompositions can be performed from the shell using [`autfilt`](https://spot.lrde.epita.fr/autfilt.html) and its `--decompose-strength` option.\n",
"\n",
"# Basics\n",
"\n",
"Let's define the following strengths of SCCs:\n",
"\n",
"- an accepting SCC is **weak** if all its transitions belong to the same acceptance sets\n",
"- an accepting SCC is **terminal** if it is *weak* and complete (i.e. from any state, you can stay in the SCC by reading any word)\n",
"- an accepting SCC is **strictly weak** if it is *weak* and not complete (in other words: *weak* but not *terminal*)\n",
"- an accepting SCC is **strong** if it is not weak.\n",
"\n",
"The strengths **strong**, **stricly weak**, and **terminal** define a partition of all accepting SCCs. The following B\u00fcchi automaton has 4 SCCs, and its 3 accepting SCCs show an example of each strength."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"aut = spot.translate('(Ga -> Gb) W c')\n",
"aut.show('.sa')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 2,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `decompose_strength()` function takes an automaton, and a string specifying which strength to preserve. \n",
"\n",
"The letters used for this specification are as follows:\n",
"\n",
"- `t`: terminal\n",
"- `w`: strictly weak\n",
"- `s`: strong\n",
"\n",
"For instance if we want to preserve only the **strictly weak** part of this automaton, we should get only the SCC with the self-loop on $b$, and the SCC above it so that we can reach it. However the SCC above is not stricly weak, so it should not accept any word in the new automaton."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"sweak = spot.decompose_strength(aut, 'w')\n",
"sweak.show('.sa')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 3,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similarly, we can extract all the behaviors captured by the **terminal** part of the automaton:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"term = spot.decompose_strength(aut, 't')\n",
"term.show('.sa')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 4,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is the strong part:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"strong = spot.decompose_strength(aut, 's')\n",
"strong.show('.sa')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 5,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 5
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The union of these three automata recognize the same language as the original automaton.\n",
"\n",
"\n",
"The application proposed in the aforementioned TACAS'13 paper is for parallelizing model checking. Instead of testing the emptiness of the product between `aut` and a system, one could test the emptiness **3** products in parallel: each with a sub-automaton of different strength. Model checking using weak and terminal automata can be done with much more simpler emptiness check procedures than needed for the general case. So in effect, we have isolated the \"hard\" (i.e. strong) part of the original automaton in a smaller automaton, and we only need to use a full-fledged emptiness check for this case.\n",
"\n",
"An additional bonus is that it is possible that the simplification algorithms will do a better job at simplifying the sub-automata than at simplifying the original `aut`. For instance here the `strong` automaton can be further simplified:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"strong.postprocess('small')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 6,
"svg": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text": [
" *' at 0x7fe3f46e3f00> >"
]
}
],
"prompt_number": 6
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Multi-strength extraction\n",
"\n",
"The string passed to `decompose_strength()` can include multiple letters to extract multiple strengths at once."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for opt in ('sw', 'st', 'wt'):\n",
" a = spot.decompose_strength(aut, opt)\n",
" a.set_name(\"option: \" + opt)\n",
" display(a.show('.asn'))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Generalized acceptance\n",
"\n",
"There is (almost, see below) nothing that prevents the above decomposition to work with other types of acceptance.\n",
"\n",
"## Rabin\n",
"\n",
"The following Rabin automaton was generated with\n",
"\n",
" ltldo -f '(Ga -> Gb) W c' 'ltl2dstar --ltl2nba=spin:ltl2tgba@-Ds' -H | autfilt -H --merge-transitions\n",
" \n",
"(The `autfilt -H --merge-transitions` pass is just here to reduce the size of the file and make the automaton more readable.)"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"aut = spot.automaton(\"\"\"\n",
"HOA: v1\n",
"States: 9\n",
"Start: 2\n",
"AP: 3 \"a\" \"b\" \"c\"\n",
"acc-name: Rabin 2\n",
"Acceptance: 4 (Fin(0) & Inf(1)) | (Fin(2) & Inf(3))\n",
"properties: trans-labels explicit-labels state-acc complete\n",
"properties: deterministic\n",
"--BODY--\n",
"State: 0 {2}\n",
"[0&!2] 0\n",
"[0&2] 1\n",
"[!0&!2] 5\n",
"[!0&2] 6\n",
"State: 1 {2}\n",
"[0] 1\n",
"[!0] 6\n",
"State: 2 {2}\n",
"[0&!1&!2] 3\n",
"[0&1&!2] 4\n",
"[!0&!2] 5\n",
"[2] 6\n",
"State: 3 {1 2}\n",
"[0&!2] 0\n",
"[0&2] 1\n",
"[!0&!2] 5\n",
"[!0&2] 6\n",
"State: 4 {1 2}\n",
"[0&!1&!2] 0\n",
"[0&!1&2] 1\n",
"[!0&!2] 5\n",
"[!0&2] 6\n",
"[0&1&!2] 7\n",
"[0&1&2] 8\n",
"State: 5 {1 2}\n",
"[0&!1&!2] 0\n",
"[!0&!2] 5\n",
"[2] 6\n",
"[0&1&!2] 7\n",
"State: 6 {1 2}\n",
"[t] 6\n",
"State: 7 {3}\n",
"[0&!1&!2] 0\n",
"[0&!1&2] 1\n",
"[!0&!2] 5\n",
"[!0&2] 6\n",
"[0&1&!2] 7\n",
"[0&1&2] 8\n",
"State: 8 {3}\n",
"[0&!1] 1\n",
"[!0] 6\n",
"[0&1] 8\n",
"--END--\n",
"\"\"\")\n",
"aut.show(\".as\")"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 8,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 8
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's decompose it into three strengths:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for (name, opt) in (('terminal', 't'), ('strictly weak', 'w'), ('strong', 's')):\n",
" a = spot.decompose_strength(aut, opt)\n",
" a.set_name(name)\n",
" display(a.show('.basn'))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 9
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note how the two weak automata (i.e., stricly weak and terminal) are now using a B\u00fcchi acceptance condition (because that is sufficient for weak automata) while the strong automaton inherited the original acceptance condition.\n",
"\n",
"When extracting multiple strengths and one of the strength is **strong**, we preserve the original acceptance. For instance extracting **strong** and **terminal** gives the following automaton, where only **stricly weak** SCCs have become rejecting."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"spot.decompose_strength(aut, \"st\").show(\".as\")"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 10,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 10
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The weak automata seem to be good candidates for further simplification. Let's add a call to `postprocess()` to out decomposition loop, trying to preserve the determinism and state-based acceptance of the original automaton."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for (name, opt) in (('terminal', 't'), ('strictly weak', 'w'), ('strong', 's')):\n",
" a = spot.decompose_strength(aut, opt).postprocess('deterministic', 'SBAcc')\n",
" a.set_name(name)\n",
" display(a.show('.basn'))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 11
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Streett\n",
"\n",
"Since this notebook also serves as a test suite, let's try a Streett automaton. This one was generated with\n",
"\n",
" ltldo -f '(Ga -> Gb) W c' 'ltl2dstar --automata=streett --ltl2nba=spin:ltl2tgba@-Ds' -H | \n",
" autfilt -H --merge-transitions"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"aut = spot.automaton(\"\"\"\n",
"HOA: v1\n",
"States: 8\n",
"Start: 7\n",
"AP: 3 \"a\" \"b\" \"c\"\n",
"acc-name: Streett 2\n",
"Acceptance: 4 (Fin(0) | Inf(1)) & (Fin(2) | Inf(3))\n",
"properties: trans-labels explicit-labels state-acc complete\n",
"properties: deterministic\n",
"--BODY--\n",
"State: 0 {2}\n",
"[0&1] 0\n",
"[0&!1] 3\n",
"[!0] 4\n",
"State: 1 {2}\n",
"[0&1&2] 0\n",
"[0&1&!2] 1\n",
"[0&!1&!2] 2\n",
"[0&!1&2] 3\n",
"[!0&2] 4\n",
"[!0&!2] 7\n",
"State: 2 {2}\n",
"[0&1&!2] 1\n",
"[0&!1&!2] 2\n",
"[0&2] 3\n",
"[!0&2] 4\n",
"[!0&!2] 7\n",
"State: 3 {0 3}\n",
"[0] 3\n",
"[!0] 4\n",
"State: 4 {1 3}\n",
"[t] 4\n",
"State: 5 {3}\n",
"[0&!1] 3\n",
"[!0] 4\n",
"[0&1] 5\n",
"State: 6 {3}\n",
"[0&!1&!2] 2\n",
"[0&!1&2] 3\n",
"[!0&2] 4\n",
"[0&1&2] 5\n",
"[0&1&!2] 6\n",
"[!0&!2] 7\n",
"State: 7 {3}\n",
"[0&!1&!2] 2\n",
"[2] 4\n",
"[0&1&!2] 6\n",
"[!0&!2] 7\n",
"--END--\n",
"\"\"\")\n",
"aut.show(\".as\")"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 12,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 12
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for (name, opt) in (('terminal', 't'), ('strictly weak', 'w'), ('strong', 's')):\n",
" a = spot.decompose_strength(aut, opt)\n",
" a.set_name(name)\n",
" display(a.show('.basn'))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 13
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The subtlety of Streett acceptance is that if a path that does not visit any accepting set infinitely often *is* accepting. So when disabling SCCs, we must be careful to label them with a combination of rejecting acceptance sets.\n",
"\n",
"This is easy to understand using an example. In the following extraction of the **strong** and **terminal** parts, the rejecting SCCs (that were either rejecting or strictly weak originally) have been labeled by the same acceptance sets, to ensure that they are rejected."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"spot.decompose_strength(aut, 'st').show('.as')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 14,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 14
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Corner cases\n",
"\n",
"Here is the reason we said there is almost nothing preventing the decomposition to work for any acceptance condition.\n",
"To disable SCCs like above, the `decompose_strength()` must be able to find a set of acceptance sets to put those SCCs\n",
"in such that they will be rejected. This is impossible if the acceptance condition is always satisifiable.\n",
"\n",
"This include acceptances like `Acceptance: 0 t`, but also trickier ones like `Acceptance: 1 Inf(0) | Fin(0)` that you can make as complex as you fancy.\n",
"\n",
"### `Acceptance: 0 t`\n",
"\n",
"This is the least problematic case. Because automata with this acceptance are necessary weak, there is reason to extract the **strong** strength, and extracting the other two strengths will work as expected:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"aut = spot.translate('(Gb|c) R a', 'any'); aut.show('.as')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 15,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 15
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# There is no strong part for this automaton\n",
"assert spot.decompose_strength(aut, 's') is None"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 16
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for opt in ('w', 't'):\n",
" display(spot.decompose_strength(aut, opt).show('.bas'))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 17
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If we try to extract multiple strengths and include the (empty) strong part, this request will simply be ignored:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"spot.decompose_strength(aut, 'st').show('.bas')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 18,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 18
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the above is exactly the output of `decompose_strength(aut, 't')`. The `'s'` flag was actively ignored. If `'s'` had not been ignored an the automaton processed as if its strong part had to be preserved, the original acceptance conditions would have been used, and this would have prevented the disabling of the initial SCC."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `Acceptance: 1 Inf(0) | Fin(0)`\n",
"\n",
"This acceptance could be replaced by `Acceptance: 0 t` without altering the language of the automaton. However its use of acceptance sets allows us to define some automata with strong SCCs."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"aut = spot.automaton(\"\"\"\n",
"HOA: v1\n",
"States: 4\n",
"Start: 0\n",
"AP: 1 \"a\"\n",
"Acceptance: 1 Inf(0) | Fin(0)\n",
"--BODY--\n",
"State: 0\n",
"[0] 0 \n",
"[!0] 1\n",
"State: 1\n",
"[0] 1 \n",
"[!0] 2 {0}\n",
"State: 2\n",
"[0] 1\n",
"[!0] 3\n",
"State: 3\n",
"[t] 3 {0}\n",
"--END--\n",
"\"\"\")\n",
"aut.show('.as')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 19,
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 19
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By our definitions, SCC $\\{0\\}$ is stricly weak, SCC $\\{1,2\\}$ is strong, and SCC $\\{3\\}$ is terminal.\n",
"\n",
"However with this acceptance condition, we would be unable to extract only the strong behaviors: because that would require a way to disable the SCC $\\{0\\}$, and we cannot do that with this acceptance condition. This could be solved by adding a new acceptance set, but if we are about the modify the accepance condition, it seems wiser to simply it.\n",
"Our solution to this problem is that whenever an acceptance condition is always satisfiable, we consider the entire automaton as weak, and ignore all requests to extract the strong part. As a consequence, the output will always use B\u00fcchi acceptance."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for (name, opt) in (('terminal', 't'), ('strictly weak', 'w'), ('strong', 's'), ('all strengths', 'swt')):\n",
" a = spot.decompose_strength(aut, opt)\n",
" if a:\n",
" a.set_name(name)\n",
" display(a.show('.asn'))\n",
" else:\n",
" print(\"no output for \" + name)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
},
{
"output_type": "stream",
"stream": "stdout",
"text": [
"no output for strong\n"
]
},
{
"metadata": {},
"output_type": "display_data",
"svg": [
""
],
"text": [
""
]
}
],
"prompt_number": 20
}
],
"metadata": {}
}
]
}