spot/tests/python/formulas.ipynb
Alexandre Duret-Lutz c5c3e905ae python: workaround different help() output in Python 3.12
Python 3.12 introduced some subtle changes in the way doc strings are
displayed by help().  This was causing spurious errors in the
following test.

* tests/python/formulas.ipynb: Use print(x.__doc__) instead of
help(x).
2024-05-03 16:32:16 +02:00

1057 lines
36 KiB
Text

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Handling LTL and PSL formulas\n",
"============================="
]
},
{
"cell_type": "code",
"execution_count": 10,
"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": 11,
"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": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = spot.formula('p1 U p2 R (p3 & !p4)')\n",
"f"
]
},
{
"cell_type": "code",
"execution_count": 12,
"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": 12,
"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": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$c \\land (a \\lor b)$"
],
"text/plain": [
"spot.formula(\"c & (a | b)\")"
]
},
"execution_count": 13,
"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": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$c \\land (a \\lor b)$"
],
"text/plain": [
"spot.formula(\"c & (a | b)\")"
]
},
"execution_count": 14,
"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": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'p1 U (p2 R (p3 & !p4))'"
]
},
"execution_count": 15,
"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 other hand, 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": 16,
"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(f\"{i:10}{f.to_str(i)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Formulas output via `format()` of f-strings can also use some convenient shorthand to select the syntax:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"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(f\"\"\"\\\n",
"Spin: {f:s}\n",
"Spin+parentheses: {f:sp}\n",
"Spot (default): {f}\n",
"Spot+shell quotes: {f:q}\n",
"LBT, right aligned: {f:l:~>40}\n",
"LBT, no M/W/R: {f:[MWR]l}\"\"\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The specifiers that can be used with `format` are documented as follows.\n",
"(Note: As this document is part of our test-suite we have to use `print(x.__doc__)` instead of `help(x)` to work around some formating changes introduced in Python 3.12's `help()`.)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"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",
" \n"
]
}
],
"source": [
"print(spot.formula.__format__.__doc__)"
]
},
{
"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": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.is_in_nenoform() and f.is_ltl_formula()"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"g.is_ltl_formula()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similarly, `is_syntactic_stutter_invariant()` tells whether the structure of the formula guaranties 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": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f.is_syntactic_stutter_invariant()"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.formula('{a[*];b}<>->c').is_syntactic_stutter_invariant()"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 23,
"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": 24,
"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": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"gf = spot.formula('(GF_foo_) && \"a > b\" && \"proc[2]@init\"'); gf"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$a \\land b \\land \\mathsf{G} \\mathsf{F} c$"
],
"text/plain": [
"spot.formula(\"a & b & GFc\")"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.relabel(gf, spot.Abc)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"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": 26,
"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": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{a;first_match({b[*];c[+]}[:*3..5];b)}<>-> (c & GFb)\n"
]
},
{
"data": {
"text/html": [
"<?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=\"170\" cy=\"-557.48\" rx=\"50.09\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"170\" 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=\"M159.34,-539.89C153.9,-531.44 147.17,-520.97 141.12,-511.56\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"143.94,-509.47 135.59,-502.95 138.05,-513.25 143.94,-509.47\"/>\n",
"<text text-anchor=\"middle\" x=\"154.34\" 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=\"M174.06,-539.15C179.63,-515.07 189.76,-470.11 197,-431.48 206.38,-381.42 215.24,-322.67 220.12,-289.03\"/>\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"223.6,-289.42 221.56,-279.03 216.67,-288.43 223.6,-289.42\"/>\n",
"<text text-anchor=\"middle\" x=\"168.56\" y=\"-527.95\" 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.95,-471.04C83.52,-461.84 64.74,-448.27 53,-431.48 29.38,-397.71 27,-383.69 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.95\" y=\"-459.84\" 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.09\" 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": 27,
"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": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\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": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"g.show_mp_hierarchy()"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'recurrence'"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.mp_class(g, 'v')"
]
},
{
"cell_type": "code",
"execution_count": 30,
"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": 30,
"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": 31,
"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": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spot.remove_x(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Removing abbreviated operators"
]
},
{
"cell_type": "code",
"execution_count": 32,
"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": 32,
"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": 33,
"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": 33,
"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": 34,
"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": 35,
"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": 36,
"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 (ipykernel)",
"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.11.8"
}
},
"nbformat": 4,
"nbformat_minor": 2
}