Work around a recent decision in Jupyter Lab and Notebook to render <svg> is inline <img>, breaking tooltips or text selection. (Rerendering all notebooks was painful.) * NEWS: Mention the change. * python/spot/__init__.py: Add a _repr_html_ method to all classes that had a _repr_svg_. It seems Jupyter will use _repr_html_ by default. * python/spot/jupyter.py: SVG replace the _repr_svg_ method by a _repr_html. * tests/python/_altscc.ipynb, tests/python/_autparserr.ipynb, tests/python/_aux.ipynb, tests/python/_mealy.ipynb, tests/python/_partitioned_relabel.ipynb, tests/python/_product_susp.ipynb, tests/python/_product_weak.ipynb, tests/python/_synthesis.ipynb, tests/python/aliases.ipynb, tests/python/alternation.ipynb, tests/python/atva16-fig2a.ipynb, tests/python/atva16-fig2b.ipynb, tests/python/automata-io.ipynb, tests/python/automata.ipynb, tests/python/cav22-figs.ipynb, tests/python/contains.ipynb, tests/python/decompose.ipynb, tests/python/formulas.ipynb, tests/python/games.ipynb, tests/python/gen.ipynb, tests/python/highlighting.ipynb, tests/python/ltsmin-dve.ipynb, tests/python/ltsmin-pml.ipynb, tests/python/parity.ipynb, tests/python/product.ipynb, tests/python/randaut.ipynb, tests/python/satmin.ipynb, tests/python/stutter-inv.ipynb, tests/python/synthesis.ipynb, tests/python/testingaut.ipynb, tests/python/twagraph-internals.ipynb, tests/python/word.ipynb, tests/python/zlktree.ipynb: Update all notebooks.
513 lines
16 KiB
Text
513 lines
16 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import spot\n",
|
|
"from spot.jupyter import display_inline\n",
|
|
"spot.setup(show_default='.a')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Containement checks\n",
|
|
"\n",
|
|
"The `spot.contains()` function checks whether the language of its right argument is included in the language of its left argument. The arguments may mix automata and formulas; the latter can be given as strings."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"f = spot.formula('GFa'); aut_f = f.translate()\n",
|
|
"g = spot.formula('FGa'); aut_g = g.translate()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(True, False)"
|
|
]
|
|
},
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"spot.contains(f, g), spot.contains(g, f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(True, False)"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"spot.contains(aut_f, aut_g), spot.contains(aut_g, aut_f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(True, False)"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"spot.contains(aut_f, g), spot.contains(aut_g, f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(True, False)"
|
|
]
|
|
},
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"spot.contains(f, aut_g), spot.contains(g, aut_f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(True, False)"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"spot.contains(\"GFa\", aut_g), spot.contains(\"FGa\", aut_f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Those functions are also usable as methods:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(True, False)"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"f.contains(aut_g), g.contains(aut_f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(True, False)"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"aut_f.contains(\"FGa\"), aut_g.contains(\"GFa\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Equivalence checks\n",
|
|
"\n",
|
|
"The `spot.are_equivalent()` tests the equivalence of the languages of its two arguments. Note that the corresponding method is called `equivalent_to()`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(False, False)"
|
|
]
|
|
},
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"spot.are_equivalent(f, g), spot.are_equivalent(aut_f, aut_g)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(False, False)"
|
|
]
|
|
},
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"f.equivalent_to(aut_g), aut_f.equivalent_to(g)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"True"
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"aut_f.equivalent_to('XXXGFa')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Containement checks between formulas with cache\n",
|
|
"\n",
|
|
"In the case of containement checks between formulas, `language_containement_checker` instances provide similar services, but they cache automata representing the formulas checked. This should be preferred when performing several containement checks using the same formulas."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"lcc = spot.language_containment_checker()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(True, False)"
|
|
]
|
|
},
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"lcc.contains(f, g), lcc.contains(g, f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"False"
|
|
]
|
|
},
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"lcc.are_equivalent(f, g)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Help for distinguishing languages\n",
|
|
"\n",
|
|
"Assume you have computed two automata, that `are_equivalent(a1, a2)` returns `False`, and you want to know why. (This often occur when debugging some algorithm that produce an automaton that is not equivalent to which it should.) The automaton class has a method called `a1.exclusive_run(a2)` that can help with this task: it returns a run that recognizes a word is is accepted by one of the two automata but not by both. The method `a1.exclusive_word(a2)` will return just a word.\n",
|
|
"\n",
|
|
"For instance let's find a word that is exclusive between `aut_f` and `aut_g`. (The adjective *exclusive* is a reference to the *exclusive or* operator: the word belongs to L(aut_f) \"xor\" it belongs to L(aut_g).)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/latex": [
|
|
"$\\mathsf{cycle}\\{a; \\lnot a\\}$"
|
|
],
|
|
"text/plain": [
|
|
"<spot.twa_word; proxy of <Swig Object of type 'std::shared_ptr< spot::twa_word > *' at 0x7f05002c3c90> >"
|
|
]
|
|
},
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"aut_f.exclusive_word(aut_g)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"We can even write a small function that highlights one difference between two automata. Note that the `run` returned will belong to either `left` or `right`, so calling the `highlight()` method will colorize one of those two automata."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def show_one_difference(left, right):\n",
|
|
" run = left.exclusive_run(right)\n",
|
|
" if not run:\n",
|
|
" print(\"The two automata are equivalent.\")\n",
|
|
" else:\n",
|
|
" print(\"The following word is only accepted by one automaton:\", spot.make_twa_word(run))\n",
|
|
" run.highlight(5)\n",
|
|
" display_inline(left, right)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"The following word is only accepted by one automaton: cycle{a; !a}\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<div style='vertical-align:text-top;display:inline-block;'><?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",
|
|
"<!-- Pages: 1 -->\n",
|
|
"<svg width=\"82pt\" height=\"161pt\"\n",
|
|
" viewBox=\"0.00 0.00 82.00 161.00\" 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 157)\">\n",
|
|
"<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-157 78,-157 78,4 -4,4\"/>\n",
|
|
"<text text-anchor=\"start\" x=\"16.5\" y=\"-138.8\" font-family=\"Lato\" font-size=\"14.00\">Inf(</text>\n",
|
|
"<text text-anchor=\"start\" x=\"37.5\" y=\"-138.8\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#1f78b4\">⓿</text>\n",
|
|
"<text text-anchor=\"start\" x=\"53.5\" y=\"-138.8\" font-family=\"Lato\" font-size=\"14.00\">)</text>\n",
|
|
"<text text-anchor=\"start\" x=\"15.5\" y=\"-124.8\" font-family=\"Lato\" font-size=\"14.00\">[Büchi]</text>\n",
|
|
"<!-- I -->\n",
|
|
"<!-- 0 -->\n",
|
|
"<g id=\"node2\" class=\"node\">\n",
|
|
"<title>0</title>\n",
|
|
"<ellipse fill=\"#ffffaa\" stroke=\"black\" cx=\"56\" cy=\"-18\" rx=\"18\" ry=\"18\"/>\n",
|
|
"<text text-anchor=\"middle\" x=\"56\" y=\"-14.3\" font-family=\"Lato\" font-size=\"14.00\">0</text>\n",
|
|
"</g>\n",
|
|
"<!-- I->0 -->\n",
|
|
"<g id=\"edge1\" class=\"edge\">\n",
|
|
"<title>I->0</title>\n",
|
|
"<path fill=\"none\" stroke=\"black\" d=\"M1.15,-18C2.79,-18 17.15,-18 30.63,-18\"/>\n",
|
|
"<polygon fill=\"black\" stroke=\"black\" points=\"37.94,-18 30.94,-21.15 34.44,-18 30.94,-18 30.94,-18 30.94,-18 34.44,-18 30.94,-14.85 37.94,-18 37.94,-18\"/>\n",
|
|
"</g>\n",
|
|
"<!-- 0->0 -->\n",
|
|
"<g id=\"edge2\" class=\"edge\">\n",
|
|
"<title>0->0</title>\n",
|
|
"<path fill=\"none\" stroke=\"#e31a1c\" stroke-width=\"2\" d=\"M52.76,-35.78C52.21,-45.31 53.29,-54 56,-54 57.99,-54 59.1,-49.32 59.33,-43.05\"/>\n",
|
|
"<polygon fill=\"#e31a1c\" stroke=\"#e31a1c\" stroke-width=\"2\" points=\"59.24,-35.78 62.48,-42.74 59.78,-39.28 59.83,-42.77 59.33,-42.78 58.83,-42.79 58.78,-39.29 56.18,-42.82 59.24,-35.78 59.24,-35.78\"/>\n",
|
|
"<text text-anchor=\"start\" x=\"50.5\" y=\"-57.8\" font-family=\"Lato\" font-size=\"14.00\">!a</text>\n",
|
|
"</g>\n",
|
|
"<!-- 0->0 -->\n",
|
|
"<g id=\"edge3\" class=\"edge\">\n",
|
|
"<title>0->0</title>\n",
|
|
"<path fill=\"none\" stroke=\"#e31a1c\" stroke-width=\"2\" d=\"M50.68,-35.42C47.65,-52.79 49.43,-72 56,-72 61.7,-72 63.79,-57.55 62.27,-42.39\"/>\n",
|
|
"<polygon fill=\"#e31a1c\" stroke=\"#e31a1c\" stroke-width=\"2\" points=\"61.32,-35.42 65.39,-41.93 62.29,-38.82 62.76,-42.29 62.26,-42.36 61.77,-42.42 61.29,-38.96 59.14,-42.78 61.32,-35.42 61.32,-35.42\"/>\n",
|
|
"<text text-anchor=\"start\" x=\"52.5\" y=\"-90.8\" font-family=\"Lato\" font-size=\"14.00\">a</text>\n",
|
|
"<text text-anchor=\"start\" x=\"48\" y=\"-75.8\" font-family=\"Lato\" font-size=\"14.00\" fill=\"#1f78b4\">⓿</text>\n",
|
|
"</g>\n",
|
|
"</g>\n",
|
|
"</svg>\n",
|
|
"</div><div style='vertical-align:text-top;display:inline-block;'><?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",
|
|
"<!-- Pages: 1 -->\n",
|
|
"<svg width=\"169pt\" height=\"108pt\"\n",
|
|
" viewBox=\"0.00 0.00 169.00 108.00\" 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 104)\">\n",
|
|
"<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-104 165,-104 165,4 -4,4\"/>\n",
|
|
"<text text-anchor=\"start\" x=\"59\" y=\"-84.8\" font-family=\"Lato\" font-size=\"14.00\">[Büchi]</text>\n",
|
|
"<!-- I -->\n",
|
|
"<!-- 0 -->\n",
|
|
"<g id=\"node2\" class=\"node\">\n",
|
|
"<title>0</title>\n",
|
|
"<ellipse fill=\"#ffffaa\" stroke=\"black\" cx=\"56\" cy=\"-22\" rx=\"18\" ry=\"18\"/>\n",
|
|
"<text text-anchor=\"middle\" x=\"56\" y=\"-18.3\" font-family=\"Lato\" font-size=\"14.00\">0</text>\n",
|
|
"</g>\n",
|
|
"<!-- I->0 -->\n",
|
|
"<g id=\"edge1\" class=\"edge\">\n",
|
|
"<title>I->0</title>\n",
|
|
"<path fill=\"none\" stroke=\"black\" d=\"M1.15,-22C2.79,-22 17.15,-22 30.63,-22\"/>\n",
|
|
"<polygon fill=\"black\" stroke=\"black\" points=\"37.94,-22 30.94,-25.15 34.44,-22 30.94,-22 30.94,-22 30.94,-22 34.44,-22 30.94,-18.85 37.94,-22 37.94,-22\"/>\n",
|
|
"</g>\n",
|
|
"<!-- 0->0 -->\n",
|
|
"<g id=\"edge2\" class=\"edge\">\n",
|
|
"<title>0->0</title>\n",
|
|
"<path fill=\"none\" stroke=\"black\" d=\"M49.62,-39.04C48.32,-48.86 50.45,-58 56,-58 60.17,-58 62.4,-52.86 62.71,-46.14\"/>\n",
|
|
"<polygon fill=\"black\" stroke=\"black\" points=\"62.38,-39.04 65.85,-45.88 62.54,-42.53 62.71,-46.03 62.71,-46.03 62.71,-46.03 62.54,-42.53 59.56,-46.18 62.38,-39.04 62.38,-39.04\"/>\n",
|
|
"<text text-anchor=\"start\" x=\"51.5\" y=\"-61.8\" font-family=\"Lato\" font-size=\"14.00\">1</text>\n",
|
|
"</g>\n",
|
|
"<!-- 1 -->\n",
|
|
"<g id=\"node3\" class=\"node\">\n",
|
|
"<title>1</title>\n",
|
|
"<ellipse fill=\"#ffffaa\" stroke=\"black\" cx=\"139\" cy=\"-22\" rx=\"18\" ry=\"18\"/>\n",
|
|
"<ellipse fill=\"none\" stroke=\"black\" cx=\"139\" cy=\"-22\" rx=\"22\" ry=\"22\"/>\n",
|
|
"<text text-anchor=\"start\" x=\"134.5\" y=\"-18.3\" font-family=\"Lato\" font-size=\"14.00\">1</text>\n",
|
|
"</g>\n",
|
|
"<!-- 0->1 -->\n",
|
|
"<g id=\"edge3\" class=\"edge\">\n",
|
|
"<title>0->1</title>\n",
|
|
"<path fill=\"none\" stroke=\"black\" d=\"M74.18,-22C84.48,-22 97.85,-22 109.68,-22\"/>\n",
|
|
"<polygon fill=\"black\" stroke=\"black\" points=\"116.81,-22 109.81,-25.15 113.31,-22 109.81,-22 109.81,-22 109.81,-22 113.31,-22 109.81,-18.85 116.81,-22 116.81,-22\"/>\n",
|
|
"<text text-anchor=\"start\" x=\"92\" y=\"-25.8\" font-family=\"Lato\" font-size=\"14.00\">a</text>\n",
|
|
"</g>\n",
|
|
"<!-- 1->1 -->\n",
|
|
"<g id=\"edge4\" class=\"edge\">\n",
|
|
"<title>1->1</title>\n",
|
|
"<path fill=\"none\" stroke=\"black\" d=\"M131.32,-42.99C130.37,-53.09 132.93,-62 139,-62 143.55,-62 146.13,-56.99 146.74,-50.22\"/>\n",
|
|
"<polygon fill=\"black\" stroke=\"black\" points=\"146.68,-42.99 149.88,-49.97 146.71,-46.49 146.73,-49.99 146.73,-49.99 146.73,-49.99 146.71,-46.49 143.58,-50.01 146.68,-42.99 146.68,-42.99\"/>\n",
|
|
"<text text-anchor=\"start\" x=\"135.5\" y=\"-65.8\" font-family=\"Lato\" font-size=\"14.00\">a</text>\n",
|
|
"</g>\n",
|
|
"</g>\n",
|
|
"</svg>\n",
|
|
"</div>"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"show_one_difference(aut_f, aut_g)"
|
|
]
|
|
}
|
|
],
|
|
"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.7"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|