spot/tests/python/formulas.ipynb
Antoine Martin 0505ee9310 tl: implement suffix operator normal form
* spot/tl/Makefile.am: New sonf files
* spot/tl/sonf.cc,spot/tl/sonf.hh: Here.
* python/spot/impl.i: include sonf.hh header
* doc/spot.bib: add entry for the SONF paper
* tests/python/formulas.ipynb: show sample usage
* tests/python/spot.py: test automata equivalence before/after SONF
* NEWS: mention the change
2022-03-03 09:13:04 +01:00

1058 lines
35 KiB
Text

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Handling LTL and PSL formulas\n",
"============================="
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import spot\n",
"from IPython.display import display # not needed with recent Jupyter"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For interactive use, formulas can be entered as text strings and passed to the `spot.formula` constructor."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$p_{1} \\mathbin{\\mathsf{U}} (p_{2} \\mathbin{\\mathsf{R}} (p_{3} \\land \\lnot p_{4}))$"
],
"text/plain": [
"spot.formula(\"p1 U (p2 R (p3 & !p4))\")"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = spot.formula('p1 U p2 R (p3 & !p4)')\n",
"f"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\{a \\mathbin{\\mathsf{;}} \\mathsf{first\\_match}(\\{b^{\\star} \\mathbin{\\mathsf{;}} c^+\\}^{\\mathsf{:}\\star3..5} \\mathbin{\\mathsf{;}} b)\\}\\mathrel{\\Diamond\\kern-1.7pt\\raise.4pt\\hbox{$\\mathord{\\rightarrow}$}} (c \\land \\mathsf{G} \\mathsf{F} b)$"
],
"text/plain": [
"spot.formula(\"{a;first_match({b[*];c[+]}[:*3..5];b)}<>-> (c & GFb)\")"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"g = spot.formula('{a;first_match((b*;c[+])[:*3..5];b)}<>->(GFb & c)'); g"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By default the parser recognizes an infix syntax, but when this fails, it tries to read the formula with the [LBT](http://www.tcs.hut.fi/Software/maria/tools/lbt/) syntax:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$c \\land (a \\lor b)$"
],
"text/plain": [
"spot.formula(\"c & (a | b)\")"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"h = spot.formula('& | a b c'); h"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Passing a `formula` to `spot.formula` simply returns the formula."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$c \\land (a \\lor b)$"
],
"text/plain": [
"spot.formula(\"c & (a | b)\")"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.formula(h)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By default, a formula object is presented using mathjax as above.\n",
"When a formula is converted to string you get Spot's syntax by default:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'p1 U (p2 R (p3 & !p4))'"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"str(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you prefer to print the string in another syntax, you may use the `to_str()` method, with an argument that indicates the output format to use. The `latex` format assumes that you will the define macros such as `\\U`, `\\R` to render all operators as you wish. On the otherhand, the `sclatex` (with `sc` for self-contained) format hard-codes the rendering of each of those operators: this is almost the same output that is used to render formulas using MathJax in a notebook. `sclatex` and `mathjax` only differ in the rendering of double-quoted atomic propositions."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"spot p1 U (p2 R (p3 & !p4))\n",
"spin p1 U (p2 V (p3 && !p4))\n",
"lbt U p1 V p2 & p3 ! p4\n",
"wring (p1=1) U ((p2=1) R ((p3=1) * (p4=0)))\n",
"utf8 p1 U (p2 R (p3∧¬p4))\n",
"latex p_{1} \\U (p_{2} \\R (p_{3} \\land \\lnot p_{4}))\n",
"sclatex p_{1} \\mathbin{\\mathsf{U}} (p_{2} \\mathbin{\\mathsf{R}} (p_{3} \\land \\lnot p_{4}))\n",
"mathjax p_{1} \\mathbin{\\mathsf{U}} (p_{2} \\mathbin{\\mathsf{R}} (p_{3} \\land \\lnot p_{4}))\n"
]
}
],
"source": [
"for i in ['spot', 'spin', 'lbt', 'wring', 'utf8', 'latex', 'sclatex', 'mathjax']:\n",
" print(\"%-10s%s\" % (i, f.to_str(i)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Formulas output via `format()` can also use some convenient shorthand to select the syntax:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Spin: p1 U (p2 V (p3 && !p4))\n",
"Spin+parentheses: (p1) U ((p2) V ((p3) && (!(p4))))\n",
"Spot (default): p1 U (p2 R (p3 & !p4))\n",
"Spot+shell quotes: 'p1 U (p2 R (p3 & !p4))'\n",
"LBT, right aligned: ~~~~~~~~~~~~~~~~~~~~~U p1 V p2 & p3 ! p4\n",
"LBT, no M/W/R: U p1 U & p3 ! p4 | & & p2 p3 ! p4 G & p3 ! p4\n"
]
}
],
"source": [
"print(\"\"\"\\\n",
"Spin: {0:s}\n",
"Spin+parentheses: {0:sp}\n",
"Spot (default): {0}\n",
"Spot+shell quotes: {0:q}\n",
"LBT, right aligned: {0:l:~>40}\n",
"LBT, no M/W/R: {0:[MWR]l}\"\"\".format(f))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The specifiers that can be used with `format` are documented as follows:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Help on function __format__ in module spot:\n",
"\n",
"__format__(self, spec)\n",
" Format the formula according to `spec`.\n",
" \n",
" Parameters\n",
" ----------\n",
" spec : str, optional\n",
" a list of letters that specify how the formula\n",
" should be formatted.\n",
" \n",
" Supported specifiers\n",
" --------------------\n",
" \n",
" - 'f': use Spot's syntax (default)\n",
" - '8': use Spot's syntax in UTF-8 mode\n",
" - 's': use Spin's syntax\n",
" - 'l': use LBT's syntax\n",
" - 'w': use Wring's syntax\n",
" - 'x': use LaTeX output\n",
" - 'X': use self-contained LaTeX output\n",
" - 'j': use self-contained LaTeX output, adjusted for MathJax\n",
" \n",
" Add some of those letters for additional options:\n",
" \n",
" - 'p': use full parentheses\n",
" - 'c': escape the formula for CSV output (this will\n",
" enclose the formula in double quotes, and escape\n",
" any included double quotes)\n",
" - 'h': escape the formula for HTML output\n",
" - 'd': escape double quotes and backslash,\n",
" for use in C-strings (the outermost double\n",
" quotes are *not* added)\n",
" - 'q': quote and escape for shell output, using single\n",
" quotes or double quotes depending on the contents.\n",
" - '[...]': rewrite away all the operators specified in brackets,\n",
" using spot.unabbreviate().\n",
" \n",
" - ':spec': pass the remaining specification to the\n",
" formating function for strings.\n",
"\n"
]
}
],
"source": [
"help(spot.formula.__format__)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A `spot.formula` object has a number of built-in predicates whose value have been computed when the formula was constructed. For instance you can check whether a formula is in negative normal form using `is_in_nenoform()`, and you can make sure it is an LTL formula (i.e. not a PSL formula) using `is_ltl_formula()`:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.is_in_nenoform() and f.is_ltl_formula()"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"g.is_ltl_formula()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similarly, `is_syntactic_stutter_invariant()` tells wether the structure of the formula guarranties it to be stutter invariant. For LTL formula, this means the `X` operator should not be used. For PSL formula, this function capture all formulas built using the [siPSL grammar](http://www.daxc.de/eth/paper/09atva.pdf)."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.is_syntactic_stutter_invariant()"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.formula('{a[*];b}<>->c').is_syntactic_stutter_invariant()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.formula('{a[+];b[*]}<>->d').is_syntactic_stutter_invariant()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`spot.relabel` renames the atomic propositions that occur in a formula, using either letters, or numbered propositions:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\unicode{x201C}\\mathit{a > b}\\unicode{x201D} \\land \\unicode{x201C}\\mathit{proc[2]@init}\\unicode{x201D} \\land \\mathsf{G} \\mathsf{F} \\mathit{\\_foo\\_}$"
],
"text/plain": [
"spot.formula(\"\\\"a > b\\\" & \\\"proc[2]@init\\\" & GF_foo_\")"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"gf = spot.formula('(GF_foo_) && \"a > b\" && \"proc[2]@init\"'); gf"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$a \\land b \\land \\mathsf{G} \\mathsf{F} c$"
],
"text/plain": [
"spot.formula(\"a & b & GFc\")"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.relabel(gf, spot.Abc)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$p_{0} \\land p_{1} \\land \\mathsf{G} \\mathsf{F} p_{2}$"
],
"text/plain": [
"spot.formula(\"p0 & p1 & GFp2\")"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.relabel(gf, spot.Pnn)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The AST of any formula can be displayed with `show_ast()`. Despite the name, this is not a tree but a DAG, because identical subtrees are merged. Binary operators have their left and right operands denoted with `L` and `R`, while non-commutative n-ary operators have their operands numbered."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{a;first_match({b[*];c[+]}[:*3..5];b)}<>-> (c & GFb)\n"
]
},
{
"data": {
"image/svg+xml": [
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
"<!-- Generated by graphviz version 2.43.0 (0)\n",
" -->\n",
"<!-- Title: G Pages: 1 -->\n",
"<svg width=\"343pt\" height=\"583pt\"\n",
" viewBox=\"0.00 0.00 343.00 583.48\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
"<g id=\"graph0\" class=\"graph\" transform=\"scale(1.0 1.0) rotate(0) translate(4 579.48)\">\n",
"<title>G</title>\n",
"<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-579.48 339,-579.48 339,4 -4,4\"/>\n",
"<!-- 0 -->\n",
"<g id=\"node1\" class=\"node\">\n",
"<title>0</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-557.48\" rx=\"50.09\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"171\" y=\"-553.78\" font-family=\"Times,serif\" font-size=\"14.00\">EConcat</text>\n",
"</g>\n",
"<!-- 1 -->\n",
"<g id=\"node2\" class=\"node\">\n",
"<title>1</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"125\" cy=\"-485.48\" rx=\"42.79\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"125\" y=\"-481.78\" font-family=\"Times,serif\" font-size=\"14.00\">Concat</text>\n",
"</g>\n",
"<!-- 0&#45;&gt;1 -->\n",
"<g id=\"edge11\" class=\"edge\">\n",
"<title>0&#45;&gt;1</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M160.1,-539.89C154.54,-531.44 147.67,-520.97 141.48,-511.56\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"144.24,-509.39 135.82,-502.95 138.39,-513.23 144.24,-509.39\"/>\n",
"<text text-anchor=\"middle\" x=\"155.1\" y=\"-528.69\" font-family=\"Times,serif\" font-size=\"14.00\">L</text>\n",
"</g>\n",
"<!-- 11 -->\n",
"<g id=\"node12\" class=\"node\">\n",
"<title>11</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"224\" cy=\"-260.61\" rx=\"28.7\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"224\" y=\"-256.91\" font-family=\"Times,serif\" font-size=\"14.00\">And</text>\n",
"</g>\n",
"<!-- 0&#45;&gt;11 -->\n",
"<g id=\"edge16\" class=\"edge\">\n",
"<title>0&#45;&gt;11</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M175.09,-539.16C180.7,-515.09 190.88,-470.13 198,-431.48 207.22,-381.44 215.7,-322.68 220.33,-289.03\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"223.81,-289.41 221.69,-279.03 216.87,-288.47 223.81,-289.41\"/>\n",
"<text text-anchor=\"middle\" x=\"169.59\" y=\"-527.96\" font-family=\"Times,serif\" font-size=\"14.00\">R</text>\n",
"</g>\n",
"<!-- 2 -->\n",
"<g id=\"node3\" class=\"node\">\n",
"<title>2</title>\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"54,-36 0,-36 0,0 54,0 54,-36\"/>\n",
"<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">a</text>\n",
"</g>\n",
"<!-- 1&#45;&gt;2 -->\n",
"<g id=\"edge1\" class=\"edge\">\n",
"<title>1&#45;&gt;2</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M98.53,-471.16C82.85,-462.01 63.81,-448.44 52,-431.48 28.52,-397.76 27,-383.57 27,-342.48 27,-342.48 27,-342.48 27,-178.74 27,-132.35 27,-78.38 27,-46.54\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"30.5,-46.26 27,-36.26 23.5,-46.26 30.5,-46.26\"/>\n",
"<text text-anchor=\"middle\" x=\"93.53\" y=\"-459.96\" font-family=\"Times,serif\" font-size=\"14.00\">1</text>\n",
"</g>\n",
"<!-- 3 -->\n",
"<g id=\"node4\" class=\"node\">\n",
"<title>3</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"125\" cy=\"-413.48\" rx=\"63.89\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"125\" y=\"-409.78\" font-family=\"Times,serif\" font-size=\"14.00\">first_match</text>\n",
"</g>\n",
"<!-- 1&#45;&gt;3 -->\n",
"<g id=\"edge10\" class=\"edge\">\n",
"<title>1&#45;&gt;3</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M125,-467.18C125,-459.46 125,-450.19 125,-441.59\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"128.5,-441.58 125,-431.58 121.5,-441.58 128.5,-441.58\"/>\n",
"<text text-anchor=\"middle\" x=\"120\" y=\"-455.98\" font-family=\"Times,serif\" font-size=\"14.00\">2</text>\n",
"</g>\n",
"<!-- 4 -->\n",
"<g id=\"node5\" class=\"node\">\n",
"<title>4</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"125\" cy=\"-341.48\" rx=\"42.79\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"125\" y=\"-337.78\" font-family=\"Times,serif\" font-size=\"14.00\">Concat</text>\n",
"</g>\n",
"<!-- 3&#45;&gt;4 -->\n",
"<g id=\"edge9\" class=\"edge\">\n",
"<title>3&#45;&gt;4</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M125,-395.18C125,-387.46 125,-378.19 125,-369.59\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"128.5,-369.58 125,-359.58 121.5,-369.58 128.5,-369.58\"/>\n",
"</g>\n",
"<!-- 5 -->\n",
"<g id=\"node6\" class=\"node\">\n",
"<title>5</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"135\" cy=\"-260.61\" rx=\"40.11\" ry=\"26.74\"/>\n",
"<text text-anchor=\"middle\" x=\"135\" y=\"-264.41\" font-family=\"Times,serif\" font-size=\"14.00\">FStar</text>\n",
"<text text-anchor=\"middle\" x=\"135\" y=\"-249.41\" font-family=\"Times,serif\" font-size=\"14.00\">3..5</text>\n",
"</g>\n",
"<!-- 4&#45;&gt;5 -->\n",
"<g id=\"edge7\" class=\"edge\">\n",
"<title>4&#45;&gt;5</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M127.17,-323.37C128.13,-315.8 129.3,-306.59 130.43,-297.62\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"133.91,-298.05 131.69,-287.69 126.96,-297.17 133.91,-298.05\"/>\n",
"<text text-anchor=\"middle\" x=\"122.17\" y=\"-312.17\" font-family=\"Times,serif\" font-size=\"14.00\">1</text>\n",
"</g>\n",
"<!-- 8 -->\n",
"<g id=\"node9\" class=\"node\">\n",
"<title>8</title>\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"151,-36 97,-36 97,0 151,0 151,-36\"/>\n",
"<text text-anchor=\"middle\" x=\"124\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">b</text>\n",
"</g>\n",
"<!-- 4&#45;&gt;8 -->\n",
"<g id=\"edge8\" class=\"edge\">\n",
"<title>4&#45;&gt;8</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M109.04,-324.36C100.47,-314.57 90.72,-301.32 86,-287.48 79.41,-268.12 80.24,-83.21 84,-72 87.35,-62 93.42,-52.38 99.83,-44.14\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"102.56,-46.33 106.27,-36.4 97.18,-41.85 102.56,-46.33\"/>\n",
"<text text-anchor=\"middle\" x=\"104.04\" y=\"-313.16\" font-family=\"Times,serif\" font-size=\"14.00\">2</text>\n",
"</g>\n",
"<!-- 6 -->\n",
"<g id=\"node7\" class=\"node\">\n",
"<title>6</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"136\" cy=\"-179.74\" rx=\"42.79\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"136\" y=\"-176.04\" font-family=\"Times,serif\" font-size=\"14.00\">Concat</text>\n",
"</g>\n",
"<!-- 5&#45;&gt;6 -->\n",
"<g id=\"edge6\" class=\"edge\">\n",
"<title>5&#45;&gt;6</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M135.33,-233.35C135.44,-225.25 135.55,-216.31 135.65,-208.13\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"139.15,-208 135.78,-197.96 132.16,-207.91 139.15,-208\"/>\n",
"</g>\n",
"<!-- 7 -->\n",
"<g id=\"node8\" class=\"node\">\n",
"<title>7</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"124\" cy=\"-98.87\" rx=\"30.59\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"124\" y=\"-95.17\" font-family=\"Times,serif\" font-size=\"14.00\">Star</text>\n",
"</g>\n",
"<!-- 6&#45;&gt;7 -->\n",
"<g id=\"edge3\" class=\"edge\">\n",
"<title>6&#45;&gt;7</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M133.4,-161.63C131.86,-151.5 129.87,-138.44 128.12,-126.94\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"131.56,-126.31 126.6,-116.95 124.64,-127.36 131.56,-126.31\"/>\n",
"<text text-anchor=\"middle\" x=\"128.4\" y=\"-150.43\" font-family=\"Times,serif\" font-size=\"14.00\">1</text>\n",
"</g>\n",
"<!-- 9 -->\n",
"<g id=\"node10\" class=\"node\">\n",
"<title>9</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"278\" cy=\"-98.87\" rx=\"33.47\" ry=\"26.74\"/>\n",
"<text text-anchor=\"middle\" x=\"278\" y=\"-102.67\" font-family=\"Times,serif\" font-size=\"14.00\">Star</text>\n",
"<text text-anchor=\"middle\" x=\"278\" y=\"-87.67\" font-family=\"Times,serif\" font-size=\"14.00\">1..</text>\n",
"</g>\n",
"<!-- 6&#45;&gt;9 -->\n",
"<g id=\"edge5\" class=\"edge\">\n",
"<title>6&#45;&gt;9</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M162.15,-165.47C182.47,-155.01 211.27,-139.89 236,-125.74 238.51,-124.3 241.09,-122.8 243.67,-121.26\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"245.63,-124.17 252.39,-116.01 242.02,-118.17 245.63,-124.17\"/>\n",
"<text text-anchor=\"middle\" x=\"157.15\" y=\"-154.27\" font-family=\"Times,serif\" font-size=\"14.00\">2</text>\n",
"</g>\n",
"<!-- 7&#45;&gt;8 -->\n",
"<g id=\"edge2\" class=\"edge\">\n",
"<title>7&#45;&gt;8</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M124,-80.76C124,-70.73 124,-57.84 124,-46.42\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"127.5,-46.08 124,-36.08 120.5,-46.08 127.5,-46.08\"/>\n",
"</g>\n",
"<!-- 10 -->\n",
"<g id=\"node11\" class=\"node\">\n",
"<title>10</title>\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"335,-36 281,-36 281,0 335,0 335,-36\"/>\n",
"<text text-anchor=\"middle\" x=\"308\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">c</text>\n",
"</g>\n",
"<!-- 9&#45;&gt;10 -->\n",
"<g id=\"edge4\" class=\"edge\">\n",
"<title>9&#45;&gt;10</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M287.49,-72.92C290.78,-64.26 294.49,-54.52 297.83,-45.73\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"301.13,-46.9 301.42,-36.31 294.59,-44.41 301.13,-46.9\"/>\n",
"</g>\n",
"<!-- 11&#45;&gt;10 -->\n",
"<g id=\"edge12\" class=\"edge\">\n",
"<title>11&#45;&gt;10</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M239.43,-244.94C262.27,-222.05 304.07,-175.29 320,-125.74 328.47,-99.38 323.08,-67.69 317.06,-45.72\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"320.4,-44.67 314.2,-36.07 313.68,-46.65 320.4,-44.67\"/>\n",
"</g>\n",
"<!-- 12 -->\n",
"<g id=\"node13\" class=\"node\">\n",
"<title>12</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"224\" cy=\"-179.74\" rx=\"27\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"224\" y=\"-176.04\" font-family=\"Times,serif\" font-size=\"14.00\">G</text>\n",
"</g>\n",
"<!-- 11&#45;&gt;12 -->\n",
"<g id=\"edge15\" class=\"edge\">\n",
"<title>11&#45;&gt;12</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M224,-242.5C224,-232.47 224,-219.58 224,-208.16\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"227.5,-207.82 224,-197.82 220.5,-207.82 227.5,-207.82\"/>\n",
"</g>\n",
"<!-- 13 -->\n",
"<g id=\"node14\" class=\"node\">\n",
"<title>13</title>\n",
"<ellipse fill=\"none\" stroke=\"black\" cx=\"200\" cy=\"-98.87\" rx=\"27\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"200\" y=\"-95.17\" font-family=\"Times,serif\" font-size=\"14.00\">F</text>\n",
"</g>\n",
"<!-- 12&#45;&gt;13 -->\n",
"<g id=\"edge14\" class=\"edge\">\n",
"<title>12&#45;&gt;13</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M218.91,-162.01C215.76,-151.66 211.65,-138.14 208.06,-126.36\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"211.34,-125.12 205.08,-116.57 204.65,-127.16 211.34,-125.12\"/>\n",
"</g>\n",
"<!-- 13&#45;&gt;8 -->\n",
"<g id=\"edge13\" class=\"edge\">\n",
"<title>13&#45;&gt;8</title>\n",
"<path fill=\"none\" stroke=\"black\" d=\"M186.06,-83.4C175.31,-72.25 160.21,-56.57 147.59,-43.48\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"149.96,-40.89 140.5,-36.12 144.92,-45.75 149.96,-40.89\"/>\n",
"</g>\n",
"</g>\n",
"</svg>\n"
],
"text/plain": [
"<spot.jupyter.SVG object>"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"print(g); g.show_ast()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Any formula can also be classified in the temporal hierarchy of Manna & Pnueli"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"<svg height=\"210\" width=\"220\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
"<polygon points=\"20,0 200,120 200,210 20,210\" fill=\"cyan\" opacity=\".2\" />\n",
"<polygon points=\"20,120 155,210 20,210\" fill=\"cyan\" opacity=\".2\" />\n",
"<polygon points=\"200,0 20,120 20,210 200,210\" fill=\"magenta\" opacity=\".15\" />\n",
"<polygon points=\"200,120 65,210 200,210\" fill=\"magenta\" opacity=\".15\" />\n",
"<g transform=\"translate(40,80)\">\n",
" <line x1=\"-10\" y1=\"-10\" x2=\"10\" y2=\"10\" stroke=\"red\" stroke-width=\"5\" />\n",
" <line x1=\"-10\" y1=\"10\" x2=\"10\" y2=\"-10\" stroke=\"red\" stroke-width=\"5\" />\n",
" </g>\n",
"<g text-anchor=\"middle\" font-size=\"14\">\n",
"<text x=\"110\" y=\"20\">Reactivity</text>\n",
"<text x=\"60\" y=\"65\">Recurrence</text>\n",
"<text x=\"160\" y=\"65\">Persistence</text>\n",
"<text x=\"110\" y=\"125\">Obligation</text>\n",
"<text x=\"60\" y=\"185\">Safety</text>\n",
"<text x=\"160\" y=\"185\">Guarantee</text>\n",
"</g>\n",
"<g font-size=\"14\">\n",
"<text text-anchor=\"begin\" transform=\"rotate(-90,18,210)\" x=\"18\" y=\"210\" fill=\"gray\">Monitor</text>\n",
"<text text-anchor=\"end\" transform=\"rotate(-90,18,0)\" x=\"18\" y=\"0\" fill=\"gray\">Deterministic Büchi</text>\n",
"<text text-anchor=\"begin\" transform=\"rotate(-90,214,210)\" x=\"214\" y=\"210\" fill=\"gray\">Terminal Büchi</text>\n",
"<text text-anchor=\"end\" transform=\"rotate(-90,214,0)\" x=\"214\" y=\"0\" fill=\"gray\">Weak Büchi</text>\n",
"</g>\n",
"</svg>"
],
"text/plain": [
"<spot.jupyter.SVG object>"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"g.show_mp_hierarchy()"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'recurrence'"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.mp_class(g, 'v')"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\mathsf{F} (a \\land \\mathsf{X} (\\lnot a \\land b))$"
],
"text/plain": [
"spot.formula(\"F(a & X(!a & b))\")"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = spot.formula('F(a & X(!a & b))'); f"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Etessami's rule for removing X (valid only in stutter-invariant formulas)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\mathsf{F} (a \\land ((a \\land (a \\mathbin{\\mathsf{U}} (\\lnot a \\land b)) \\land ((\\lnot b \\mathbin{\\mathsf{U}} \\lnot a) \\lor (b \\mathbin{\\mathsf{U}} \\lnot a))) \\lor (\\lnot a \\land (\\lnot a \\mathbin{\\mathsf{U}} (a \\land \\lnot a \\land b)) \\land ((\\lnot b \\mathbin{\\mathsf{U}} a) \\lor (b \\mathbin{\\mathsf{U}} a))) \\lor (b \\land (b \\mathbin{\\mathsf{U}} (\\lnot a \\land b \\land \\lnot b)) \\land ((\\lnot a \\mathbin{\\mathsf{U}} \\lnot b) \\lor (a \\mathbin{\\mathsf{U}} \\lnot b))) \\lor (\\lnot b \\land (\\lnot b \\mathbin{\\mathsf{U}} (\\lnot a \\land b)) \\land ((\\lnot a \\mathbin{\\mathsf{U}} b) \\lor (a \\mathbin{\\mathsf{U}} b))) \\lor (\\lnot a \\land b \\land (\\mathsf{G} \\lnot a \\lor \\mathsf{G} a) \\land (\\mathsf{G} \\lnot b \\lor \\mathsf{G} b))))$"
],
"text/plain": [
"spot.formula(\"F(a & ((a & (a U (!a & b)) & ((!b U !a) | (b U !a))) | (!a & (!a U (a & !a & b)) & ((!b U a) | (b U a))) | (b & (b U (!a & b & !b)) & ((!a U !b) | (a U !b))) | (!b & (!b U (!a & b)) & ((!a U b) | (a U b))) | (!a & b & (G!a | Ga) & (G!b | Gb))))\")"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.remove_x(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Removing abbreviated operators"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$(\\bot \\mathbin{\\mathsf{R}} \\lnot (a \\leftrightarrow b)) \\rightarrow (\\top \\mathbin{\\mathsf{U}} (a \\leftrightarrow b))$"
],
"text/plain": [
"spot.formula(\"(0 R !(a <-> b)) -> (1 U (a <-> b))\")"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = spot.formula(\"G(a xor b) -> F(a <-> b)\")\n",
"spot.unabbreviate(f, \"GF^\")"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$(\\top \\mathbin{\\mathsf{U}} ((a \\land b) \\lor (\\lnot a \\land \\lnot b))) \\lor \\lnot (\\bot \\mathbin{\\mathsf{R}} ((\\lnot a \\land b) \\lor (a \\land \\lnot b)))$"
],
"text/plain": [
"spot.formula(\"(1 U ((a & b) | (!a & !b))) | !(0 R ((!a & b) | (a & !b)))\")"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.unabbreviate(f, \"GF^ei\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nesting level of operators"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"U 3\n",
"F 2\n",
"U 3\n",
"F 2\n",
"FU 4\n"
]
}
],
"source": [
"f = spot.formula('F(b & X(a U b U ((a W Fb) | (c U d))))')\n",
"print(\"U\", spot.nesting_depth(f, spot.op_U))\n",
"print(\"F\", spot.nesting_depth(f, spot.op_F))\n",
"# These following two are syntactic sugar for the above two\n",
"print(\"U\", spot.nesting_depth(f, \"U\"))\n",
"print(\"F\", spot.nesting_depth(f, \"F\"))\n",
"# If you want to consider \"U\" and \"F\" are a similar type of\n",
"# operator, you can count both with\n",
"print(\"FU\", spot.nesting_depth(f, \"FU\"))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Collecting the set of atomic propositions used by a formula:"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"spot.atomic_prop_set([spot.formula(\"a\"), spot.formula(\"b\"), spot.formula(\"c\"), spot.formula(\"d\")])\n",
"{\"a\", \"b\", \"c\", \"d\"}\n"
]
},
{
"data": {
"text/latex": [
"$\\{\\unicode{x201C}a\\unicode{x201D}, \\unicode{x201C}b\\unicode{x201D}, \\unicode{x201C}c\\unicode{x201D}, \\unicode{x201C}d\\unicode{x201D}\\}$"
],
"text/plain": [
"spot.atomic_prop_set([spot.formula(\"a\"), spot.formula(\"b\"), spot.formula(\"c\"), spot.formula(\"d\")])"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ap = spot.atomic_prop_collect(f)\n",
"print(repr(ap)) # print as an atomic_prop_set object\n",
"print(ap) # print as a string\n",
"display(ap) # LaTeX-style, for notebooks"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Converting to Suffix Operator Normal Form:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\mathsf{G} (\\{x^{\\star}\\}\\mathrel{\\Box\\kern-1.7pt\\raise.4pt\\hbox{$\\mathord{\\rightarrow}$}} \\mathsf{F} a)$"
],
"text/plain": [
"spot.formula(\"G({x[*]}[]-> Fa)\")"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/latex": [
"$\\mathsf{G} \\mathit{sonf\\_}_{0} \\land \\mathsf{G} (\\lnot \\mathit{sonf\\_}_{1} \\lor \\mathsf{F} a) \\land \\mathsf{G} (\\lnot \\mathit{sonf\\_}_{0} \\lor (\\{x^{\\star}\\}\\mathrel{\\Box\\kern-1.7pt\\raise.4pt\\hbox{$\\mathord{\\rightarrow}$}} \\mathit{sonf\\_}_{1}))$"
],
"text/plain": [
"spot.formula(\"Gsonf_0 & G(!sonf_1 | Fa) & G(!sonf_0 | ({x[*]}[]-> sonf_1))\")"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"('sonf_0', 'sonf_1')"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"f = spot.formula('G({x*} []-> Fa)')\n",
"display(f)\n",
"\n",
"# In addition to the formula, returns a list of newly introduced APs\n",
"f, aps = spot.suffix_operator_normal_form(f, 'sonf_')\n",
"display(f)\n",
"display(aps)"
]
}
],
"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.9.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}