diff --git a/_downloads/0578bfaf7af2be312ae446ace5180d38/plot_simple_cascade.ipynb b/_downloads/0578bfaf7af2be312ae446ace5180d38/plot_simple_cascade.ipynb index 840c039..566ac6d 100644 --- a/_downloads/0578bfaf7af2be312ae446ace5180d38/plot_simple_cascade.ipynb +++ b/_downloads/0578bfaf7af2be312ae446ace5180d38/plot_simple_cascade.ipynb @@ -1,16 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -26,7 +15,7 @@ }, "outputs": [], "source": [ - "import matplotlib as mpl\nimport matplotlib.pyplot as plt\nimport pandas as pd\n\nimport p3\n\n# Initialize synthetic performance efficiency data\n# (not shown, but available in script download)\n\n# Read performance efficiency data into pandas DataFrame\ndf = pd.DataFrame(data)\n\n# Generate a cascade plot\nfig = plt.figure(figsize=(6, 5))\nax = p3.plot.cascade(df)\nplt.savefig(\"cascade.png\", bbox_inches=\"tight\")" + "# Initialize synthetic performance efficiency data\n# (not shown, but available in script download)\n\n# Read performance efficiency data into pandas DataFrame\ndf = pd.DataFrame(data)\n\n# Generate a cascade plot\ncascade = p3analysis.plot.cascade(df, size=(6, 5))\ncascade.save(\"cascade.png\")" ] } ], @@ -46,7 +35,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/_downloads/07075ea048bb69f84e56a96405892fba/plot_customized_navchart.ipynb b/_downloads/07075ea048bb69f84e56a96405892fba/plot_customized_navchart.ipynb new file mode 100644 index 0000000..2882dc6 --- /dev/null +++ b/_downloads/07075ea048bb69f84e56a96405892fba/plot_customized_navchart.ipynb @@ -0,0 +1,43 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Customized Navigation Chart\n\nA customized navigation chart.\n\nIn this example, we show how to customize a navigation chart by increasing the\nnumber of axis ticks and by annotating one of the datapoints. Adjusting the\nnumber of axis ticks can improve a reader's ability to discern between two\nsimilar values, while annotations can be useful to draw attention to certain\npoints and/or provide some additional context.\n\nInstead of trying to expose all possible customization options as arguments to\n:py:func:`p3analysis.plot.navchart`, the function returns a\n:py:class:`p3analysis.plot.NavChart` object that provides direct access to library\ninternals. When using the :py:mod:`matplotlib` backend it is possible to\naccess the :py:class:`matplotlib.axes.Axes` that were used and subsequently\ncall any number of :py:mod:`matplotlib` functions. In our example, we can\nuse :py:meth:`matplotlib.axes.Axes.set_xticks` and\n:py:meth:`matplotlib.axes.Axes.set_yticks` to control the ticks, and can use\n:py:meth:`matplotlib.axes.Axes.annotate` for annotations.\n\n.. NOTE::\n :py:mod:`matplotlib` is currently the only backend supported by the P3\n Analysis Library, but this is subject to change.\n\n.. TIP::\n If you have any trouble customizing a navigation chart, or the\n :py:class:`~p3analysis.plot.backend.NavChart` object does not provide access to the\n internals you are looking for, then please [open an issue](https://github.com/intel/p3-analysis-library/issues/new/choose).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Initialize synthetic data\n# (not shown, but available in script download)\n\n# Read performance portability and code divergence data into pandas DataFrame\npp = pd.DataFrame(pp_data)\ncd = pd.DataFrame(cd_data)\n\n# Generate a navigation chart with custom style options\nlegend = p3analysis.plot.Legend(loc=\"center left\", bbox_to_anchor=(1.0, 0.5))\nastyle = p3analysis.plot.ApplicationStyle(markers=[\"x\", \"*\", \"s\", \"o\", \"P\"])\nnavchart = p3analysis.plot.navchart(pp, cd, size=(5, 5), legend=legend, style=astyle)\n\n# Further customize the navigation chart using matplotlib\n# In this example, we add a label and adjust the ticks\nax = navchart.get_axes()\nax.annotate(\n \"Balances performance and code re-use.\",\n xy=(0.7, 0.7),\n xytext=(0.2, 0.55),\n arrowprops=dict(facecolor='black', shrink=0.05),\n)\nax.set_xticks([x * 0.1 for x in range(0, 11)])\nax.set_yticks([y * 0.1 for y in range(0, 11)])\n\nnavchart.save(\"customized-navchart.png\")" + ] + } + ], + "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.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/0c8f924ed3a2f732c6a73301834a9f3a/case-studies_python.zip b/_downloads/0c8f924ed3a2f732c6a73301834a9f3a/case-studies_python.zip index 15f82d8..5e2aa1d 100644 Binary files a/_downloads/0c8f924ed3a2f732c6a73301834a9f3a/case-studies_python.zip and b/_downloads/0c8f924ed3a2f732c6a73301834a9f3a/case-studies_python.zip differ diff --git a/_downloads/2552e62e0a77fd0b0546cdf90383adad/plot_simple_cascade.py b/_downloads/2552e62e0a77fd0b0546cdf90383adad/plot_simple_cascade.py index 1b59795..2c51bdb 100644 --- a/_downloads/2552e62e0a77fd0b0546cdf90383adad/plot_simple_cascade.py +++ b/_downloads/2552e62e0a77fd0b0546cdf90383adad/plot_simple_cascade.py @@ -32,17 +32,15 @@ .. _Interpreting and Visualizing Performance Portability Metrics: https://doi.org/10.1109/P3HPC51967.2020.00007 """ -import matplotlib as mpl -import matplotlib.pyplot as plt -import pandas as pd - -import p3 - # Initialize synthetic performance efficiency data # (not shown, but available in script download) # sphinx_gallery_start_ignore from collections import defaultdict +import pandas as pd + +import p3analysis + data = defaultdict(list) for (i, platform) in enumerate(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]): data["problem"] += ["Synthetic"] * 6 @@ -71,6 +69,5 @@ df = pd.DataFrame(data) # Generate a cascade plot -fig = plt.figure(figsize=(6, 5)) -ax = p3.plot.cascade(df) -plt.savefig("cascade.png", bbox_inches="tight") +cascade = p3analysis.plot.cascade(df, size=(6, 5)) +cascade.save("cascade.png") diff --git a/_downloads/310b4df53d89925b24f6b7bc3e4d3ec2/plot_babelstream_cascade.ipynb b/_downloads/310b4df53d89925b24f6b7bc3e4d3ec2/plot_babelstream_cascade.ipynb index dcc55c3..8a1f320 100644 --- a/_downloads/310b4df53d89925b24f6b7bc3e4d3ec2/plot_babelstream_cascade.ipynb +++ b/_downloads/310b4df53d89925b24f6b7bc3e4d3ec2/plot_babelstream_cascade.ipynb @@ -1,16 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -33,14 +22,14 @@ }, "outputs": [], "source": [ - "# import libraries\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport p3\n\n\n# Load performance data from BabelStream results\ndf = pd.read_csv(performance_csv)" + "# Load performance data from BabelStream results\ndf = pd.read_csv(performance_csv)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Project Labels into Expected Forms\nThe :py:func:`p3.data.projection` method can be used to project column names\nfrom the original data into names required by the P3 Analysis Library.\n\n" + "## Project Labels into Expected Forms\nThe :py:func:`p3analysis.data.projection` method can be used to project column names\nfrom the original data into names required by the P3 Analysis Library.\n\n" ] }, { @@ -51,7 +40,7 @@ }, "outputs": [], "source": [ - "df = p3.data.projection(\n df, problem=[\"name\"], platform=[\"arch\"], application=[\"language\"]\n)" + "df = p3analysis.data.projection(\n df, problem=[\"name\"], platform=[\"arch\"], application=[\"language\"],\n)" ] }, { @@ -76,7 +65,7 @@ }, "outputs": [], "source": [ - "effs = p3.metrics.application_efficiency(df)\nprint(effs)" + "effs = p3analysis.metrics.application_efficiency(df)\nprint(effs)" ] }, { @@ -94,7 +83,7 @@ }, "outputs": [], "source": [ - "fig = plt.figure(figsize=(6, 5))\nax = p3.plot.cascade(effs) \nplt.savefig(\"cascade.png\", bbox_inches=\"tight\")" + "cascade = p3analysis.plot.cascade(effs)\ncascade.save(\"cascade.png\")" ] }, { @@ -121,7 +110,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/_downloads/32adcc08533a44cfc53b5a4b6e78c2d3/plot_customized_navchart.py b/_downloads/32adcc08533a44cfc53b5a4b6e78c2d3/plot_customized_navchart.py new file mode 100644 index 0000000..9edb1c1 --- /dev/null +++ b/_downloads/32adcc08533a44cfc53b5a4b6e78c2d3/plot_customized_navchart.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 Intel Corporation +# SPDX-License-Identifier: 0BSD +""" +Customized Navigation Chart +=========================== + +A customized navigation chart. + +In this example, we show how to customize a navigation chart by increasing the +number of axis ticks and by annotating one of the datapoints. Adjusting the +number of axis ticks can improve a reader's ability to discern between two +similar values, while annotations can be useful to draw attention to certain +points and/or provide some additional context. + +Instead of trying to expose all possible customization options as arguments to +:py:func:`p3analysis.plot.navchart`, the function returns a +:py:class:`p3analysis.plot.NavChart` object that provides direct access to library +internals. When using the :py:mod:`matplotlib` backend it is possible to +access the :py:class:`matplotlib.axes.Axes` that were used and subsequently +call any number of :py:mod:`matplotlib` functions. In our example, we can +use :py:meth:`matplotlib.axes.Axes.set_xticks` and +:py:meth:`matplotlib.axes.Axes.set_yticks` to control the ticks, and can use +:py:meth:`matplotlib.axes.Axes.annotate` for annotations. + +.. NOTE:: + :py:mod:`matplotlib` is currently the only backend supported by the P3 + Analysis Library, but this is subject to change. + +.. TIP:: + If you have any trouble customizing a navigation chart, or the + :py:class:`~p3analysis.plot.backend.NavChart` object does not provide access to the + internals you are looking for, then please `open an issue + `_. +""" + +# Initialize synthetic data +# (not shown, but available in script download) +# sphinx_gallery_start_ignore +from collections import defaultdict + +import matplotlib.pyplot as plt +import pandas as pd + +import p3analysis + +pp_data = defaultdict(list) +cd_data = defaultdict(list) +for data in [pp_data, cd_data]: + data["problem"] += ["Synthetic"] * 5 + data["application"] += [ + "Unportable", + "Ideal", + "Per-Platform Source", + "Portability Framework", + "Specialized", + ] +pp_data["app pp"] += [0, 1, 1, 0.5, 0.7] +cd_data["divergence"] += [1, 0, 1, 0, 0.3] +# sphinx_gallery_end_ignore + +# Read performance portability and code divergence data into pandas DataFrame +pp = pd.DataFrame(pp_data) +cd = pd.DataFrame(cd_data) + +# Generate a navigation chart with custom style options +legend = p3analysis.plot.Legend(loc="center left", bbox_to_anchor=(1.0, 0.5)) +astyle = p3analysis.plot.ApplicationStyle(markers=["x", "*", "s", "o", "P"]) +navchart = p3analysis.plot.navchart(pp, cd, size=(5, 5), legend=legend, style=astyle) + +# Further customize the navigation chart using matplotlib +# In this example, we add a label and adjust the ticks +ax = navchart.get_axes() +ax.annotate( + "Balances performance and code re-use.", + xy=(0.7, 0.7), + xytext=(0.2, 0.55), + arrowprops=dict(facecolor='black', shrink=0.05), +) +ax.set_xticks([x * 0.1 for x in range(0, 11)]) +ax.set_yticks([y * 0.1 for y in range(0, 11)]) + +navchart.save("customized-navchart.png") diff --git a/_downloads/33ebb1e5f3c0c327518929e0081f5a18/plot_babelstream_cascade.zip b/_downloads/33ebb1e5f3c0c327518929e0081f5a18/plot_babelstream_cascade.zip new file mode 100644 index 0000000..e7ec3e6 Binary files /dev/null and b/_downloads/33ebb1e5f3c0c327518929e0081f5a18/plot_babelstream_cascade.zip differ diff --git a/_downloads/36eb5c73460de2b4f5af873867d4d962/plot_customized_cascade.ipynb b/_downloads/36eb5c73460de2b4f5af873867d4d962/plot_customized_cascade.ipynb new file mode 100644 index 0000000..de4b273 --- /dev/null +++ b/_downloads/36eb5c73460de2b4f5af873867d4d962/plot_customized_cascade.ipynb @@ -0,0 +1,43 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Customized Cascade\n\nA customized cascade plot.\n\nIn this example, we show how to customize a cascade plot by changing the limits\nof the y-axis. Although the default limit (of 1) is useful for comparing many\nplots side-by-side, in practice it is often useful to be able to zoom-in on\nspecific regions of data. For example, when dealing with applications that do\nnot achieve very high levels of architectural efficiency, setting a lower\nmaximum value for the y-axis can improve readability.\n\nInstead of trying to expose all possible customization options as arguments to\n:py:func:`p3analysis.plot.cascade`, the function returns a\n:py:class:`p3analysis.plot.CascadePlot` object that provides direct access to library\ninternals. When using the :py:mod:`matplotlib` backend it is possible to\naccess the :py:class:`matplotlib.axes.Axes` that were used and subsequently\ncall any number of :py:mod:`matplotlib` functions. In our example, we can\nuse :py:meth:`matplotlib.axes.Axes.set_ylim` to update the y-axis.\n\n.. NOTE::\n :py:mod:`matplotlib` is currently the only backend supported by the P3\n Analysis Library, but this is subject to change.\n\n.. TIP::\n If you have any trouble customizing a plot, or the\n :py:class:`~p3analysis.plot.backend.CascadePlot` object does not provide access to\n the internals you are looking for, then please [open an issue](https://github.com/intel/p3-analysis-library/issues/new/choose).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Initialize synthetic performance efficiency data\n# (not shown, but available in script download)\n\n# Read performance efficiency data into pandas DataFrame\ndf = pd.DataFrame(data)\n\n# Generate a cascade plot with custom style options\nlegend = p3analysis.plot.Legend(loc=\"center left\", bbox_to_anchor=(0.91, 0.225), ncols=2)\npstyle = p3analysis.plot.PlatformStyle(colors=\"GnBu\")\nastyle = p3analysis.plot.ApplicationStyle(markers=[\"x\", \"s\", \"p\", \"h\", \"H\", \"v\"])\ncascade = p3analysis.plot.cascade(df, size=(6, 5), platform_legend=legend, platform_style=pstyle, application_style=astyle)\n\n# Further customize the cascade plot using matplotlib\n# In this example, we adjust the range of the y-axis to improve readability\n# This may be necessary for studies using architectural efficiency\ncascade.get_axes(\"eff\").set_ylim([0, 0.12])\ncascade.get_axes(\"pp\").set_ylim([0, 0.12])\n\ncascade.save(\"customized-cascade.png\")" + ] + } + ], + "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.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/3e6eedd9c8ee0dac2f8e11671ca04b72/application_efficiency.zip b/_downloads/3e6eedd9c8ee0dac2f8e11671ca04b72/application_efficiency.zip new file mode 100644 index 0000000..852a838 Binary files /dev/null and b/_downloads/3e6eedd9c8ee0dac2f8e11671ca04b72/application_efficiency.zip differ diff --git a/_downloads/417730b0904deb5a67bfcfa392303195/plot_simple_navchart.ipynb b/_downloads/417730b0904deb5a67bfcfa392303195/plot_simple_navchart.ipynb index 7c379ac..1e2410b 100644 --- a/_downloads/417730b0904deb5a67bfcfa392303195/plot_simple_navchart.ipynb +++ b/_downloads/417730b0904deb5a67bfcfa392303195/plot_simple_navchart.ipynb @@ -1,16 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -26,7 +15,7 @@ }, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\nimport pandas as pd\n\nimport p3\n\n# Initialize synthetic data\n# (not shown, but available in script download)\n\n# Read performance portability and code divergence data into pandas DataFrame\npp = pd.DataFrame(pp_data)\ncd = pd.DataFrame(cd_data)\n\n# Generate a navigation chart\nfig = plt.figure(figsize=(5, 5))\nax = p3.plot.navchart(pp, cd)\nplt.savefig(\"navchart.png\")" + "# Initialize synthetic data\n# (not shown, but available in script download)\n\n# Read performance portability and code divergence data into pandas DataFrame\npp = pd.DataFrame(pp_data)\ncd = pd.DataFrame(cd_data)\n\n# Generate a navigation chart\nnavchart = p3analysis.plot.navchart(pp, cd, size=(5, 5))\nnavchart.save(\"navchart.png\")" ] } ], @@ -46,7 +35,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/_downloads/445b85ee23078a89243e2303f371ee91/projection.ipynb b/_downloads/445b85ee23078a89243e2303f371ee91/projection.ipynb new file mode 100644 index 0000000..15bb251 --- /dev/null +++ b/_downloads/445b85ee23078a89243e2303f371ee91/projection.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n\n# Understanding Data Projection\n\nProjecting data onto P3 definitions.\n\nThe P3 Analysis Library expects data to be prepared in a specific\n`format `. This format was inspired by the\n`terminology ` first introduced in \"`Implications of a Metric\nfor Performance Portability`_\":\n\n https://doi.org/10.1016/j.future.2017.08.007\n\n**Problem**\n A task with a pass/fail metric for which quantitative performance may be\n measured. Multiplying an $N \\times K$ matrix by a $K \\times M$\n matrix to the accuracy guaranteed by IEEE 754 double precision, computing\n $\\pi$ to a certain number of decimal places, and sorting an array of\n $N$ elements are all examples of problems.\n\n**Application**\n Software capable of solving a *problem* with measurable correctness and\n performance. Math libraries, Python scripts, C functions, and entire software\n packages are all examples of applications; the Intel |reg| oneAPI Math Kernel\n Library is an example of an application for solving linear algebra problems.\n\n.. |reg| unicode:: U+00AE\n :ltrim:\n\n**Platform**\n A collection of software **and** hardware on which an *application* may run a\n *problem*. A specific processor coupled with an operating system, compiler,\n runtime, drivers, library dependencies, etc is an example of a precise\n platform definition.\n\nThese definitions are flexible, allowing the same performance data to be used\nfor multiple case studies with different interpretations of these terms.\n\nRather than store raw performance data in columns corresponding to these\ndefinitions, the P3 Analysis Library provides functionality to *project* raw\nperformance data onto specific meanings of \"problem\", \"application\" and\n\"platform\".\n\n## Using Projection to Rename Columns\n\nThe simplest example of projection is a straightforward renaming of columns.\n\nLet's assume that we've collected some performance data from a few different\nimplementations of a function, running a number of problem sizes on multiple\nmachines.\n\n.. important::\n Although we are looking at \"function\" performance here, the concepts\n generalize to entire software packages.\n\nOur raw performance data might look like this:\n\n.. list-table::\n :widths: 20 20 20 20\n :header-rows: 1\n\n * - size\n - implementation\n - machine\n - fom\n\n * - 128x128x128\n - Library 1\n - Cluster 1\n - 0.5\n\n * - 256x256x256\n - Library 1\n - Cluster 1\n - 2.0\n\n * - 128x128x128\n - Library 2\n - Cluster 1\n - 0.7\n\n * - 256x256x256\n - Library 2\n - Cluster 1\n - 2.1\n\n * - 128x128x128\n - Library 1\n - Cluster 2\n - 0.25\n\n * - 256x256x256\n - Library 1\n - Cluster 2\n - 1.0\n\n * - 128x128x128\n - Library 2\n - Cluster 2\n - 0.125\n\n * - 256x256x256\n - Library 2\n - Cluster 2\n - 0.5\n\nThe most obvious projection of this data onto P3 definitions is as follows:\n\n- Each input size maps to a different problem, because each input\n represents a different task to be solved, with its own expected answer to\n validate against.\n\n- Each implementation maps to a different application, because each\n library's implementation of the function produces a solution for a given\n input with measurable performance and correctness.\n\n- Each machine maps to a different platform, because each cluster name\n describes the combination of hardware **and** software used to run the\n experiments.\n\n.. important::\n In reality, a single value is unlikely to provide enough information to\n fully and unambiguously describe a function's behavior, its implementation,\n or the state of a machine when its performance was recorded. But we'll come\n back to that later.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After loading our data into a :py:class:`pandas.DataFrame`\n(``df``), we can use the\n:py:func:`p3analysis.data.projection` function to perform this\nprojection, renaming the columns as described above.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "proj = p3analysis.data.projection(\n df,\n problem=[\"size\"],\n application=[\"implementation\"],\n platform=[\"machine\"],\n)\nprint(proj)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Following projection, our performance data is now ready to be passed to\nfunctions in the :py:mod:`p3analysis.metrics` module.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using Projection to Combine Columns\n\nAs we alluded to earlier, it's unlikely that a single column of the raw data\nfully captures the definition of a \"problem\", \"application\" or \"platform\".\n\nLet's make our raw data slightly more complicated, by introducing the notion\nthat the function of interest is available in both single precision (FP32)\nand double precision (FP64).\n\n.. list-table::\n :widths: 20 20 20 20 20\n :header-rows: 1\n\n * - size\n - precision\n - implementation\n - machine\n - fom\n\n * - 128x128x128\n - FP32\n - Library 1\n - Cluster 1\n - 0.5\n\n * - 256x256x256\n - FP32\n - Library 1\n - Cluster 1\n - 2.0\n\n * - 128x128x128\n - FP32\n - Library 2\n - Cluster 1\n - 0.7\n\n * - 256x256x256\n - FP32\n - Library 2\n - Cluster 1\n - 2.1\n\n * - 128x128x128\n - FP32\n - Library 1\n - Cluster 2\n - 0.25\n\n * - 256x256x256\n - FP32\n - Library 1\n - Cluster 2\n - 1.0\n\n * - 128x128x128\n - FP32\n - Library 2\n - Cluster 2\n - 0.125\n\n * - 256x256x256\n - FP32\n - Library 2\n - Cluster 2\n - 0.5\n\n * - 128x128x128\n - FP64\n - Library 1\n - Cluster 1\n - 1.0\n\n * - 256x256x256\n - FP64\n - Library 1\n - Cluster 1\n - 4.0\n\n * - 128x128x128\n - FP64\n - Library 2\n - Cluster 1\n - 1.4\n\n * - 256x256x256\n - FP64\n - Library 2\n - Cluster 1\n - 4.2\n\n * - 128x128x128\n - FP64\n - Library 1\n - Cluster 2\n - 0.5\n\n * - 256x256x256\n - FP64\n - Library 1\n - Cluster 2\n - 2.0\n\n * - 128x128x128\n - FP64\n - Library 2\n - Cluster 2\n - 0.25\n\n * - 256x256x256\n - FP64\n - Library 2\n - Cluster 2\n - 1.0\n\nHow does this impact our projection? The implementation and machine columns\nare still enough to describe the application and platform (respectively),\nbut what about the problem? The answer is, of course: \"It depends\".\n\nLuckily, this dataset is simple enough that we can enumerate our options:\n\n1. Each unique (size, precision) tuple maps to a different problem,\n representing that the problem definition requires the task to be solved to\n a specific precision (and that the precision has a material impact on the\n verification of results).\n\n2. Each size maps to a different problem as before, representing that the\n problem definition does **not** require the task to be solved to any\n specific precision, and that implementations are free to select whichever\n precision delivers the best performance.\n\nNeither of these options is more correct than the other. Rather, they\nrepresent different studies.\n\nBoth projections can be performed with the :py:func:`p3analysis.data.projection`\nfunction, by passing different arguments.\n\nFor the first projection, we now need to specify the names of two columns\n(\"size\" and \"precision\") to define the problem:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "proj1 = p3analysis.data.projection(\n df,\n problem=[\"size\", \"precision\"],\n application=[\"implementation\"],\n platform=[\"machine\"],\n)\nprint(proj1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The original \"size\" and \"precision\" columns have been removed, and their\nvalues have been concatenated to form the new \"problem\" column.\n\nFor the second projection, we just need to specify \"size\", exactly as we did\nbefore:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "proj2 = p3analysis.data.projection(\n df,\n problem=[\"size\"],\n application=[\"implementation\"],\n platform=[\"machine\"],\n)\nprint(proj2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This time, the original \"size\" column has been removed, but the \"precision\"\ncolumn remains.\n\nClearly, the values provided to the projection function change the structure\nof the resulting :py:class:`pandas.DataFrame`. But why does that matter?\nWell, let's take a look at what happens if we compute the maximum \"fom\" for\neach (problem, application, platform) tuple in our projected datasets:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "max1 = proj1.groupby([\"problem\", \"application\", \"platform\"])[\"fom\"].max()\nprint(max1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "max2 = proj2.groupby([\"problem\", \"application\", \"platform\"])[\"fom\"].max()\nprint(max2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar :py:func:`pandas.DataFrame.groupby` calls form the backbone of many\nfunctions provided by the P3 Analysis Library, since metrics like\n\"application efficiency\" and \"performance portability\" ultimately depend on\nan understanding of which variable combinations deliver the best performance.\n\n.. important::\n The selected projection can have significant impact on the results of\n subsequent analysis, and it is critical to ensure that the projection\n is correct before digging too deep into (or presenting!) any results.\n\n## Next Steps\n\nAfter raw performance data has been projected onto definitions\nof \"problem\", \"application\", and \"platform\", it can be passed to\nany of the P3 Analysis Library functions that expect a\n:py:class:`pandas.DataFrame`.\n\nThe examples below show how to use projected data to compute\nand visualize derived metrics like \"application efficiency\",\n\"performance portability\", and \"code divergence\".\n\n.. minigallery::\n :add-heading: Examples\n\n ../../examples/metrics/application_efficiency.py\n ../../examples/cascade/plot_simple_cascade.py\n ../../examples/navchart/plot_simple_navchart.py\n\n" + ] + } + ], + "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.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/497f67f521e406e27e0c04370eaf55b0/projection.zip b/_downloads/497f67f521e406e27e0c04370eaf55b0/projection.zip new file mode 100644 index 0000000..ba9aeec Binary files /dev/null and b/_downloads/497f67f521e406e27e0c04370eaf55b0/projection.zip differ diff --git a/_downloads/4fc66f9a9f7717a6f401e66bb1256966/plot_simple_navchart.py b/_downloads/4fc66f9a9f7717a6f401e66bb1256966/plot_simple_navchart.py index 8717869..1b5b72b 100644 --- a/_downloads/4fc66f9a9f7717a6f401e66bb1256966/plot_simple_navchart.py +++ b/_downloads/4fc66f9a9f7717a6f401e66bb1256966/plot_simple_navchart.py @@ -25,16 +25,15 @@ of the P3 space and reasoning about how to reach development goals. """ -import matplotlib.pyplot as plt -import pandas as pd - -import p3 - # Initialize synthetic data # (not shown, but available in script download) # sphinx_gallery_start_ignore from collections import defaultdict +import pandas as pd + +import p3analysis + pp_data = defaultdict(list) cd_data = defaultdict(list) for data in [pp_data, cd_data]: @@ -55,6 +54,5 @@ cd = pd.DataFrame(cd_data) # Generate a navigation chart -fig = plt.figure(figsize=(5, 5)) -ax = p3.plot.navchart(pp, cd) -plt.savefig("navchart.png") +navchart = p3analysis.plot.navchart(pp, cd, size=(5, 5)) +navchart.save("navchart.png") diff --git a/_downloads/50b189a0f6c8c40714768470ea6ba889/multiple_components.ipynb b/_downloads/50b189a0f6c8c40714768470ea6ba889/multiple_components.ipynb new file mode 100644 index 0000000..323a017 --- /dev/null +++ b/_downloads/50b189a0f6c8c40714768470ea6ba889/multiple_components.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Handling Software with Multiple Components\n\nViewing applications as composites.\n\nWhen working with very large and complex pieces of software, reporting\nperformance using a single number (e.g., total time-to-solution) obscures\ndetails about the performance of different software components. Using such\ntotals during P3 analysis therefore prevents us from understanding how\ndifferent software components behave on different platforms.\n\nIdentifying which software components have poor P3 characteristics is necessary\nto understand what action(s) we can take to improve the P3 characteristics of\na software package as a whole. Although accounting for multiple components can\nmake data collection and analysis slightly more complicated, the additional\ninsight it provides is very valuable.\n\n.. tip::\n This approach can be readily applied to parallel software written to\n heterogeneous programming frameworks (e.g., CUDA, OpenCL, SYCL, Kokkos),\n where distinct \"kernel\"s can be identified and profiled easily. For a\n real-life example of this approach in practice, see \"[A\n Performance-Portable SYCL Implementation of CRK-HACC for Exascale](https://dl.acm.org/doi/10.1145/3624062.3624187).\n\n## Data Preparation\n\nTo keep things simple, let's imagine that our software package consists of just\ntwo components, and that each component has two different implementations that\ncan both be run on two different machines:\n\n .. list-table::\n :widths: 20 20 20 20\n :header-rows: 1\n\n * - component\n - implementation\n - machine\n - fom\n\n * - Component 1\n - Implementation 1\n - Cluster 1\n - 2.0\n\n * - Component 2\n - Implementation 1\n - Cluster 1\n - 5.0\n\n * - Component 1\n - Implementation 2\n - Cluster 1\n - 3.0\n\n * - Component 2\n - Implementation 2\n - Cluster 1\n - 4.0\n\n * - Component 1\n - Implementation 1\n - Cluster 2\n - 1.0\n\n * - Component 2\n - Implementation 1\n - Cluster 2\n - 2.5\n\n * - Component 1\n - Implementation 2\n - Cluster 2\n - 0.5\n\n * - Component 1\n - Implementation 2\n - Cluster 2\n - 3.0\n\nOur first step is to project this data onto P3 definitions,\ntreating the functionality provided by each component as a\nseparate problem to be solved:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "proj = p3analysis.data.projection(\n df,\n problem=[\"component\"],\n application=[\"implementation\"],\n platform=[\"machine\"],\n)\nprint(proj)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

Note

See \"`Understanding Data Projection `\" for\n more information about projection.

\n\n## Application Efficiency per Component\n\nHaving projected the performance data onto P3 definitions, we can now compute\nthe application efficiency for each component:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "effs = p3analysis.metrics.application_efficiency(proj)\nprint(effs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "

Note

See \"`Working with Application Efficiency\n `\" for more information about application\n efficiency.

\n\nPlotting a graph for each platform separately is a good way to visualize and\ncompare the application efficiency of each component:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "cluster1 = effs[effs[\"platform\"] == \"Cluster 1\"]\npivot = cluster1.pivot(index=\"application\", columns=[\"problem\"])[\"app eff\"]\npivot.plot(\n kind=\"bar\",\n xlabel=\"Component\",\n ylabel=\"Application Efficiency\",\n title=\"Cluster 1\",\n)\nplt.savefig(\"cluster1_application_efficiency_bars.png\")\n\ncluster2 = effs[effs[\"platform\"] == \"Cluster 2\"]\npivot = cluster2.pivot(index=\"application\", columns=[\"problem\"])[\"app eff\"]\npivot.plot(\n kind=\"bar\",\n xlabel=\"Component\",\n ylabel=\"Application Efficiency\",\n title=\"Cluster 2\",\n)\nplt.savefig(\"cluster2_application_efficiency_bars.png\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On Cluster 1, Implementation 1 delivers the best performance for Component 1,\nbut Implementation 2 delivers the best performance for Component 2. On\nCluster 2, that trend is reversed. Clearly, there is no single implementation\nthat delivers the best performance everywhere.\n\n## Overall Application Efficiency\n\nComputing the application efficiency of the software package as a whole\nrequires a few more steps.\n\nFirst, we need to compute the total time taken by each application on each\nplatform:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "package = proj.groupby([\"platform\", \"application\"], as_index=False)[\"fom\"].sum()\npackage[\"problem\"] = \"Package\"\nprint(package)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we can use this data to compute application efficiency, as below:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "effs = p3analysis.metrics.application_efficiency(package)\nprint(effs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These latest results suggest that both Implementation 1 and Implementation 2\nare both achieving the best-known performance when running the package as a\nwhole. This isn't *strictly* incorrect, since the values of their combined\nfigure-of-merit *are* the same, but we know from our earlier per-component\nanalysis that it could be possible to achieve better performance results.\n\nSpecifically, our per-component analysis shows us that an application that\ncould pick and choose the best implementation of different components for\ndifferent platforms would achieve better overall performance.\n\n.. important::\n Combining component implementations in this way is purely hypothetical,\n and there may be very good reasons (e.g., incompatible data structures)\n that an application is unable to use certain combinations. Although\n removing such invalid combinations would result in a tighter upper\n bound, it is much simpler to leave them in place. Including all\n combinations may even identify potential opportunities to combine\n approaches that initially appeared incompatible (e.g., by writing\n routines to convert between data structures).\n\nWe can fold that observation into our P3 analysis by creating an entry in our\ndataset that represents the results from a hypothetical application:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "hypothetical_components = proj.groupby([\"problem\", \"platform\"], as_index=False)[\n \"fom\"\n].min()\nhypothetical_components[\"application\"] = \"Hypothetical\"\nprint(hypothetical_components)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Calculate the combined figure of merit for both components\nhypothetical_package = hypothetical_components.groupby(\n [\"platform\", \"application\"], as_index=False,\n)[\"fom\"].sum()\nhypothetical_package[\"problem\"] = \"Package\"\n\n# Append the hypothetical package data to our previous results\npackage = pd.concat([package, hypothetical_package], ignore_index=True)\nprint(package)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As expected, our new hypothetical application achieves better performance\nby mixing and matching different implementations. And if we now re-compute\napplication efficiency with this data included:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "effs = p3analysis.metrics.application_efficiency(package)\nprint(effs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... we see that the application efficiency of Implementation 1 and\nImplementation 2 has been reduced accordingly. Including hypothetical\nupper-bounds of performance in our dataset can therefore be a simple and\neffective way to improve the accuracy of our P3 analysis, even if a\ntrue theoretical upper-bound (i.e., from a performance model) is unknown.\n\n

Note

The two implementations still have the *same* efficiency, even after\n introducing the hypothetical implementation. Per-component analysis is\n still required to understand how each component contributes to the\n overall efficiency, and to identify which component(s) should be improved\n on which platform(s).

\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Further Analysis\n\nComputing application efficiency is often simply the first step of a\nmore detailed P3 analysis.\n\nThe examples below show how we can use the visualization capabilities\nof the P3 Analysis Library to compare the efficiency of different\napplications running across the same platform set, or to gain insight\ninto how an application's efficiency relates to the code it uses on each\nplatform.\n\n.. minigallery::\n :add-heading: Examples\n\n ../../examples/cascade/plot_simple_cascade.py\n ../../examples/navchart/plot_simple_navchart.py\n\n" + ] + } + ], + "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.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/5395c89147cc159c306693497f74c574/plot_simple_cascade.zip b/_downloads/5395c89147cc159c306693497f74c574/plot_simple_cascade.zip new file mode 100644 index 0000000..5345f51 Binary files /dev/null and b/_downloads/5395c89147cc159c306693497f74c574/plot_simple_cascade.zip differ diff --git a/_downloads/59352f97890496454a6cb3cf836d0644/application_efficiency.py b/_downloads/59352f97890496454a6cb3cf836d0644/application_efficiency.py new file mode 100644 index 0000000..908b5ba --- /dev/null +++ b/_downloads/59352f97890496454a6cb3cf836d0644/application_efficiency.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 Intel Corporation +# SPDX-License-Identifier: 0BSD +""" +.. _working_with_app_efficiency: + +Working with Application Efficiency +=================================== + +Understanding relative performance. + +One goal of P3 analysis is to understand how well a given application is able +to adapt to and make effective use of the capabilities of different +platforms. Comparisons of *raw* performance (e.g., time to solution) across +platforms can't help us, because raw performance doesn't reflect how fast an +application **should** run. + +To address this, the P3 Analysis Library works with normalized *performance +efficiency* data. With normalized data, an application's performance can be +represented as a number in the range :math:`[0, 1]`, where :math:`1` means +an application is achieving the best possible performance. + +There are multiple ways we can normalize the data to measure relative +efficiency, and for this tutorial we will consider *application efficiency*, +defined below: + +**Application Efficiency** + The performance of an *application*, measured relative to the best known + performance previously demonstrated for solving the same *problem* on the + same *platform*. + +Working with application efficiency is simple because it does not rely on +performance models or theoretical hardware limits. Although it can't tell us +whether an application is performing as well as theoretically possible, it +shows how an application compares to the state-of-the-art, which is often good +enough. + +Calculating Application Efficiency +---------------------------------- + +Let's begin with a very simple example, with a single application, using the +data below: + +.. list-table:: + :widths: 20 20 20 20 20 + :header-rows: 1 + + * - problem + - application + - platform + - fom + - date + + * - Test + - MyApp + - A + - 25.0 + - 2023 + * - Test + - MyApp + - B + - 12.5 + - 2023 + * - Test + - MyApp + - C + - 25.0 + - 2023 + * - Test + - MyApp + - D + - NaN + - 2023 + * - Test + - MyApp + - E + - 5.0 + - 2023 + +.. tip:: + A NaN or 0.0 performance result is interpreted by the P3 Analysis Library + to mean that an application run was in some way invalid. We can use this + to explicitly represent cases where applications did not compile on + specific platforms, did not run to completion, or ran but produced + numerical results that failed some sort of verification. + +""" + +# %% +# After loading this data into a :py:class:`pandas.DataFrame` (`df`), we can +# use the :py:func:`p3analysis.metrics.application_efficiency` function to calculate a +# table of application efficiencies. + +#sphinx_gallery_start_ignore +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +import p3analysis + +myapp_data = { + "problem": ["Test"] * 5, + "application": ["MyApp"] * 5, + "platform": ["A", "B", "C", "D", "E"], + "fom": [25.0, 12.5, 25.0, np.nan, 5.0], + "date": [2023] * 5, +} + +df = pd.DataFrame(myapp_data) +#sphinx_gallery_end_ignore + +effs = p3analysis.metrics.application_efficiency(df) +print(effs) + +# %% +# These initial results may be a little surprising, because they're all +# either 1.0 or 0.0. What happened? Since our dataset contains only one result +# for MyApp on each platform, each non-zero result is the "best known" result +# for that platform! The only exception is Platform D, which is assigned an +# efficiency of 0.0 to reflect that it either did not compile, or did not run +# successfully. +# +# .. tip:: +# Calculating meaningful application efficiency results requires a minimum +# of two results *per platform*. +# +# Digging Deeper: Adding More Data +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# Let's see what happens if we add some more data, from a different application +# running on the same platforms: +# +# .. list-table:: +# :widths: 20 20 20 20 20 +# :header-rows: 1 +# +# * - problem +# - application +# - platform +# - fom +# - date +# +# * - Test +# - YourApp +# - A +# - 25.0 +# - 2023 +# * - Test +# - YourApp +# - B +# - 10.0 +# - 2023 +# * - Test +# - YourApp +# - C +# - 12.5 +# - 2023 +# * - Test +# - YourApp +# - D +# - 6.0 +# - 2023 +# * - Test +# - YourApp +# - E +# - 1.0 +# - 2023 + +# %% +# After updating our DataFrame, we can re-run the same function as before to +# recompute the application efficiencies. + +#sphinx_gallery_start_ignore +yourapp_data = { + "problem": ["Test"] * 5, + "application": ["YourApp"] * 5, + "platform": ["A", "B", "C", "D", "E"], + "fom": [25.0, 10.0, 12.5, 6.0, 1.0], + "date": [2023] * 5, +} + +df = pd.concat([df, pd.DataFrame(yourapp_data)], ignore_index=True) +#sphinx_gallery_end_ignore + +effs = p3analysis.metrics.application_efficiency(df) +print(effs) + +# %% +# YourApp is now the fastest (best known) application on every platform, +# and so it assigned an application efficiency of 1.0 everywhere. The +# application efficiency values for MyApp are all between 0.0 and 1.0, +# reflecting how close it gets to the state-of-the-art performance on each +# platform. +# +# .. important:: +# Adding new data changed the application efficiencies for MyApp *and* +# YourApp. Application efficiency values can become stale over time, +# and accurate P3 analysis requires us to track "best known" results +# carefully. +# +# Plotting Application Efficiency +# ------------------------------- +# +# The P3 Analysis Library does not contain any dedicated functionality for +# plotting application efficiency values. However, it is straightforward to use +# :py:mod:`matplotlib` and/or the plotting functionality of :py:mod:`pandas` to +# produce useful visualizations. +# +# For example, plotting a bar chart of application efficiences for one +# application can help us to summarize that application's performance more +# effectively than a table: + +filtered = effs[effs["application"]=="MyApp"] +filtered.plot(kind="bar", x="platform", y="app eff", xlabel="Platform", ylabel="Application Efficiency", legend=False) +plt.savefig("application_efficiency_bars_2023.png") + +# %% +# We can now clearly see that MyApp can adapt to and make very effective use of +# Platforms A and B, is within 2x of state-of-the-art performance on Platform +# C, but performs poorly on Platforms D and E. The key takeaway from this +# analysis is that a developer wishing to improve the "performance portability" +# of MyApp should focus on improving support for Platforms D and E. +# +# Working with Historical Data +# ---------------------------- +# +# The performance of an application can change over time, as developers add new +# features and optimize for new platforms, or due to changes in the software +# stack (e.g., new compiler or driver versions). +# +# Let's see how this could affect the application efficiency of MyApp, by adding +# some new data points collected at a later point in time: +# +# .. list-table:: +# :widths: 20 20 20 20 20 +# :header-rows: 1 +# +# * - problem +# - application +# - platform +# - fom +# - date +# +# * - Test +# - MyApp +# - A +# - 30.0 +# - 2024 +# * - Test +# - MyApp +# - B +# - 15.0 +# - 2024 +# * - Test +# - MyApp +# - C +# - 25.0 +# - 2024 +# * - Test +# - MyApp +# - D +# - 3.0 +# - 2024 +# * - Test +# - MyApp +# - E +# - 2.5 +# - 2024 + +# %% +# We can compute application efficiency as before: + +#sphinx_gallery_start_ignore +new_myapp_data = { + "problem": ["Test"] * 5, + "application": ["MyApp"] * 5, + "platform": ["A", "B", "C", "D", "E"], + "fom": [30.0, 15.0, 25.0, 3.0, 2.5], + "date": [2024] * 5, +} + +df = pd.concat([df, pd.DataFrame(new_myapp_data)], ignore_index=True) +#sphinx_gallery_end_ignore + +effs = p3analysis.metrics.application_efficiency(df) +print(effs) + +# %% +# These latest results suggest that the developers of MyApp acted upon +# earlier results and improved support for Platforms D and E. But in doing so, +# a small performance regression was introduced in Platforms A and B. +# +# .. note:: +# Such trade-offs are very common, especially when developers wish to +# maintain a single source code that targets multiple platforms. +# Different platforms may respond differently to the same code changes, +# owing to architectural differences (e.g., cache size, available +# parallelism) or differences in the software stack (e.g., compilers +# performing different optimizations). +# For some real-life examples, see the papers +# `here `__ +# and +# `here `__. +# +# Computing the correct application efficiency values for MyApp and YourApp +# requires that our dataset contains all of our historical performance results. +# Since what we're really interested in understanding is the *latest* +# application efficiency, we should take care to filter our data appropriately +# before producing any plots. + +filtered = effs[(effs["application"]=="MyApp") & (effs["date"]==2024)] +filtered.plot(kind="bar", x="platform", y="app eff", xlabel="Platform", ylabel="Application Efficiency", legend=False) +plt.savefig("application_efficiency_bars_2024.png") + +# %% +# Further Analysis +# ---------------- +# +# Computing application efficiency is often simply the first step of a +# more detailed P3 analysis. +# +# The examples below show how we can use the visualization capabilities +# of the P3 Analysis Library to compare the efficiency of different +# applications running across the same platform set, or to gain insight +# into how an application's efficiency relates to the code it uses on each +# platform. +# +# .. minigallery:: +# :add-heading: Examples +# +# ../../examples/cascade/plot_simple_cascade.py +# ../../examples/navchart/plot_simple_navchart.py diff --git a/_downloads/5def2c8c46cfdf2f0d873ad4bfb015fd/plot_babelstream_navchart.zip b/_downloads/5def2c8c46cfdf2f0d873ad4bfb015fd/plot_babelstream_navchart.zip new file mode 100644 index 0000000..86bf851 Binary files /dev/null and b/_downloads/5def2c8c46cfdf2f0d873ad4bfb015fd/plot_babelstream_navchart.zip differ diff --git a/_downloads/63d25a6d655f59551dbcb0dc6f6da08b/plot_simple_navchart.zip b/_downloads/63d25a6d655f59551dbcb0dc6f6da08b/plot_simple_navchart.zip new file mode 100644 index 0000000..5027f32 Binary files /dev/null and b/_downloads/63d25a6d655f59551dbcb0dc6f6da08b/plot_simple_navchart.zip differ diff --git a/_downloads/64693b4e3c7190c6f68982b862f83243/plot_customized_cascade.py b/_downloads/64693b4e3c7190c6f68982b862f83243/plot_customized_cascade.py new file mode 100644 index 0000000..ca1f691 --- /dev/null +++ b/_downloads/64693b4e3c7190c6f68982b862f83243/plot_customized_cascade.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 Intel Corporation +# SPDX-License-Identifier: 0BSD +""" +Customized Cascade +================== + +A customized cascade plot. + +In this example, we show how to customize a cascade plot by changing the limits +of the y-axis. Although the default limit (of 1) is useful for comparing many +plots side-by-side, in practice it is often useful to be able to zoom-in on +specific regions of data. For example, when dealing with applications that do +not achieve very high levels of architectural efficiency, setting a lower +maximum value for the y-axis can improve readability. + +Instead of trying to expose all possible customization options as arguments to +:py:func:`p3analysis.plot.cascade`, the function returns a +:py:class:`p3analysis.plot.CascadePlot` object that provides direct access to library +internals. When using the :py:mod:`matplotlib` backend it is possible to +access the :py:class:`matplotlib.axes.Axes` that were used and subsequently +call any number of :py:mod:`matplotlib` functions. In our example, we can +use :py:meth:`matplotlib.axes.Axes.set_ylim` to update the y-axis. + +.. NOTE:: + :py:mod:`matplotlib` is currently the only backend supported by the P3 + Analysis Library, but this is subject to change. + +.. TIP:: + If you have any trouble customizing a plot, or the + :py:class:`~p3analysis.plot.backend.CascadePlot` object does not provide access to + the internals you are looking for, then please `open an issue + `_. +""" + +# Initialize synthetic performance efficiency data +# (not shown, but available in script download) +# sphinx_gallery_start_ignore +from collections import defaultdict + +import pandas as pd + +import p3analysis + +data = defaultdict(list) +for (i, platform) in enumerate(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]): + data["problem"] += ["Synthetic"] * 6 + data["platform"] += [platform] * 6 + data["application"] += [ + "Unportable", + "Single Target", + "Multi-Target", + "Consistent (3%)", + "Consistent (7%)", + "Inconsistent", + ] + eff = [0] * 6 + eff[0] = 0.1 if i == 0 else 0 + eff[1] = 0.1 if i == 0 else 0.01 + eff[2] = 0.01 if i % 2 else 0.1 + eff[3] = 0.03 + eff[4] = 0.07 + eff[5] = (i + 1) * 0.01 + data["arch eff"] += eff +# sphinx_gallery_end_ignore + +# Read performance efficiency data into pandas DataFrame +df = pd.DataFrame(data) + +# Generate a cascade plot with custom style options +legend = p3analysis.plot.Legend(loc="center left", bbox_to_anchor=(0.91, 0.225), ncols=2) +pstyle = p3analysis.plot.PlatformStyle(colors="GnBu") +astyle = p3analysis.plot.ApplicationStyle(markers=["x", "s", "p", "h", "H", "v"]) +cascade = p3analysis.plot.cascade(df, size=(6, 5), platform_legend=legend, platform_style=pstyle, application_style=astyle) + +# Further customize the cascade plot using matplotlib +# In this example, we adjust the range of the y-axis to improve readability +# This may be necessary for studies using architectural efficiency +cascade.get_axes("eff").set_ylim([0, 0.12]) +cascade.get_axes("pp").set_ylim([0, 0.12]) + +cascade.save("customized-cascade.png") diff --git a/_downloads/7b771bddd0782685a73c72442585bd5b/multiple_components.py b/_downloads/7b771bddd0782685a73c72442585bd5b/multiple_components.py new file mode 100644 index 0000000..8a2eabf --- /dev/null +++ b/_downloads/7b771bddd0782685a73c72442585bd5b/multiple_components.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 Intel Corporation +# SPDX-License-Identifier: 0BSD +""" +Handling Software with Multiple Components +========================================== + +Viewing applications as composites. + +When working with very large and complex pieces of software, reporting +performance using a single number (e.g., total time-to-solution) obscures +details about the performance of different software components. Using such +totals during P3 analysis therefore prevents us from understanding how +different software components behave on different platforms. + +Identifying which software components have poor P3 characteristics is necessary +to understand what action(s) we can take to improve the P3 characteristics of +a software package as a whole. Although accounting for multiple components can +make data collection and analysis slightly more complicated, the additional +insight it provides is very valuable. + +.. tip:: + This approach can be readily applied to parallel software written to + heterogeneous programming frameworks (e.g., CUDA, OpenCL, SYCL, Kokkos), + where distinct "kernel"s can be identified and profiled easily. For a + real-life example of this approach in practice, see "`A + Performance-Portable SYCL Implementation of CRK-HACC for Exascale + `_. + +Data Preparation +---------------- + +To keep things simple, let's imagine that our software package consists of just +two components, and that each component has two different implementations that +can both be run on two different machines: + + .. list-table:: + :widths: 20 20 20 20 + :header-rows: 1 + + * - component + - implementation + - machine + - fom + + * - Component 1 + - Implementation 1 + - Cluster 1 + - 2.0 + + * - Component 2 + - Implementation 1 + - Cluster 1 + - 5.0 + + * - Component 1 + - Implementation 2 + - Cluster 1 + - 3.0 + + * - Component 2 + - Implementation 2 + - Cluster 1 + - 4.0 + + * - Component 1 + - Implementation 1 + - Cluster 2 + - 1.0 + + * - Component 2 + - Implementation 1 + - Cluster 2 + - 2.5 + + * - Component 1 + - Implementation 2 + - Cluster 2 + - 0.5 + + * - Component 1 + - Implementation 2 + - Cluster 2 + - 3.0 + +Our first step is to project this data onto P3 definitions, +treating the functionality provided by each component as a +separate problem to be solved: + +""" + +# sphinx_gallery_start_ignore +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +import p3analysis + +data = { + "component": ["Component 1", "Component 2"] * 4, + "implementation": (["Implementation 1"] * 2 + ["Implementation 2"] * 2) * 2, + "machine": ["Cluster 1"] * 4 + ["Cluster 2"] * 4, + "fom": [2.0, 5.0, 3.0, 4.0, 1.0, 2.5, 0.5, 3.0], +} + +df = pd.DataFrame(data) +# sphinx_gallery_end_ignore + +proj = p3analysis.data.projection( + df, + problem=["component"], + application=["implementation"], + platform=["machine"], +) +print(proj) + +# %% +# .. note:: +# See ":ref:`Understanding Data Projection `" for +# more information about projection. +# +# Application Efficiency per Component +# ------------------------------------ +# +# Having projected the performance data onto P3 definitions, we can now compute +# the application efficiency for each component: + +effs = p3analysis.metrics.application_efficiency(proj) +print(effs) + +# %% +# .. note:: +# See ":ref:`Working with Application Efficiency +# `" for more information about application +# efficiency. +# +# Plotting a graph for each platform separately is a good way to visualize and +# compare the application efficiency of each component: + +cluster1 = effs[effs["platform"] == "Cluster 1"] +pivot = cluster1.pivot(index="application", columns=["problem"])["app eff"] +pivot.plot( + kind="bar", + xlabel="Component", + ylabel="Application Efficiency", + title="Cluster 1", +) +plt.savefig("cluster1_application_efficiency_bars.png") + +cluster2 = effs[effs["platform"] == "Cluster 2"] +pivot = cluster2.pivot(index="application", columns=["problem"])["app eff"] +pivot.plot( + kind="bar", + xlabel="Component", + ylabel="Application Efficiency", + title="Cluster 2", +) +plt.savefig("cluster2_application_efficiency_bars.png") + +# %% +# On Cluster 1, Implementation 1 delivers the best performance for Component 1, +# but Implementation 2 delivers the best performance for Component 2. On +# Cluster 2, that trend is reversed. Clearly, there is no single implementation +# that delivers the best performance everywhere. +# +# Overall Application Efficiency +# ------------------------------ +# +# Computing the application efficiency of the software package as a whole +# requires a few more steps. +# +# First, we need to compute the total time taken by each application on each +# platform: + +package = proj.groupby(["platform", "application"], as_index=False)["fom"].sum() +package["problem"] = "Package" +print(package) + +# %% +# Then, we can use this data to compute application efficiency, as below: + +effs = p3analysis.metrics.application_efficiency(package) +print(effs) + +# %% +# These latest results suggest that both Implementation 1 and Implementation 2 +# are both achieving the best-known performance when running the package as a +# whole. This isn't *strictly* incorrect, since the values of their combined +# figure-of-merit *are* the same, but we know from our earlier per-component +# analysis that it could be possible to achieve better performance results. +# +# Specifically, our per-component analysis shows us that an application that +# could pick and choose the best implementation of different components for +# different platforms would achieve better overall performance. +# +# .. important:: +# Combining component implementations in this way is purely hypothetical, +# and there may be very good reasons (e.g., incompatible data structures) +# that an application is unable to use certain combinations. Although +# removing such invalid combinations would result in a tighter upper +# bound, it is much simpler to leave them in place. Including all +# combinations may even identify potential opportunities to combine +# approaches that initially appeared incompatible (e.g., by writing +# routines to convert between data structures). +# +# We can fold that observation into our P3 analysis by creating an entry in our +# dataset that represents the results from a hypothetical application: + +hypothetical_components = proj.groupby(["problem", "platform"], as_index=False)[ + "fom" +].min() +hypothetical_components["application"] = "Hypothetical" +print(hypothetical_components) + +# %% + +# Calculate the combined figure of merit for both components +hypothetical_package = hypothetical_components.groupby( + ["platform", "application"], as_index=False, +)["fom"].sum() +hypothetical_package["problem"] = "Package" + +# Append the hypothetical package data to our previous results +package = pd.concat([package, hypothetical_package], ignore_index=True) +print(package) + +# %% +# As expected, our new hypothetical application achieves better performance +# by mixing and matching different implementations. And if we now re-compute +# application efficiency with this data included: + +effs = p3analysis.metrics.application_efficiency(package) +print(effs) + +# %% +# ... we see that the application efficiency of Implementation 1 and +# Implementation 2 has been reduced accordingly. Including hypothetical +# upper-bounds of performance in our dataset can therefore be a simple and +# effective way to improve the accuracy of our P3 analysis, even if a +# true theoretical upper-bound (i.e., from a performance model) is unknown. +# +# .. note:: +# The two implementations still have the *same* efficiency, even after +# introducing the hypothetical implementation. Per-component analysis is +# still required to understand how each component contributes to the +# overall efficiency, and to identify which component(s) should be improved +# on which platform(s). + +# %% +# Further Analysis +# ---------------- +# +# Computing application efficiency is often simply the first step of a +# more detailed P3 analysis. +# +# The examples below show how we can use the visualization capabilities +# of the P3 Analysis Library to compare the efficiency of different +# applications running across the same platform set, or to gain insight +# into how an application's efficiency relates to the code it uses on each +# platform. +# +# .. minigallery:: +# :add-heading: Examples +# +# ../../examples/cascade/plot_simple_cascade.py +# ../../examples/navchart/plot_simple_navchart.py diff --git a/_downloads/7b9e708df771ebd154427a5a10ba5150/plot_babelstream_navchart.py b/_downloads/7b9e708df771ebd154427a5a10ba5150/plot_babelstream_navchart.py index 37ab82e..67e2b33 100644 --- a/_downloads/7b9e708df771ebd154427a5a10ba5150/plot_babelstream_navchart.py +++ b/_downloads/7b9e708df771ebd154427a5a10ba5150/plot_babelstream_navchart.py @@ -16,13 +16,14 @@ # Load Data into Pandas # --------------------- +#sphinx_gallery_start_ignore +import os + # import libraries import pandas as pd -import matplotlib.pyplot as plt -import p3 -#sphinx_gallery_start_ignore -import os +import p3analysis + dpath = os.path.realpath(os.getcwd()) performance_csv = os.path.join(dpath, "performance.csv") coverage_csv = os.path.join(dpath, "coverage.csv") @@ -41,11 +42,11 @@ # %% # Project Labels into Expected Forms # ---------------------------------- -# The :py:func:`p3.data.projection` method can be used to project column names +# The :py:func:`p3analysis.data.projection` method can be used to project column names # from the original data into names required by the P3 Analysis Library. -df = p3.data.projection( - df, problem=["name"], platform=["arch"], application=["language"] +df = p3analysis.data.projection( + df, problem=["name"], platform=["arch"], application=["language"], ) # %% @@ -75,7 +76,7 @@ # (problem, platform) combination; any row with an `app eff` of 1 represents # an application achieving the best-known performance. -effs = p3.metrics.application_efficiency(df) +effs = p3analysis.metrics.application_efficiency(df) print(effs) # %% @@ -86,7 +87,7 @@ # has a non-zero divergence, as it is the only implementation containing # different code paths for different platforms. -div = p3.metrics.divergence(df, df_cov) +div = p3analysis.metrics.divergence(df, df_cov) print(div) # %% @@ -97,16 +98,15 @@ # to achieve a non-zero performance portability score. For BabelStream, # Language 0 is the only programming model that runs across all 11 platforms. -pp = p3.metrics.pp(effs) +pp = p3analysis.metrics.pp(effs) print(pp) # %% # Generate a Navigation Chart # --------------------------- -fig = plt.figure(figsize=(5, 5)) -ax = p3.plot.navchart(pp, div) -plt.savefig("navchart.png") +navchart = p3analysis.plot.navchart(pp, div) +navchart.save("navchart.png") # %% # The plot shows the performance portability and code convergence values for diff --git a/_downloads/8a894c42ddeeaab2c25c35d5dcd9d396/plot_customized_cascade.zip b/_downloads/8a894c42ddeeaab2c25c35d5dcd9d396/plot_customized_cascade.zip new file mode 100644 index 0000000..2dcf5fe Binary files /dev/null and b/_downloads/8a894c42ddeeaab2c25c35d5dcd9d396/plot_customized_cascade.zip differ diff --git a/_downloads/9f3417b5ea9c0221a0c4d240fcb14aed/case-studies_jupyter.zip b/_downloads/9f3417b5ea9c0221a0c4d240fcb14aed/case-studies_jupyter.zip index 5703b81..4f9dbe5 100644 Binary files a/_downloads/9f3417b5ea9c0221a0c4d240fcb14aed/case-studies_jupyter.zip and b/_downloads/9f3417b5ea9c0221a0c4d240fcb14aed/case-studies_jupyter.zip differ diff --git a/_downloads/bc82bea3a5dd7bdba60b65220891d9e5/examples_python.zip b/_downloads/bc82bea3a5dd7bdba60b65220891d9e5/examples_python.zip index cf8a523..ee473c0 100644 Binary files a/_downloads/bc82bea3a5dd7bdba60b65220891d9e5/examples_python.zip and b/_downloads/bc82bea3a5dd7bdba60b65220891d9e5/examples_python.zip differ diff --git a/_downloads/bc8e97bbfe262a17fbb42353e9e5c42f/plot_babelstream_cascade.py b/_downloads/bc8e97bbfe262a17fbb42353e9e5c42f/plot_babelstream_cascade.py index 1938df4..9a1726c 100644 --- a/_downloads/bc8e97bbfe262a17fbb42353e9e5c42f/plot_babelstream_cascade.py +++ b/_downloads/bc8e97bbfe262a17fbb42353e9e5c42f/plot_babelstream_cascade.py @@ -20,13 +20,14 @@ # Load Data into Pandas # --------------------- +#sphinx_gallery_start_ignore +import os + # import libraries import pandas as pd -import matplotlib.pyplot as plt -import p3 -#sphinx_gallery_start_ignore -import os +import p3analysis + dpath = os.path.realpath(os.getcwd()) performance_csv = os.path.join(dpath, "performance.csv") #sphinx_gallery_end_ignore @@ -37,11 +38,11 @@ # %% # Project Labels into Expected Forms # ---------------------------------- -# The :py:func:`p3.data.projection` method can be used to project column names +# The :py:func:`p3analysis.data.projection` method can be used to project column names # from the original data into names required by the P3 Analysis Library. -df = p3.data.projection( - df, problem=["name"], platform=["arch"], application=["language"] +df = p3analysis.data.projection( + df, problem=["name"], platform=["arch"], application=["language"], ) # %% @@ -68,16 +69,15 @@ # (problem, platform) combination; any row with an `app eff` of 1 represents # an application achieving the best-known performance. -effs = p3.metrics.application_efficiency(df) +effs = p3analysis.metrics.application_efficiency(df) print(effs) # %% # Generate a Cascade Plot # ----------------------- -fig = plt.figure(figsize=(6, 5)) -ax = p3.plot.cascade(effs) -plt.savefig("cascade.png", bbox_inches="tight") +cascade = p3analysis.plot.cascade(effs) +cascade.save("cascade.png") # %% # The plot shows the *application efficiency* (line chart, left) and diff --git a/_downloads/c7bc97f3470206b44df0bcc2bac16d76/plot_customized_navchart.zip b/_downloads/c7bc97f3470206b44df0bcc2bac16d76/plot_customized_navchart.zip new file mode 100644 index 0000000..1272f17 Binary files /dev/null and b/_downloads/c7bc97f3470206b44df0bcc2bac16d76/plot_customized_navchart.zip differ diff --git a/_downloads/cda5bdd84eac85cdf77eb751d123eeaa/plot_babelstream_navchart.ipynb b/_downloads/cda5bdd84eac85cdf77eb751d123eeaa/plot_babelstream_navchart.ipynb index d9c0c17..1ab4398 100644 --- a/_downloads/cda5bdd84eac85cdf77eb751d123eeaa/plot_babelstream_navchart.ipynb +++ b/_downloads/cda5bdd84eac85cdf77eb751d123eeaa/plot_babelstream_navchart.ipynb @@ -1,16 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -33,7 +22,7 @@ }, "outputs": [], "source": [ - "# import libraries\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport p3\n\n\n# Load data from BabelStream results\ndf = pd.read_csv(performance_csv)\ndf_cov = pd.read_csv(coverage_csv)" + "# Load data from BabelStream results\ndf = pd.read_csv(performance_csv)\ndf_cov = pd.read_csv(coverage_csv)" ] }, { @@ -47,7 +36,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Project Labels into Expected Forms\nThe :py:func:`p3.data.projection` method can be used to project column names\nfrom the original data into names required by the P3 Analysis Library.\n\n" + "## Project Labels into Expected Forms\nThe :py:func:`p3analysis.data.projection` method can be used to project column names\nfrom the original data into names required by the P3 Analysis Library.\n\n" ] }, { @@ -58,7 +47,7 @@ }, "outputs": [], "source": [ - "df = p3.data.projection(\n df, problem=[\"name\"], platform=[\"arch\"], application=[\"language\"]\n)" + "df = p3analysis.data.projection(\n df, problem=[\"name\"], platform=[\"arch\"], application=[\"language\"],\n)" ] }, { @@ -83,7 +72,7 @@ }, "outputs": [], "source": [ - "effs = p3.metrics.application_efficiency(df)\nprint(effs)" + "effs = p3analysis.metrics.application_efficiency(df)\nprint(effs)" ] }, { @@ -101,7 +90,7 @@ }, "outputs": [], "source": [ - "div = p3.metrics.divergence(df, df_cov)\nprint(div)" + "div = p3analysis.metrics.divergence(df, df_cov)\nprint(div)" ] }, { @@ -119,7 +108,7 @@ }, "outputs": [], "source": [ - "pp = p3.metrics.pp(effs)\nprint(pp)" + "pp = p3analysis.metrics.pp(effs)\nprint(pp)" ] }, { @@ -137,7 +126,7 @@ }, "outputs": [], "source": [ - "fig = plt.figure(figsize=(5, 5))\nax = p3.plot.navchart(pp, div)\nplt.savefig(\"navchart.png\")" + "navchart = p3analysis.plot.navchart(pp, div)\nnavchart.save(\"navchart.png\")" ] }, { @@ -164,7 +153,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/_downloads/dc1bc69ba78b54d4d52ec80d63940962/multiple_components.zip b/_downloads/dc1bc69ba78b54d4d52ec80d63940962/multiple_components.zip new file mode 100644 index 0000000..1864a2f Binary files /dev/null and b/_downloads/dc1bc69ba78b54d4d52ec80d63940962/multiple_components.zip differ diff --git a/_downloads/df0ea69ce19157e4de3a35a149e1a089/application_efficiency.ipynb b/_downloads/df0ea69ce19157e4de3a35a149e1a089/application_efficiency.ipynb new file mode 100644 index 0000000..392c65e --- /dev/null +++ b/_downloads/df0ea69ce19157e4de3a35a149e1a089/application_efficiency.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n\n# Working with Application Efficiency\n\nUnderstanding relative performance.\n\nOne goal of P3 analysis is to understand how well a given application is able\nto adapt to and make effective use of the capabilities of different\nplatforms. Comparisons of *raw* performance (e.g., time to solution) across\nplatforms can't help us, because raw performance doesn't reflect how fast an\napplication **should** run.\n\nTo address this, the P3 Analysis Library works with normalized *performance\nefficiency* data. With normalized data, an application's performance can be\nrepresented as a number in the range $[0, 1]$, where $1$ means\nan application is achieving the best possible performance.\n\nThere are multiple ways we can normalize the data to measure relative\nefficiency, and for this tutorial we will consider *application efficiency*,\ndefined below:\n\n**Application Efficiency**\n The performance of an *application*, measured relative to the best known\n performance previously demonstrated for solving the same *problem* on the\n same *platform*.\n\nWorking with application efficiency is simple because it does not rely on\nperformance models or theoretical hardware limits. Although it can't tell us\nwhether an application is performing as well as theoretically possible, it\nshows how an application compares to the state-of-the-art, which is often good\nenough.\n\n## Calculating Application Efficiency\n\nLet's begin with a very simple example, with a single application, using the\ndata below:\n\n.. list-table::\n :widths: 20 20 20 20 20\n :header-rows: 1\n\n * - problem\n - application\n - platform\n - fom\n - date\n\n * - Test\n - MyApp\n - A\n - 25.0\n - 2023\n * - Test\n - MyApp\n - B\n - 12.5\n - 2023\n * - Test\n - MyApp\n - C\n - 25.0\n - 2023\n * - Test\n - MyApp\n - D\n - NaN\n - 2023\n * - Test\n - MyApp\n - E\n - 5.0\n - 2023\n\n.. tip::\n A NaN or 0.0 performance result is interpreted by the P3 Analysis Library\n to mean that an application run was in some way invalid. We can use this\n to explicitly represent cases where applications did not compile on\n specific platforms, did not run to completion, or ran but produced\n numerical results that failed some sort of verification.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After loading this data into a :py:class:`pandas.DataFrame` (`df`), we can\nuse the :py:func:`p3analysis.metrics.application_efficiency` function to calculate a\ntable of application efficiencies.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "effs = p3analysis.metrics.application_efficiency(df)\nprint(effs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These initial results may be a little surprising, because they're all\neither 1.0 or 0.0. What happened? Since our dataset contains only one result\nfor MyApp on each platform, each non-zero result is the \"best known\" result\nfor that platform! The only exception is Platform D, which is assigned an\nefficiency of 0.0 to reflect that it either did not compile, or did not run\nsuccessfully.\n\n.. tip::\n Calculating meaningful application efficiency results requires a minimum\n of two results *per platform*.\n\n### Digging Deeper: Adding More Data\n\nLet's see what happens if we add some more data, from a different application\nrunning on the same platforms:\n\n.. list-table::\n :widths: 20 20 20 20 20\n :header-rows: 1\n\n * - problem\n - application\n - platform\n - fom\n - date\n\n * - Test\n - YourApp\n - A\n - 25.0\n - 2023\n * - Test\n - YourApp\n - B\n - 10.0\n - 2023\n * - Test\n - YourApp\n - C\n - 12.5\n - 2023\n * - Test\n - YourApp\n - D\n - 6.0\n - 2023\n * - Test\n - YourApp\n - E\n - 1.0\n - 2023\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After updating our DataFrame, we can re-run the same function as before to\nrecompute the application efficiencies.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "effs = p3analysis.metrics.application_efficiency(df)\nprint(effs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "YourApp is now the fastest (best known) application on every platform,\nand so it assigned an application efficiency of 1.0 everywhere. The\napplication efficiency values for MyApp are all between 0.0 and 1.0,\nreflecting how close it gets to the state-of-the-art performance on each\nplatform.\n\n.. important::\n Adding new data changed the application efficiencies for MyApp *and*\n YourApp. Application efficiency values can become stale over time,\n and accurate P3 analysis requires us to track \"best known\" results\n carefully.\n\n## Plotting Application Efficiency\n\nThe P3 Analysis Library does not contain any dedicated functionality for\nplotting application efficiency values. However, it is straightforward to use\n:py:mod:`matplotlib` and/or the plotting functionality of :py:mod:`pandas` to\nproduce useful visualizations.\n\nFor example, plotting a bar chart of application efficiences for one\napplication can help us to summarize that application's performance more\neffectively than a table:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "filtered = effs[effs[\"application\"]==\"MyApp\"]\nfiltered.plot(kind=\"bar\", x=\"platform\", y=\"app eff\", xlabel=\"Platform\", ylabel=\"Application Efficiency\", legend=False)\nplt.savefig(\"application_efficiency_bars_2023.png\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now clearly see that MyApp can adapt to and make very effective use of\nPlatforms A and B, is within 2x of state-of-the-art performance on Platform\nC, but performs poorly on Platforms D and E. The key takeaway from this\nanalysis is that a developer wishing to improve the \"performance portability\"\nof MyApp should focus on improving support for Platforms D and E.\n\n## Working with Historical Data\n\nThe performance of an application can change over time, as developers add new\nfeatures and optimize for new platforms, or due to changes in the software\nstack (e.g., new compiler or driver versions).\n\nLet's see how this could affect the application efficiency of MyApp, by adding\nsome new data points collected at a later point in time:\n\n.. list-table::\n :widths: 20 20 20 20 20\n :header-rows: 1\n\n * - problem\n - application\n - platform\n - fom\n - date\n\n * - Test\n - MyApp\n - A\n - 30.0\n - 2024\n * - Test\n - MyApp\n - B\n - 15.0\n - 2024\n * - Test\n - MyApp\n - C\n - 25.0\n - 2024\n * - Test\n - MyApp\n - D\n - 3.0\n - 2024\n * - Test\n - MyApp\n - E\n - 2.5\n - 2024\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can compute application efficiency as before:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "effs = p3analysis.metrics.application_efficiency(df)\nprint(effs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These latest results suggest that the developers of MyApp acted upon\nearlier results and improved support for Platforms D and E. But in doing so,\na small performance regression was introduced in Platforms A and B.\n\n

Note

Such trade-offs are very common, especially when developers wish to\n maintain a single source code that targets multiple platforms.\n Different platforms may respond differently to the same code changes,\n owing to architectural differences (e.g., cache size, available\n parallelism) or differences in the software stack (e.g., compilers\n performing different optimizations).\n For some real-life examples, see the papers\n [here](https://doi.org/10.1016/j.jpdc.2012.07.005)_\n and\n [here](https://doi.org/10.48550/arXiv.2407.11488)_.

\n\nComputing the correct application efficiency values for MyApp and YourApp\nrequires that our dataset contains all of our historical performance results.\nSince what we're really interested in understanding is the *latest*\napplication efficiency, we should take care to filter our data appropriately\nbefore producing any plots.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "filtered = effs[(effs[\"application\"]==\"MyApp\") & (effs[\"date\"]==2024)]\nfiltered.plot(kind=\"bar\", x=\"platform\", y=\"app eff\", xlabel=\"Platform\", ylabel=\"Application Efficiency\", legend=False)\nplt.savefig(\"application_efficiency_bars_2024.png\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Further Analysis\n\nComputing application efficiency is often simply the first step of a\nmore detailed P3 analysis.\n\nThe examples below show how we can use the visualization capabilities\nof the P3 Analysis Library to compare the efficiency of different\napplications running across the same platform set, or to gain insight\ninto how an application's efficiency relates to the code it uses on each\nplatform.\n\n.. minigallery::\n :add-heading: Examples\n\n ../../examples/cascade/plot_simple_cascade.py\n ../../examples/navchart/plot_simple_navchart.py\n\n" + ] + } + ], + "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.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/edc26f90f5285983edbd8cc895430081/projection.py b/_downloads/edc26f90f5285983edbd8cc895430081/projection.py new file mode 100644 index 0000000..355cdcc --- /dev/null +++ b/_downloads/edc26f90f5285983edbd8cc895430081/projection.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 Intel Corporation +# SPDX-License-Identifier: 0BSD +""" +.. _understanding_projection: + +Understanding Data Projection +============================= + +Projecting data onto P3 definitions. + +The P3 Analysis Library expects data to be prepared in a specific +:ref:`format `. This format was inspired by the +:ref:`terminology ` first introduced in "`Implications of a Metric +for Performance Portability`_": + +.. _Implications of a Metric for Performance Portability: + https://doi.org/10.1016/j.future.2017.08.007 + +**Problem** + A task with a pass/fail metric for which quantitative performance may be + measured. Multiplying an :math:`N \\times K` matrix by a :math:`K \\times M` + matrix to the accuracy guaranteed by IEEE 754 double precision, computing + :math:`\\pi` to a certain number of decimal places, and sorting an array of + :math:`N` elements are all examples of problems. + +**Application** + Software capable of solving a *problem* with measurable correctness and + performance. Math libraries, Python scripts, C functions, and entire software + packages are all examples of applications; the Intel |reg| oneAPI Math Kernel + Library is an example of an application for solving linear algebra problems. + +.. |reg| unicode:: U+00AE + :ltrim: + +**Platform** + A collection of software **and** hardware on which an *application* may run a + *problem*. A specific processor coupled with an operating system, compiler, + runtime, drivers, library dependencies, etc is an example of a precise + platform definition. + +These definitions are flexible, allowing the same performance data to be used +for multiple case studies with different interpretations of these terms. + +Rather than store raw performance data in columns corresponding to these +definitions, the P3 Analysis Library provides functionality to *project* raw +performance data onto specific meanings of "problem", "application" and +"platform". + +Using Projection to Rename Columns +---------------------------------- + +The simplest example of projection is a straightforward renaming of columns. + +Let's assume that we've collected some performance data from a few different +implementations of a function, running a number of problem sizes on multiple +machines. + +.. important:: + Although we are looking at "function" performance here, the concepts + generalize to entire software packages. + +Our raw performance data might look like this: + +.. list-table:: + :widths: 20 20 20 20 + :header-rows: 1 + + * - size + - implementation + - machine + - fom + + * - 128x128x128 + - Library 1 + - Cluster 1 + - 0.5 + + * - 256x256x256 + - Library 1 + - Cluster 1 + - 2.0 + + * - 128x128x128 + - Library 2 + - Cluster 1 + - 0.7 + + * - 256x256x256 + - Library 2 + - Cluster 1 + - 2.1 + + * - 128x128x128 + - Library 1 + - Cluster 2 + - 0.25 + + * - 256x256x256 + - Library 1 + - Cluster 2 + - 1.0 + + * - 128x128x128 + - Library 2 + - Cluster 2 + - 0.125 + + * - 256x256x256 + - Library 2 + - Cluster 2 + - 0.5 + +The most obvious projection of this data onto P3 definitions is as follows: + +- Each input size maps to a different problem, because each input + represents a different task to be solved, with its own expected answer to + validate against. + +- Each implementation maps to a different application, because each + library's implementation of the function produces a solution for a given + input with measurable performance and correctness. + +- Each machine maps to a different platform, because each cluster name + describes the combination of hardware **and** software used to run the + experiments. + +.. important:: + In reality, a single value is unlikely to provide enough information to + fully and unambiguously describe a function's behavior, its implementation, + or the state of a machine when its performance was recorded. But we'll come + back to that later. + +""" + +# %% +# After loading our data into a :py:class:`pandas.DataFrame` +# (``df``), we can use the +# :py:func:`p3analysis.data.projection` function to perform this +# projection, renaming the columns as described above. + +# sphinx_gallery_start_ignore +# sphinx_gallery_thumbnail_path = "_static/projection_thumbnail.png" +import matplotlib.pyplot as plt +import pandas as pd + +import p3analysis + +rename_data = { + "size": ["128x128x128", "256x256x256"] * 4, + "implementation": (["Library 1"] * 2 + ["Library 2"] * 2) * 2, + "machine": ["Cluster 1"] * 4 + ["Cluster 2"] * 4, + "fom": [0.5, 2.0, 0.7, 2.1, 0.25, 1.0, 0.125, 0.5], +} + +df = pd.DataFrame(rename_data) +# sphinx_gallery_end_ignore + +proj = p3analysis.data.projection( + df, + problem=["size"], + application=["implementation"], + platform=["machine"], +) +print(proj) + +# %% +# Following projection, our performance data is now ready to be passed to +# functions in the :py:mod:`p3analysis.metrics` module. + +# %% +# Using Projection to Combine Columns +# ----------------------------------- +# +# As we alluded to earlier, it's unlikely that a single column of the raw data +# fully captures the definition of a "problem", "application" or "platform". +# +# Let's make our raw data slightly more complicated, by introducing the notion +# that the function of interest is available in both single precision (FP32) +# and double precision (FP64). +# +# .. list-table:: +# :widths: 20 20 20 20 20 +# :header-rows: 1 +# +# * - size +# - precision +# - implementation +# - machine +# - fom +# +# * - 128x128x128 +# - FP32 +# - Library 1 +# - Cluster 1 +# - 0.5 +# +# * - 256x256x256 +# - FP32 +# - Library 1 +# - Cluster 1 +# - 2.0 +# +# * - 128x128x128 +# - FP32 +# - Library 2 +# - Cluster 1 +# - 0.7 +# +# * - 256x256x256 +# - FP32 +# - Library 2 +# - Cluster 1 +# - 2.1 +# +# * - 128x128x128 +# - FP32 +# - Library 1 +# - Cluster 2 +# - 0.25 +# +# * - 256x256x256 +# - FP32 +# - Library 1 +# - Cluster 2 +# - 1.0 +# +# * - 128x128x128 +# - FP32 +# - Library 2 +# - Cluster 2 +# - 0.125 +# +# * - 256x256x256 +# - FP32 +# - Library 2 +# - Cluster 2 +# - 0.5 +# +# * - 128x128x128 +# - FP64 +# - Library 1 +# - Cluster 1 +# - 1.0 +# +# * - 256x256x256 +# - FP64 +# - Library 1 +# - Cluster 1 +# - 4.0 +# +# * - 128x128x128 +# - FP64 +# - Library 2 +# - Cluster 1 +# - 1.4 +# +# * - 256x256x256 +# - FP64 +# - Library 2 +# - Cluster 1 +# - 4.2 +# +# * - 128x128x128 +# - FP64 +# - Library 1 +# - Cluster 2 +# - 0.5 +# +# * - 256x256x256 +# - FP64 +# - Library 1 +# - Cluster 2 +# - 2.0 +# +# * - 128x128x128 +# - FP64 +# - Library 2 +# - Cluster 2 +# - 0.25 +# +# * - 256x256x256 +# - FP64 +# - Library 2 +# - Cluster 2 +# - 1.0 +# +# How does this impact our projection? The implementation and machine columns +# are still enough to describe the application and platform (respectively), +# but what about the problem? The answer is, of course: "It depends". +# +# Luckily, this dataset is simple enough that we can enumerate our options: +# +# 1. Each unique (size, precision) tuple maps to a different problem, +# representing that the problem definition requires the task to be solved to +# a specific precision (and that the precision has a material impact on the +# verification of results). +# +# 2. Each size maps to a different problem as before, representing that the +# problem definition does **not** require the task to be solved to any +# specific precision, and that implementations are free to select whichever +# precision delivers the best performance. +# +# Neither of these options is more correct than the other. Rather, they +# represent different studies. +# +# Both projections can be performed with the :py:func:`p3analysis.data.projection` +# function, by passing different arguments. +# +# For the first projection, we now need to specify the names of two columns +# ("size" and "precision") to define the problem: + +# sphinx_gallery_start_ignore +df["precision"] = "FP32" +combine_data = { + "size": ["128x128x128", "256x256x256"] * 4, + "precision": ["FP64"] * 8, + "implementation": (["Library 1"] * 2 + ["Library 2"] * 2) * 2, + "machine": ["Cluster 1"] * 4 + ["Cluster 2"] * 4, + "fom": [1.0, 4.0, 1.4, 4.2, 0.5, 2.0, 0.25, 1.0], +} +df = pd.concat([df, pd.DataFrame(combine_data)], ignore_index=True) +# sphinx_gallery_end_ignore + +proj1 = p3analysis.data.projection( + df, + problem=["size", "precision"], + application=["implementation"], + platform=["machine"], +) +print(proj1) + +# %% +# The original "size" and "precision" columns have been removed, and their +# values have been concatenated to form the new "problem" column. +# +# For the second projection, we just need to specify "size", exactly as we did +# before: + +proj2 = p3analysis.data.projection( + df, + problem=["size"], + application=["implementation"], + platform=["machine"], +) +print(proj2) + +# %% +# This time, the original "size" column has been removed, but the "precision" +# column remains. +# +# Clearly, the values provided to the projection function change the structure +# of the resulting :py:class:`pandas.DataFrame`. But why does that matter? +# Well, let's take a look at what happens if we compute the maximum "fom" for +# each (problem, application, platform) tuple in our projected datasets: + +max1 = proj1.groupby(["problem", "application", "platform"])["fom"].max() +print(max1) + +# %% + +max2 = proj2.groupby(["problem", "application", "platform"])["fom"].max() +print(max2) + +# %% +# +# Similar :py:func:`pandas.DataFrame.groupby` calls form the backbone of many +# functions provided by the P3 Analysis Library, since metrics like +# "application efficiency" and "performance portability" ultimately depend on +# an understanding of which variable combinations deliver the best performance. +# +# .. important:: +# The selected projection can have significant impact on the results of +# subsequent analysis, and it is critical to ensure that the projection +# is correct before digging too deep into (or presenting!) any results. +# +# Next Steps +# ---------- +# +# After raw performance data has been projected onto definitions +# of "problem", "application", and "platform", it can be passed to +# any of the P3 Analysis Library functions that expect a +# :py:class:`pandas.DataFrame`. +# +# The examples below show how to use projected data to compute +# and visualize derived metrics like "application efficiency", +# "performance portability", and "code divergence". +# +# .. minigallery:: +# :add-heading: Examples +# +# ../../examples/metrics/application_efficiency.py +# ../../examples/cascade/plot_simple_cascade.py +# ../../examples/navchart/plot_simple_navchart.py diff --git a/_downloads/fb625db3c50d423b1b7881136ffdeec8/examples_jupyter.zip b/_downloads/fb625db3c50d423b1b7881136ffdeec8/examples_jupyter.zip index 9339590..d1c529f 100644 Binary files a/_downloads/fb625db3c50d423b1b7881136ffdeec8/examples_jupyter.zip and b/_downloads/fb625db3c50d423b1b7881136ffdeec8/examples_jupyter.zip differ diff --git a/_images/p3-analysis-flowchart.svg b/_images/p3-analysis-flowchart.svg index bd2c048..85f1c07 100644 --- a/_images/p3-analysis-flowchart.svg +++ b/_images/p3-analysis-flowchart.svg @@ -145,6 +145,13 @@ inkscape:groupmode="layer" id="layer1" transform="translate(-10.173512,-10.630526)"> + +.. thumbnail-parent-div-open .. raw:: html @@ -24,7 +25,7 @@ bandwidth. Implementations are available using multiple programming languages. .. only:: html .. image:: /case-studies/babelstream/images/thumb/sphx_glr_plot_babelstream_cascade_thumb.png - :alt: BabelStream Cascade Plot + :alt: :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_cascade.py` @@ -41,7 +42,7 @@ bandwidth. Implementations are available using multiple programming languages. .. only:: html .. image:: /case-studies/babelstream/images/thumb/sphx_glr_plot_babelstream_navchart_thumb.png - :alt: BabelStream Navigation Chart + :alt: :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_navchart.py` @@ -51,6 +52,8 @@ bandwidth. Implementations are available using multiple programming languages. +.. thumbnail-parent-div-close + .. raw:: html diff --git a/_sources/case-studies/babelstream/plot_babelstream_cascade.rst.txt b/_sources/case-studies/babelstream/plot_babelstream_cascade.rst.txt index 0075cda..b1487ee 100644 --- a/_sources/case-studies/babelstream/plot_babelstream_cascade.rst.txt +++ b/_sources/case-studies/babelstream/plot_babelstream_cascade.rst.txt @@ -10,8 +10,8 @@ .. note:: :class: sphx-glr-download-link-note - Click :ref:`here ` - to download the full example code + :ref:`Go to the end ` + to download the full example code. .. rst-class:: sphx-glr-example-title @@ -36,16 +36,11 @@ readily available in the dataset. Load Data into Pandas --------------------- -.. GENERATED FROM PYTHON SOURCE LINES 22-32 +.. GENERATED FROM PYTHON SOURCE LINES 22-27 -.. code-block:: default +.. code-block:: Python - # import libraries - import pandas as pd - import matplotlib.pyplot as plt - import p3 - # Load performance data from BabelStream results df = pd.read_csv(performance_csv) @@ -57,20 +52,20 @@ Load Data into Pandas -.. GENERATED FROM PYTHON SOURCE LINES 38-42 +.. GENERATED FROM PYTHON SOURCE LINES 39-43 Project Labels into Expected Forms ---------------------------------- -The :py:func:`p3.data.projection` method can be used to project column names +The :py:func:`p3analysis.data.projection` method can be used to project column names from the original data into names required by the P3 Analysis Library. -.. GENERATED FROM PYTHON SOURCE LINES 42-47 +.. GENERATED FROM PYTHON SOURCE LINES 43-48 -.. code-block:: default +.. code-block:: Python - df = p3.data.projection( - df, problem=["name"], platform=["arch"], application=["language"] + df = p3analysis.data.projection( + df, problem=["name"], platform=["arch"], application=["language"], ) @@ -80,7 +75,7 @@ from the original data into names required by the P3 Analysis Library. -.. GENERATED FROM PYTHON SOURCE LINES 48-61 +.. GENERATED FROM PYTHON SOURCE LINES 49-62 Our BabelStream data contains only one problem, and the "name" field is always "BabelStream". Other BabelStream case studies may feature multiple @@ -96,7 +91,7 @@ is written in. .. _here: ../../introduction.html#terminology -.. GENERATED FROM PYTHON SOURCE LINES 63-70 +.. GENERATED FROM PYTHON SOURCE LINES 64-71 Calculate Application Efficiencies ---------------------------------- @@ -106,12 +101,12 @@ at utilizing a given platform. In the below, any row with an 'app eff' of (problem, platform) combination; any row with an `app eff` of 1 represents an application achieving the best-known performance. -.. GENERATED FROM PYTHON SOURCE LINES 70-74 +.. GENERATED FROM PYTHON SOURCE LINES 71-75 -.. code-block:: default +.. code-block:: Python - effs = p3.metrics.application_efficiency(df) + effs = p3analysis.metrics.application_efficiency(df) print(effs) @@ -182,19 +177,18 @@ an application achieving the best-known performance. -.. GENERATED FROM PYTHON SOURCE LINES 75-77 +.. GENERATED FROM PYTHON SOURCE LINES 76-78 Generate a Cascade Plot ----------------------- -.. GENERATED FROM PYTHON SOURCE LINES 77-82 +.. GENERATED FROM PYTHON SOURCE LINES 78-82 -.. code-block:: default +.. code-block:: Python - fig = plt.figure(figsize=(6, 5)) - ax = p3.plot.cascade(effs) - plt.savefig("cascade.png", bbox_inches="tight") + cascade = p3analysis.plot.cascade(effs) + cascade.save("cascade.png") @@ -228,7 +222,7 @@ without a 0 for performance portability in the bar chart. .. rst-class:: sphx-glr-timing - **Total running time of the script:** ( 0 minutes 0.680 seconds) + **Total running time of the script:** (0 minutes 0.419 seconds) .. _sphx_glr_download_case-studies_babelstream_plot_babelstream_cascade.py: @@ -237,14 +231,17 @@ without a 0 for performance portability in the bar chart. .. container:: sphx-glr-footer sphx-glr-footer-example + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: plot_babelstream_cascade.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_babelstream_cascade.py ` - .. container:: sphx-glr-download sphx-glr-download-jupyter + .. container:: sphx-glr-download sphx-glr-download-zip - :download:`Download Jupyter notebook: plot_babelstream_cascade.ipynb ` + :download:`Download zipped: plot_babelstream_cascade.zip ` .. only:: html diff --git a/_sources/case-studies/babelstream/plot_babelstream_navchart.rst.txt b/_sources/case-studies/babelstream/plot_babelstream_navchart.rst.txt index bf1358f..07e6403 100644 --- a/_sources/case-studies/babelstream/plot_babelstream_navchart.rst.txt +++ b/_sources/case-studies/babelstream/plot_babelstream_navchart.rst.txt @@ -10,8 +10,8 @@ .. note:: :class: sphx-glr-download-link-note - Click :ref:`here ` - to download the full example code + :ref:`Go to the end ` + to download the full example code. .. rst-class:: sphx-glr-example-title @@ -32,16 +32,11 @@ The original data is available from https://github.com/UoB-HPC/BabelStream. Load Data into Pandas --------------------- -.. GENERATED FROM PYTHON SOURCE LINES 18-29 +.. GENERATED FROM PYTHON SOURCE LINES 18-24 -.. code-block:: default +.. code-block:: Python - # import libraries - import pandas as pd - import matplotlib.pyplot as plt - import p3 - # Load data from BabelStream results df = pd.read_csv(performance_csv) @@ -54,27 +49,27 @@ Load Data into Pandas -.. GENERATED FROM PYTHON SOURCE LINES 36-40 +.. GENERATED FROM PYTHON SOURCE LINES 37-41 Note that, unlike a `cascade plot`_, plotting a navigation chart requires both performance and coverage data. .. _cascade plot: ../plot_babelstream_cascade -.. GENERATED FROM PYTHON SOURCE LINES 42-46 +.. GENERATED FROM PYTHON SOURCE LINES 43-47 Project Labels into Expected Forms ---------------------------------- -The :py:func:`p3.data.projection` method can be used to project column names +The :py:func:`p3analysis.data.projection` method can be used to project column names from the original data into names required by the P3 Analysis Library. -.. GENERATED FROM PYTHON SOURCE LINES 46-51 +.. GENERATED FROM PYTHON SOURCE LINES 47-52 -.. code-block:: default +.. code-block:: Python - df = p3.data.projection( - df, problem=["name"], platform=["arch"], application=["language"] + df = p3analysis.data.projection( + df, problem=["name"], platform=["arch"], application=["language"], ) @@ -84,7 +79,7 @@ from the original data into names required by the P3 Analysis Library. -.. GENERATED FROM PYTHON SOURCE LINES 52-65 +.. GENERATED FROM PYTHON SOURCE LINES 53-66 Our BabelStream data contains only one problem, and the "name" field is always "BabelStream". Other BabelStream case studies may feature multiple @@ -100,7 +95,7 @@ is written in. .. _here: ../../introduction.html#terminology -.. GENERATED FROM PYTHON SOURCE LINES 67-77 +.. GENERATED FROM PYTHON SOURCE LINES 68-78 Calculate P3 Metrics -------------------- @@ -113,12 +108,12 @@ at utilizing a given platform. In the below, any row with an 'app eff' of (problem, platform) combination; any row with an `app eff` of 1 represents an application achieving the best-known performance. -.. GENERATED FROM PYTHON SOURCE LINES 77-81 +.. GENERATED FROM PYTHON SOURCE LINES 78-82 -.. code-block:: default +.. code-block:: Python - effs = p3.metrics.application_efficiency(df) + effs = p3analysis.metrics.application_efficiency(df) print(effs) @@ -189,7 +184,7 @@ an application achieving the best-known performance. -.. GENERATED FROM PYTHON SOURCE LINES 82-88 +.. GENERATED FROM PYTHON SOURCE LINES 83-89 Code Divergence ^^^^^^^^^^^^^^^ @@ -198,12 +193,12 @@ targeted by a specific application. For BabelStream, only Language 0 has a non-zero divergence, as it is the only implementation containing different code paths for different platforms. -.. GENERATED FROM PYTHON SOURCE LINES 88-92 +.. GENERATED FROM PYTHON SOURCE LINES 89-93 -.. code-block:: default +.. code-block:: Python - div = p3.metrics.divergence(df, df_cov) + div = p3analysis.metrics.divergence(df, df_cov) print(div) @@ -224,7 +219,7 @@ different code paths for different platforms. -.. GENERATED FROM PYTHON SOURCE LINES 93-99 +.. GENERATED FROM PYTHON SOURCE LINES 94-100 Performance Portability ^^^^^^^^^^^^^^^^^^^^^^^ @@ -233,12 +228,12 @@ using all platforms in the set. An application must run across all platforms to achieve a non-zero performance portability score. For BabelStream, Language 0 is the only programming model that runs across all 11 platforms. -.. GENERATED FROM PYTHON SOURCE LINES 99-103 +.. GENERATED FROM PYTHON SOURCE LINES 100-104 -.. code-block:: default +.. code-block:: Python - pp = p3.metrics.pp(effs) + pp = p3analysis.metrics.pp(effs) print(pp) @@ -259,19 +254,18 @@ Language 0 is the only programming model that runs across all 11 platforms. -.. GENERATED FROM PYTHON SOURCE LINES 104-106 +.. GENERATED FROM PYTHON SOURCE LINES 105-107 Generate a Navigation Chart --------------------------- -.. GENERATED FROM PYTHON SOURCE LINES 106-111 +.. GENERATED FROM PYTHON SOURCE LINES 107-111 -.. code-block:: default +.. code-block:: Python - fig = plt.figure(figsize=(5, 5)) - ax = p3.plot.navchart(pp, div) - plt.savefig("navchart.png") + navchart = p3analysis.plot.navchart(pp, div) + navchart.save("navchart.png") @@ -307,7 +301,7 @@ where they do not run. .. rst-class:: sphx-glr-timing - **Total running time of the script:** ( 0 minutes 0.498 seconds) + **Total running time of the script:** (0 minutes 0.813 seconds) .. _sphx_glr_download_case-studies_babelstream_plot_babelstream_navchart.py: @@ -316,14 +310,17 @@ where they do not run. .. container:: sphx-glr-footer sphx-glr-footer-example + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: plot_babelstream_navchart.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_babelstream_navchart.py ` - .. container:: sphx-glr-download sphx-glr-download-jupyter + .. container:: sphx-glr-download sphx-glr-download-zip - :download:`Download Jupyter notebook: plot_babelstream_navchart.ipynb ` + :download:`Download zipped: plot_babelstream_navchart.zip ` .. only:: html diff --git a/_sources/case-studies/babelstream/sg_execution_times.rst.txt b/_sources/case-studies/babelstream/sg_execution_times.rst.txt index a3a9305..cc03593 100644 --- a/_sources/case-studies/babelstream/sg_execution_times.rst.txt +++ b/_sources/case-studies/babelstream/sg_execution_times.rst.txt @@ -3,12 +3,38 @@ .. _sphx_glr_case-studies_babelstream_sg_execution_times: + Computation times ================= -**00:01.178** total execution time for **case-studies_babelstream** files: +**00:01.232** total execution time for 2 files **from case-studies/babelstream**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable -+----------------------------------------------------------------------------------------------------------+-----------+--------+ -| :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_cascade.py` (``plot_babelstream_cascade.py``) | 00:00.680 | 0.0 MB | -+----------------------------------------------------------------------------------------------------------+-----------+--------+ -| :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_navchart.py` (``plot_babelstream_navchart.py``) | 00:00.498 | 0.0 MB | -+----------------------------------------------------------------------------------------------------------+-----------+--------+ + * - Example + - Time + - Mem (MB) + * - :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_navchart.py` (``plot_babelstream_navchart.py``) + - 00:00.813 + - 0.0 + * - :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_cascade.py` (``plot_babelstream_cascade.py``) + - 00:00.419 + - 0.0 diff --git a/_sources/case-studies/index.rst.txt b/_sources/case-studies/index.rst.txt index bfe1814..97d0a00 100644 --- a/_sources/case-studies/index.rst.txt +++ b/_sources/case-studies/index.rst.txt @@ -11,6 +11,9 @@ Complete end-to-end examples showcasing library capabilities with real data.
+.. thumbnail-parent-div-open + +.. thumbnail-parent-div-close .. raw:: html @@ -30,6 +33,7 @@ bandwidth. Implementations are available using multiple programming languages.
+.. thumbnail-parent-div-open .. raw:: html @@ -38,7 +42,7 @@ bandwidth. Implementations are available using multiple programming languages. .. only:: html .. image:: /case-studies/babelstream/images/thumb/sphx_glr_plot_babelstream_cascade_thumb.png - :alt: BabelStream Cascade Plot + :alt: :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_cascade.py` @@ -55,7 +59,7 @@ bandwidth. Implementations are available using multiple programming languages. .. only:: html .. image:: /case-studies/babelstream/images/thumb/sphx_glr_plot_babelstream_navchart_thumb.png - :alt: BabelStream Navigation Chart + :alt: :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_navchart.py` @@ -65,6 +69,8 @@ bandwidth. Implementations are available using multiple programming languages.
+.. thumbnail-parent-div-close + .. raw:: html
@@ -74,6 +80,7 @@ bandwidth. Implementations are available using multiple programming languages. :hidden: :includehidden: + /case-studies/babelstream/index.rst diff --git a/_sources/case-studies/sg_execution_times.rst.txt b/_sources/case-studies/sg_execution_times.rst.txt new file mode 100644 index 0000000..7fbabd6 --- /dev/null +++ b/_sources/case-studies/sg_execution_times.rst.txt @@ -0,0 +1,37 @@ + +:orphan: + +.. _sphx_glr_case-studies_sg_execution_times: + + +Computation times +================= +**00:00.000** total execution time for 0 files **from case-studies**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable + + * - Example + - Time + - Mem (MB) + * - N/A + - N/A + - N/A diff --git a/_sources/data.rst.txt b/_sources/data.rst.txt index a5619fb..d4da415 100644 --- a/_sources/data.rst.txt +++ b/_sources/data.rst.txt @@ -19,6 +19,8 @@ expects columns to have specific names. The simplest way to work with the library is to collect and store data in a compatible format (e.g. in a CSV file). +.. _performance_data: + Performance Data ################ @@ -35,7 +37,7 @@ The following fields have special meaning: It is recommended not to store *application*, *problem* and *platform* fields when results are initially collected, and to instead construct them using the -:py:func:`p3.data.projection` function. This makes it simpler to re-use the +:py:func:`p3analysis.data.projection` function. This makes it simpler to re-use the same data across multiple analyses using different meanings of *application*, *problem*, and *platform*. diff --git a/_sources/examples/cascade/index.rst.txt b/_sources/examples/cascade/index.rst.txt index 536a16b..9484291 100644 --- a/_sources/examples/cascade/index.rst.txt +++ b/_sources/examples/cascade/index.rst.txt @@ -14,6 +14,7 @@ application, highlighting differences in platform support.
+.. thumbnail-parent-div-open .. raw:: html @@ -22,7 +23,7 @@ application, highlighting differences in platform support. .. only:: html .. image:: /examples/cascade/images/thumb/sphx_glr_plot_simple_cascade_thumb.png - :alt: Simple Cascade + :alt: :ref:`sphx_glr_examples_cascade_plot_simple_cascade.py` @@ -32,6 +33,25 @@ application, highlighting differences in platform support.
+.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/cascade/images/thumb/sphx_glr_plot_customized_cascade_thumb.png + :alt: + + :ref:`sphx_glr_examples_cascade_plot_customized_cascade.py` + +.. raw:: html + +
Customized Cascade
+
+ + +.. thumbnail-parent-div-close + .. raw:: html @@ -41,4 +61,5 @@ application, highlighting differences in platform support. :hidden: /examples/cascade/plot_simple_cascade + /examples/cascade/plot_customized_cascade diff --git a/_sources/examples/cascade/plot_customized_cascade.rst.txt b/_sources/examples/cascade/plot_customized_cascade.rst.txt new file mode 100644 index 0000000..1d82e4e --- /dev/null +++ b/_sources/examples/cascade/plot_customized_cascade.rst.txt @@ -0,0 +1,116 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "examples/cascade/plot_customized_cascade.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_examples_cascade_plot_customized_cascade.py: + + +Customized Cascade +================== + +A customized cascade plot. + +In this example, we show how to customize a cascade plot by changing the limits +of the y-axis. Although the default limit (of 1) is useful for comparing many +plots side-by-side, in practice it is often useful to be able to zoom-in on +specific regions of data. For example, when dealing with applications that do +not achieve very high levels of architectural efficiency, setting a lower +maximum value for the y-axis can improve readability. + +Instead of trying to expose all possible customization options as arguments to +:py:func:`p3analysis.plot.cascade`, the function returns a +:py:class:`p3analysis.plot.CascadePlot` object that provides direct access to library +internals. When using the :py:mod:`matplotlib` backend it is possible to +access the :py:class:`matplotlib.axes.Axes` that were used and subsequently +call any number of :py:mod:`matplotlib` functions. In our example, we can +use :py:meth:`matplotlib.axes.Axes.set_ylim` to update the y-axis. + +.. NOTE:: + :py:mod:`matplotlib` is currently the only backend supported by the P3 + Analysis Library, but this is subject to change. + +.. TIP:: + If you have any trouble customizing a plot, or the + :py:class:`~p3analysis.plot.backend.CascadePlot` object does not provide access to + the internals you are looking for, then please `open an issue + `_. + +.. GENERATED FROM PYTHON SOURCE LINES 35-55 + + + +.. image-sg:: /examples/cascade/images/sphx_glr_plot_customized_cascade_001.png + :alt: plot customized cascade + :srcset: /examples/cascade/images/sphx_glr_plot_customized_cascade_001.png + :class: sphx-glr-single-img + + + + + +.. code-block:: Python + + + # Initialize synthetic performance efficiency data + # (not shown, but available in script download) + + # Read performance efficiency data into pandas DataFrame + df = pd.DataFrame(data) + + # Generate a cascade plot with custom style options + legend = p3analysis.plot.Legend(loc="center left", bbox_to_anchor=(0.91, 0.225), ncols=2) + pstyle = p3analysis.plot.PlatformStyle(colors="GnBu") + astyle = p3analysis.plot.ApplicationStyle(markers=["x", "s", "p", "h", "H", "v"]) + cascade = p3analysis.plot.cascade(df, size=(6, 5), platform_legend=legend, platform_style=pstyle, application_style=astyle) + + # Further customize the cascade plot using matplotlib + # In this example, we adjust the range of the y-axis to improve readability + # This may be necessary for studies using architectural efficiency + cascade.get_axes("eff").set_ylim([0, 0.12]) + cascade.get_axes("pp").set_ylim([0, 0.12]) + + cascade.save("customized-cascade.png") + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.378 seconds) + + +.. _sphx_glr_download_examples_cascade_plot_customized_cascade.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: plot_customized_cascade.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: plot_customized_cascade.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: plot_customized_cascade.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/examples/cascade/plot_simple_cascade.rst.txt b/_sources/examples/cascade/plot_simple_cascade.rst.txt index 0f8c240..689609a 100644 --- a/_sources/examples/cascade/plot_simple_cascade.rst.txt +++ b/_sources/examples/cascade/plot_simple_cascade.rst.txt @@ -10,8 +10,8 @@ .. note:: :class: sphx-glr-download-link-note - Click :ref:`here ` - to download the full example code + :ref:`Go to the end ` + to download the full example code. .. rst-class:: sphx-glr-example-title @@ -47,7 +47,7 @@ The bar chart shows the performance portability of each application. .. _Interpreting and Visualizing Performance Portability Metrics: https://doi.org/10.1109/P3HPC51967.2020.00007 -.. GENERATED FROM PYTHON SOURCE LINES 34-51 +.. GENERATED FROM PYTHON SOURCE LINES 34-44 @@ -60,15 +60,9 @@ The bar chart shows the performance portability of each application. -.. code-block:: default +.. code-block:: Python - import matplotlib as mpl - import matplotlib.pyplot as plt - import pandas as pd - - import p3 - # Initialize synthetic performance efficiency data # (not shown, but available in script download) @@ -76,14 +70,13 @@ The bar chart shows the performance portability of each application. df = pd.DataFrame(data) # Generate a cascade plot - fig = plt.figure(figsize=(6, 5)) - ax = p3.plot.cascade(df) - plt.savefig("cascade.png", bbox_inches="tight") + cascade = p3analysis.plot.cascade(df, size=(6, 5)) + cascade.save("cascade.png") .. rst-class:: sphx-glr-timing - **Total running time of the script:** ( 0 minutes 0.828 seconds) + **Total running time of the script:** (0 minutes 0.866 seconds) .. _sphx_glr_download_examples_cascade_plot_simple_cascade.py: @@ -92,14 +85,17 @@ The bar chart shows the performance portability of each application. .. container:: sphx-glr-footer sphx-glr-footer-example + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: plot_simple_cascade.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_simple_cascade.py ` - .. container:: sphx-glr-download sphx-glr-download-jupyter + .. container:: sphx-glr-download sphx-glr-download-zip - :download:`Download Jupyter notebook: plot_simple_cascade.ipynb ` + :download:`Download zipped: plot_simple_cascade.zip ` .. only:: html diff --git a/_sources/examples/cascade/sg_execution_times.rst.txt b/_sources/examples/cascade/sg_execution_times.rst.txt index b39fa3b..b82ff7f 100644 --- a/_sources/examples/cascade/sg_execution_times.rst.txt +++ b/_sources/examples/cascade/sg_execution_times.rst.txt @@ -3,10 +3,38 @@ .. _sphx_glr_examples_cascade_sg_execution_times: + Computation times ================= -**00:00.828** total execution time for **examples_cascade** files: +**00:01.244** total execution time for 2 files **from examples/cascade**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable -+--------------------------------------------------------------------------------------+-----------+--------+ -| :ref:`sphx_glr_examples_cascade_plot_simple_cascade.py` (``plot_simple_cascade.py``) | 00:00.828 | 0.0 MB | -+--------------------------------------------------------------------------------------+-----------+--------+ + * - Example + - Time + - Mem (MB) + * - :ref:`sphx_glr_examples_cascade_plot_simple_cascade.py` (``plot_simple_cascade.py``) + - 00:00.866 + - 0.0 + * - :ref:`sphx_glr_examples_cascade_plot_customized_cascade.py` (``plot_customized_cascade.py``) + - 00:00.378 + - 0.0 diff --git a/_sources/examples/index.rst.txt b/_sources/examples/index.rst.txt index 52d1803..b3d21fa 100644 --- a/_sources/examples/index.rst.txt +++ b/_sources/examples/index.rst.txt @@ -14,6 +14,9 @@ Click on a thumbnail to view the code that produced it.
+.. thumbnail-parent-div-open + +.. thumbnail-parent-div-close .. raw:: html @@ -31,6 +34,7 @@ application, highlighting differences in platform support.
+.. thumbnail-parent-div-open .. raw:: html @@ -39,7 +43,7 @@ application, highlighting differences in platform support. .. only:: html .. image:: /examples/cascade/images/thumb/sphx_glr_plot_simple_cascade_thumb.png - :alt: Simple Cascade + :alt: :ref:`sphx_glr_examples_cascade_plot_simple_cascade.py` @@ -49,6 +53,97 @@ application, highlighting differences in platform support.
+.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/cascade/images/thumb/sphx_glr_plot_customized_cascade_thumb.png + :alt: + + :ref:`sphx_glr_examples_cascade_plot_customized_cascade.py` + +.. raw:: html + +
Customized Cascade
+
+ + +.. thumbnail-parent-div-close + +.. raw:: html + +
+ +Metrics +------- + +Generating metrics from raw P3 data is a pre-requisite for generating specific +plots. Understanding how to manipulate and prepare raw P3 data enables the +exploration of more complex "what if" scenarios and more detailed analyses. + + + +.. raw:: html + +
+ +.. thumbnail-parent-div-open + +.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/metrics/images/thumb/sphx_glr_projection_thumb.png + :alt: + + :ref:`sphx_glr_examples_metrics_projection.py` + +.. raw:: html + +
Understanding Data Projection
+
+ + +.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/metrics/images/thumb/sphx_glr_application_efficiency_thumb.png + :alt: + + :ref:`sphx_glr_examples_metrics_application_efficiency.py` + +.. raw:: html + +
Working with Application Efficiency
+
+ + +.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/metrics/images/thumb/sphx_glr_multiple_components_thumb.png + :alt: + + :ref:`sphx_glr_examples_metrics_multiple_components.py` + +.. raw:: html + +
Handling Software with Multiple Components
+
+ + +.. thumbnail-parent-div-close + .. raw:: html
@@ -66,6 +161,7 @@ programmer productivity.
+.. thumbnail-parent-div-open .. raw:: html @@ -74,7 +170,7 @@ programmer productivity. .. only:: html .. image:: /examples/navchart/images/thumb/sphx_glr_plot_simple_navchart_thumb.png - :alt: Simple Navigation Chart + :alt: :ref:`sphx_glr_examples_navchart_plot_simple_navchart.py` @@ -84,6 +180,25 @@ programmer productivity.
+.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/navchart/images/thumb/sphx_glr_plot_customized_navchart_thumb.png + :alt: + + :ref:`sphx_glr_examples_navchart_plot_customized_navchart.py` + +.. raw:: html + +
Customized Navigation Chart
+
+ + +.. thumbnail-parent-div-close + .. raw:: html @@ -93,7 +208,9 @@ programmer productivity. :hidden: :includehidden: + /examples/cascade/index.rst + /examples/metrics/index.rst /examples/navchart/index.rst diff --git a/_sources/examples/metrics/application_efficiency.rst.txt b/_sources/examples/metrics/application_efficiency.rst.txt new file mode 100644 index 0000000..d775c92 --- /dev/null +++ b/_sources/examples/metrics/application_efficiency.rst.txt @@ -0,0 +1,464 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "examples/metrics/application_efficiency.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_examples_metrics_application_efficiency.py: + + +.. _working_with_app_efficiency: + +Working with Application Efficiency +=================================== + +Understanding relative performance. + +One goal of P3 analysis is to understand how well a given application is able +to adapt to and make effective use of the capabilities of different +platforms. Comparisons of *raw* performance (e.g., time to solution) across +platforms can't help us, because raw performance doesn't reflect how fast an +application **should** run. + +To address this, the P3 Analysis Library works with normalized *performance +efficiency* data. With normalized data, an application's performance can be +represented as a number in the range :math:`[0, 1]`, where :math:`1` means +an application is achieving the best possible performance. + +There are multiple ways we can normalize the data to measure relative +efficiency, and for this tutorial we will consider *application efficiency*, +defined below: + +**Application Efficiency** + The performance of an *application*, measured relative to the best known + performance previously demonstrated for solving the same *problem* on the + same *platform*. + +Working with application efficiency is simple because it does not rely on +performance models or theoretical hardware limits. Although it can't tell us +whether an application is performing as well as theoretically possible, it +shows how an application compares to the state-of-the-art, which is often good +enough. + +Calculating Application Efficiency +---------------------------------- + +Let's begin with a very simple example, with a single application, using the +data below: + +.. list-table:: + :widths: 20 20 20 20 20 + :header-rows: 1 + + * - problem + - application + - platform + - fom + - date + + * - Test + - MyApp + - A + - 25.0 + - 2023 + * - Test + - MyApp + - B + - 12.5 + - 2023 + * - Test + - MyApp + - C + - 25.0 + - 2023 + * - Test + - MyApp + - D + - NaN + - 2023 + * - Test + - MyApp + - E + - 5.0 + - 2023 + +.. tip:: + A NaN or 0.0 performance result is interpreted by the P3 Analysis Library + to mean that an application run was in some way invalid. We can use this + to explicitly represent cases where applications did not compile on + specific platforms, did not run to completion, or ran but produced + numerical results that failed some sort of verification. + +.. GENERATED FROM PYTHON SOURCE LINES 90-93 + +After loading this data into a :py:class:`pandas.DataFrame` (`df`), we can +use the :py:func:`p3analysis.metrics.application_efficiency` function to calculate a +table of application efficiencies. + +.. GENERATED FROM PYTHON SOURCE LINES 93-98 + +.. code-block:: Python + + + + effs = p3analysis.metrics.application_efficiency(df) + print(effs) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem platform application fom date app eff + 0 Test A MyApp 25.0 2023 1.0 + 1 Test B MyApp 12.5 2023 1.0 + 2 Test C MyApp 25.0 2023 1.0 + 3 Test D MyApp NaN 2023 0.0 + 4 Test E MyApp 5.0 2023 1.0 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 116-168 + +These initial results may be a little surprising, because they're all +either 1.0 or 0.0. What happened? Since our dataset contains only one result +for MyApp on each platform, each non-zero result is the "best known" result +for that platform! The only exception is Platform D, which is assigned an +efficiency of 0.0 to reflect that it either did not compile, or did not run +successfully. + +.. tip:: + Calculating meaningful application efficiency results requires a minimum + of two results *per platform*. + +Digging Deeper: Adding More Data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Let's see what happens if we add some more data, from a different application +running on the same platforms: + +.. list-table:: + :widths: 20 20 20 20 20 + :header-rows: 1 + + * - problem + - application + - platform + - fom + - date + + * - Test + - YourApp + - A + - 25.0 + - 2023 + * - Test + - YourApp + - B + - 10.0 + - 2023 + * - Test + - YourApp + - C + - 12.5 + - 2023 + * - Test + - YourApp + - D + - 6.0 + - 2023 + * - Test + - YourApp + - E + - 1.0 + - 2023 + +.. GENERATED FROM PYTHON SOURCE LINES 170-172 + +After updating our DataFrame, we can re-run the same function as before to +recompute the application efficiencies. + +.. GENERATED FROM PYTHON SOURCE LINES 172-177 + +.. code-block:: Python + + + + effs = p3analysis.metrics.application_efficiency(df) + print(effs) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem platform application fom date app eff + 0 Test A MyApp 25.0 2023 1.0 + 1 Test B MyApp 12.5 2023 0.8 + 2 Test C MyApp 25.0 2023 0.5 + 3 Test D MyApp NaN 2023 0.0 + 4 Test E MyApp 5.0 2023 0.2 + 5 Test A YourApp 25.0 2023 1.0 + 6 Test B YourApp 10.0 2023 1.0 + 7 Test C YourApp 12.5 2023 1.0 + 8 Test D YourApp 6.0 2023 1.0 + 9 Test E YourApp 1.0 2023 1.0 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 189-212 + +YourApp is now the fastest (best known) application on every platform, +and so it assigned an application efficiency of 1.0 everywhere. The +application efficiency values for MyApp are all between 0.0 and 1.0, +reflecting how close it gets to the state-of-the-art performance on each +platform. + +.. important:: + Adding new data changed the application efficiencies for MyApp *and* + YourApp. Application efficiency values can become stale over time, + and accurate P3 analysis requires us to track "best known" results + carefully. + +Plotting Application Efficiency +------------------------------- + +The P3 Analysis Library does not contain any dedicated functionality for +plotting application efficiency values. However, it is straightforward to use +:py:mod:`matplotlib` and/or the plotting functionality of :py:mod:`pandas` to +produce useful visualizations. + +For example, plotting a bar chart of application efficiences for one +application can help us to summarize that application's performance more +effectively than a table: + +.. GENERATED FROM PYTHON SOURCE LINES 212-217 + +.. code-block:: Python + + + filtered = effs[effs["application"]=="MyApp"] + filtered.plot(kind="bar", x="platform", y="app eff", xlabel="Platform", ylabel="Application Efficiency", legend=False) + plt.savefig("application_efficiency_bars_2023.png") + + + + +.. image-sg:: /examples/metrics/images/sphx_glr_application_efficiency_001.png + :alt: application efficiency + :srcset: /examples/metrics/images/sphx_glr_application_efficiency_001.png + :class: sphx-glr-single-img + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 218-269 + +We can now clearly see that MyApp can adapt to and make very effective use of +Platforms A and B, is within 2x of state-of-the-art performance on Platform +C, but performs poorly on Platforms D and E. The key takeaway from this +analysis is that a developer wishing to improve the "performance portability" +of MyApp should focus on improving support for Platforms D and E. + +Working with Historical Data +---------------------------- + +The performance of an application can change over time, as developers add new +features and optimize for new platforms, or due to changes in the software +stack (e.g., new compiler or driver versions). + +Let's see how this could affect the application efficiency of MyApp, by adding +some new data points collected at a later point in time: + +.. list-table:: + :widths: 20 20 20 20 20 + :header-rows: 1 + + * - problem + - application + - platform + - fom + - date + + * - Test + - MyApp + - A + - 30.0 + - 2024 + * - Test + - MyApp + - B + - 15.0 + - 2024 + * - Test + - MyApp + - C + - 25.0 + - 2024 + * - Test + - MyApp + - D + - 3.0 + - 2024 + * - Test + - MyApp + - E + - 2.5 + - 2024 + +.. GENERATED FROM PYTHON SOURCE LINES 271-272 + +We can compute application efficiency as before: + +.. GENERATED FROM PYTHON SOURCE LINES 272-277 + +.. code-block:: Python + + + + effs = p3analysis.metrics.application_efficiency(df) + print(effs) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem platform application fom date app eff + 0 Test A MyApp 25.0 2023 1.000000 + 1 Test B MyApp 12.5 2023 0.800000 + 2 Test C MyApp 25.0 2023 0.500000 + 3 Test D MyApp NaN 2023 0.000000 + 4 Test E MyApp 5.0 2023 0.200000 + 5 Test A YourApp 25.0 2023 1.000000 + 6 Test B YourApp 10.0 2023 1.000000 + 7 Test C YourApp 12.5 2023 1.000000 + 8 Test D YourApp 6.0 2023 0.500000 + 9 Test E YourApp 1.0 2023 1.000000 + 10 Test A MyApp 30.0 2024 0.833333 + 11 Test B MyApp 15.0 2024 0.666667 + 12 Test C MyApp 25.0 2024 0.500000 + 13 Test D MyApp 3.0 2024 1.000000 + 14 Test E MyApp 2.5 2024 0.400000 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 289-310 + +These latest results suggest that the developers of MyApp acted upon +earlier results and improved support for Platforms D and E. But in doing so, +a small performance regression was introduced in Platforms A and B. + +.. note:: + Such trade-offs are very common, especially when developers wish to + maintain a single source code that targets multiple platforms. + Different platforms may respond differently to the same code changes, + owing to architectural differences (e.g., cache size, available + parallelism) or differences in the software stack (e.g., compilers + performing different optimizations). + For some real-life examples, see the papers + `here `__ + and + `here `__. + +Computing the correct application efficiency values for MyApp and YourApp +requires that our dataset contains all of our historical performance results. +Since what we're really interested in understanding is the *latest* +application efficiency, we should take care to filter our data appropriately +before producing any plots. + +.. GENERATED FROM PYTHON SOURCE LINES 310-315 + +.. code-block:: Python + + + filtered = effs[(effs["application"]=="MyApp") & (effs["date"]==2024)] + filtered.plot(kind="bar", x="platform", y="app eff", xlabel="Platform", ylabel="Application Efficiency", legend=False) + plt.savefig("application_efficiency_bars_2024.png") + + + + +.. image-sg:: /examples/metrics/images/sphx_glr_application_efficiency_002.png + :alt: application efficiency + :srcset: /examples/metrics/images/sphx_glr_application_efficiency_002.png + :class: sphx-glr-single-img + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 316-333 + +Further Analysis +---------------- + +Computing application efficiency is often simply the first step of a +more detailed P3 analysis. + +The examples below show how we can use the visualization capabilities +of the P3 Analysis Library to compare the efficiency of different +applications running across the same platform set, or to gain insight +into how an application's efficiency relates to the code it uses on each +platform. + +.. minigallery:: + :add-heading: Examples + + ../../examples/cascade/plot_simple_cascade.py + ../../examples/navchart/plot_simple_navchart.py + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.210 seconds) + + +.. _sphx_glr_download_examples_metrics_application_efficiency.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: application_efficiency.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: application_efficiency.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: application_efficiency.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/examples/metrics/index.rst.txt b/_sources/examples/metrics/index.rst.txt new file mode 100644 index 0000000..a0fad5c --- /dev/null +++ b/_sources/examples/metrics/index.rst.txt @@ -0,0 +1,84 @@ + + +.. _sphx_glr_examples_metrics: + +Metrics +------- + +Generating metrics from raw P3 data is a pre-requisite for generating specific +plots. Understanding how to manipulate and prepare raw P3 data enables the +exploration of more complex "what if" scenarios and more detailed analyses. + + + +.. raw:: html + +
+ +.. thumbnail-parent-div-open + +.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/metrics/images/thumb/sphx_glr_projection_thumb.png + :alt: + + :ref:`sphx_glr_examples_metrics_projection.py` + +.. raw:: html + +
Understanding Data Projection
+
+ + +.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/metrics/images/thumb/sphx_glr_application_efficiency_thumb.png + :alt: + + :ref:`sphx_glr_examples_metrics_application_efficiency.py` + +.. raw:: html + +
Working with Application Efficiency
+
+ + +.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/metrics/images/thumb/sphx_glr_multiple_components_thumb.png + :alt: + + :ref:`sphx_glr_examples_metrics_multiple_components.py` + +.. raw:: html + +
Handling Software with Multiple Components
+
+ + +.. thumbnail-parent-div-close + +.. raw:: html + +
+ + +.. toctree:: + :hidden: + + /examples/metrics/projection + /examples/metrics/application_efficiency + /examples/metrics/multiple_components + diff --git a/_sources/examples/metrics/multiple_components.rst.txt b/_sources/examples/metrics/multiple_components.rst.txt new file mode 100644 index 0000000..c1e66bb --- /dev/null +++ b/_sources/examples/metrics/multiple_components.rst.txt @@ -0,0 +1,496 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "examples/metrics/multiple_components.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_examples_metrics_multiple_components.py: + + +Handling Software with Multiple Components +========================================== + +Viewing applications as composites. + +When working with very large and complex pieces of software, reporting +performance using a single number (e.g., total time-to-solution) obscures +details about the performance of different software components. Using such +totals during P3 analysis therefore prevents us from understanding how +different software components behave on different platforms. + +Identifying which software components have poor P3 characteristics is necessary +to understand what action(s) we can take to improve the P3 characteristics of +a software package as a whole. Although accounting for multiple components can +make data collection and analysis slightly more complicated, the additional +insight it provides is very valuable. + +.. tip:: + This approach can be readily applied to parallel software written to + heterogeneous programming frameworks (e.g., CUDA, OpenCL, SYCL, Kokkos), + where distinct "kernel"s can be identified and profiled easily. For a + real-life example of this approach in practice, see "`A + Performance-Portable SYCL Implementation of CRK-HACC for Exascale + `_. + +Data Preparation +---------------- + +To keep things simple, let's imagine that our software package consists of just +two components, and that each component has two different implementations that +can both be run on two different machines: + + .. list-table:: + :widths: 20 20 20 20 + :header-rows: 1 + + * - component + - implementation + - machine + - fom + + * - Component 1 + - Implementation 1 + - Cluster 1 + - 2.0 + + * - Component 2 + - Implementation 1 + - Cluster 1 + - 5.0 + + * - Component 1 + - Implementation 2 + - Cluster 1 + - 3.0 + + * - Component 2 + - Implementation 2 + - Cluster 1 + - 4.0 + + * - Component 1 + - Implementation 1 + - Cluster 2 + - 1.0 + + * - Component 2 + - Implementation 1 + - Cluster 2 + - 2.5 + + * - Component 1 + - Implementation 2 + - Cluster 2 + - 0.5 + + * - Component 1 + - Implementation 2 + - Cluster 2 + - 3.0 + +Our first step is to project this data onto P3 definitions, +treating the functionality provided by each component as a +separate problem to be solved: + +.. GENERATED FROM PYTHON SOURCE LINES 91-101 + +.. code-block:: Python + + + + proj = p3analysis.data.projection( + df, + problem=["component"], + application=["implementation"], + platform=["machine"], + ) + print(proj) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem application platform fom + 0 Component 1 Implementation 1 Cluster 1 2.0 + 1 Component 2 Implementation 1 Cluster 1 5.0 + 2 Component 1 Implementation 2 Cluster 1 3.0 + 3 Component 2 Implementation 2 Cluster 1 4.0 + 4 Component 1 Implementation 1 Cluster 2 1.0 + 5 Component 2 Implementation 1 Cluster 2 2.5 + 6 Component 1 Implementation 2 Cluster 2 0.5 + 7 Component 2 Implementation 2 Cluster 2 3.0 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 118-127 + +.. note:: + See ":ref:`Understanding Data Projection `" for + more information about projection. + +Application Efficiency per Component +------------------------------------ + +Having projected the performance data onto P3 definitions, we can now compute +the application efficiency for each component: + +.. GENERATED FROM PYTHON SOURCE LINES 127-131 + +.. code-block:: Python + + + effs = p3analysis.metrics.application_efficiency(proj) + print(effs) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem platform application fom app eff + 0 Component 1 Cluster 1 Implementation 1 2.0 1.000000 + 1 Component 2 Cluster 1 Implementation 1 5.0 0.800000 + 2 Component 1 Cluster 1 Implementation 2 3.0 0.666667 + 3 Component 2 Cluster 1 Implementation 2 4.0 1.000000 + 4 Component 1 Cluster 2 Implementation 1 1.0 0.500000 + 5 Component 2 Cluster 2 Implementation 1 2.5 1.000000 + 6 Component 1 Cluster 2 Implementation 2 0.5 1.000000 + 7 Component 2 Cluster 2 Implementation 2 3.0 0.833333 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 132-139 + +.. note:: + See ":ref:`Working with Application Efficiency + `" for more information about application + efficiency. + +Plotting a graph for each platform separately is a good way to visualize and +compare the application efficiency of each component: + +.. GENERATED FROM PYTHON SOURCE LINES 139-160 + +.. code-block:: Python + + + cluster1 = effs[effs["platform"] == "Cluster 1"] + pivot = cluster1.pivot(index="application", columns=["problem"])["app eff"] + pivot.plot( + kind="bar", + xlabel="Component", + ylabel="Application Efficiency", + title="Cluster 1", + ) + plt.savefig("cluster1_application_efficiency_bars.png") + + cluster2 = effs[effs["platform"] == "Cluster 2"] + pivot = cluster2.pivot(index="application", columns=["problem"])["app eff"] + pivot.plot( + kind="bar", + xlabel="Component", + ylabel="Application Efficiency", + title="Cluster 2", + ) + plt.savefig("cluster2_application_efficiency_bars.png") + + + + +.. rst-class:: sphx-glr-horizontal + + + * + + .. image-sg:: /examples/metrics/images/sphx_glr_multiple_components_001.png + :alt: Cluster 1 + :srcset: /examples/metrics/images/sphx_glr_multiple_components_001.png + :class: sphx-glr-multi-img + + * + + .. image-sg:: /examples/metrics/images/sphx_glr_multiple_components_002.png + :alt: Cluster 2 + :srcset: /examples/metrics/images/sphx_glr_multiple_components_002.png + :class: sphx-glr-multi-img + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 161-174 + +On Cluster 1, Implementation 1 delivers the best performance for Component 1, +but Implementation 2 delivers the best performance for Component 2. On +Cluster 2, that trend is reversed. Clearly, there is no single implementation +that delivers the best performance everywhere. + +Overall Application Efficiency +------------------------------ + +Computing the application efficiency of the software package as a whole +requires a few more steps. + +First, we need to compute the total time taken by each application on each +platform: + +.. GENERATED FROM PYTHON SOURCE LINES 174-179 + +.. code-block:: Python + + + package = proj.groupby(["platform", "application"], as_index=False)["fom"].sum() + package["problem"] = "Package" + print(package) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + platform application fom problem + 0 Cluster 1 Implementation 1 7.0 Package + 1 Cluster 1 Implementation 2 7.0 Package + 2 Cluster 2 Implementation 1 3.5 Package + 3 Cluster 2 Implementation 2 3.5 Package + + + + +.. GENERATED FROM PYTHON SOURCE LINES 180-181 + +Then, we can use this data to compute application efficiency, as below: + +.. GENERATED FROM PYTHON SOURCE LINES 181-185 + +.. code-block:: Python + + + effs = p3analysis.metrics.application_efficiency(package) + print(effs) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem platform application fom app eff + 0 Package Cluster 1 Implementation 1 7.0 1.0 + 1 Package Cluster 1 Implementation 2 7.0 1.0 + 2 Package Cluster 2 Implementation 1 3.5 1.0 + 3 Package Cluster 2 Implementation 2 3.5 1.0 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 186-208 + +These latest results suggest that both Implementation 1 and Implementation 2 +are both achieving the best-known performance when running the package as a +whole. This isn't *strictly* incorrect, since the values of their combined +figure-of-merit *are* the same, but we know from our earlier per-component +analysis that it could be possible to achieve better performance results. + +Specifically, our per-component analysis shows us that an application that +could pick and choose the best implementation of different components for +different platforms would achieve better overall performance. + +.. important:: + Combining component implementations in this way is purely hypothetical, + and there may be very good reasons (e.g., incompatible data structures) + that an application is unable to use certain combinations. Although + removing such invalid combinations would result in a tighter upper + bound, it is much simpler to leave them in place. Including all + combinations may even identify potential opportunities to combine + approaches that initially appeared incompatible (e.g., by writing + routines to convert between data structures). + +We can fold that observation into our P3 analysis by creating an entry in our +dataset that represents the results from a hypothetical application: + +.. GENERATED FROM PYTHON SOURCE LINES 208-215 + +.. code-block:: Python + + + hypothetical_components = proj.groupby(["problem", "platform"], as_index=False)[ + "fom" + ].min() + hypothetical_components["application"] = "Hypothetical" + print(hypothetical_components) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem platform fom application + 0 Component 1 Cluster 1 2.0 Hypothetical + 1 Component 1 Cluster 2 0.5 Hypothetical + 2 Component 2 Cluster 1 4.0 Hypothetical + 3 Component 2 Cluster 2 2.5 Hypothetical + + + + +.. GENERATED FROM PYTHON SOURCE LINES 216-227 + +.. code-block:: Python + + + # Calculate the combined figure of merit for both components + hypothetical_package = hypothetical_components.groupby( + ["platform", "application"], as_index=False, + )["fom"].sum() + hypothetical_package["problem"] = "Package" + + # Append the hypothetical package data to our previous results + package = pd.concat([package, hypothetical_package], ignore_index=True) + print(package) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + platform application fom problem + 0 Cluster 1 Implementation 1 7.0 Package + 1 Cluster 1 Implementation 2 7.0 Package + 2 Cluster 2 Implementation 1 3.5 Package + 3 Cluster 2 Implementation 2 3.5 Package + 4 Cluster 1 Hypothetical 6.0 Package + 5 Cluster 2 Hypothetical 3.0 Package + + + + +.. GENERATED FROM PYTHON SOURCE LINES 228-231 + +As expected, our new hypothetical application achieves better performance +by mixing and matching different implementations. And if we now re-compute +application efficiency with this data included: + +.. GENERATED FROM PYTHON SOURCE LINES 231-235 + +.. code-block:: Python + + + effs = p3analysis.metrics.application_efficiency(package) + print(effs) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem platform application fom app eff + 0 Package Cluster 1 Implementation 1 7.0 0.857143 + 1 Package Cluster 1 Implementation 2 7.0 0.857143 + 2 Package Cluster 2 Implementation 1 3.5 0.857143 + 3 Package Cluster 2 Implementation 2 3.5 0.857143 + 4 Package Cluster 1 Hypothetical 6.0 1.000000 + 5 Package Cluster 2 Hypothetical 3.0 1.000000 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 236-248 + +... we see that the application efficiency of Implementation 1 and +Implementation 2 has been reduced accordingly. Including hypothetical +upper-bounds of performance in our dataset can therefore be a simple and +effective way to improve the accuracy of our P3 analysis, even if a +true theoretical upper-bound (i.e., from a performance model) is unknown. + +.. note:: + The two implementations still have the *same* efficiency, even after + introducing the hypothetical implementation. Per-component analysis is + still required to understand how each component contributes to the + overall efficiency, and to identify which component(s) should be improved + on which platform(s). + +.. GENERATED FROM PYTHON SOURCE LINES 250-267 + +Further Analysis +---------------- + +Computing application efficiency is often simply the first step of a +more detailed P3 analysis. + +The examples below show how we can use the visualization capabilities +of the P3 Analysis Library to compare the efficiency of different +applications running across the same platform set, or to gain insight +into how an application's efficiency relates to the code it uses on each +platform. + +.. minigallery:: + :add-heading: Examples + + ../../examples/cascade/plot_simple_cascade.py + ../../examples/navchart/plot_simple_navchart.py + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.244 seconds) + + +.. _sphx_glr_download_examples_metrics_multiple_components.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: multiple_components.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: multiple_components.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: multiple_components.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/examples/metrics/projection.rst.txt b/_sources/examples/metrics/projection.rst.txt new file mode 100644 index 0000000..7fcba63 --- /dev/null +++ b/_sources/examples/metrics/projection.rst.txt @@ -0,0 +1,571 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "examples/metrics/projection.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_examples_metrics_projection.py: + + +.. _understanding_projection: + +Understanding Data Projection +============================= + +Projecting data onto P3 definitions. + +The P3 Analysis Library expects data to be prepared in a specific +:ref:`format `. This format was inspired by the +:ref:`terminology ` first introduced in "`Implications of a Metric +for Performance Portability`_": + +.. _Implications of a Metric for Performance Portability: + https://doi.org/10.1016/j.future.2017.08.007 + +**Problem** + A task with a pass/fail metric for which quantitative performance may be + measured. Multiplying an :math:`N \times K` matrix by a :math:`K \times M` + matrix to the accuracy guaranteed by IEEE 754 double precision, computing + :math:`\pi` to a certain number of decimal places, and sorting an array of + :math:`N` elements are all examples of problems. + +**Application** + Software capable of solving a *problem* with measurable correctness and + performance. Math libraries, Python scripts, C functions, and entire software + packages are all examples of applications; the Intel |reg| oneAPI Math Kernel + Library is an example of an application for solving linear algebra problems. + +.. |reg| unicode:: U+00AE + :ltrim: + +**Platform** + A collection of software **and** hardware on which an *application* may run a + *problem*. A specific processor coupled with an operating system, compiler, + runtime, drivers, library dependencies, etc is an example of a precise + platform definition. + +These definitions are flexible, allowing the same performance data to be used +for multiple case studies with different interpretations of these terms. + +Rather than store raw performance data in columns corresponding to these +definitions, the P3 Analysis Library provides functionality to *project* raw +performance data onto specific meanings of "problem", "application" and +"platform". + +Using Projection to Rename Columns +---------------------------------- + +The simplest example of projection is a straightforward renaming of columns. + +Let's assume that we've collected some performance data from a few different +implementations of a function, running a number of problem sizes on multiple +machines. + +.. important:: + Although we are looking at "function" performance here, the concepts + generalize to entire software packages. + +Our raw performance data might look like this: + +.. list-table:: + :widths: 20 20 20 20 + :header-rows: 1 + + * - size + - implementation + - machine + - fom + + * - 128x128x128 + - Library 1 + - Cluster 1 + - 0.5 + + * - 256x256x256 + - Library 1 + - Cluster 1 + - 2.0 + + * - 128x128x128 + - Library 2 + - Cluster 1 + - 0.7 + + * - 256x256x256 + - Library 2 + - Cluster 1 + - 2.1 + + * - 128x128x128 + - Library 1 + - Cluster 2 + - 0.25 + + * - 256x256x256 + - Library 1 + - Cluster 2 + - 1.0 + + * - 128x128x128 + - Library 2 + - Cluster 2 + - 0.125 + + * - 256x256x256 + - Library 2 + - Cluster 2 + - 0.5 + +The most obvious projection of this data onto P3 definitions is as follows: + +- Each input size maps to a different problem, because each input + represents a different task to be solved, with its own expected answer to + validate against. + +- Each implementation maps to a different application, because each + library's implementation of the function produces a solution for a given + input with measurable performance and correctness. + +- Each machine maps to a different platform, because each cluster name + describes the combination of hardware **and** software used to run the + experiments. + +.. important:: + In reality, a single value is unlikely to provide enough information to + fully and unambiguously describe a function's behavior, its implementation, + or the state of a machine when its performance was recorded. But we'll come + back to that later. + +.. GENERATED FROM PYTHON SOURCE LINES 137-141 + +After loading our data into a :py:class:`pandas.DataFrame` +(``df``), we can use the +:py:func:`p3analysis.data.projection` function to perform this +projection, renaming the columns as described above. + +.. GENERATED FROM PYTHON SOURCE LINES 141-151 + +.. code-block:: Python + + + + proj = p3analysis.data.projection( + df, + problem=["size"], + application=["implementation"], + platform=["machine"], + ) + print(proj) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem application platform fom + 0 128x128x128 Library 1 Cluster 1 0.500 + 1 256x256x256 Library 1 Cluster 1 2.000 + 2 128x128x128 Library 2 Cluster 1 0.700 + 3 256x256x256 Library 2 Cluster 1 2.100 + 4 128x128x128 Library 1 Cluster 2 0.250 + 5 256x256x256 Library 1 Cluster 2 1.000 + 6 128x128x128 Library 2 Cluster 2 0.125 + 7 256x256x256 Library 2 Cluster 2 0.500 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 168-170 + +Following projection, our performance data is now ready to be passed to +functions in the :py:mod:`p3analysis.metrics` module. + +.. GENERATED FROM PYTHON SOURCE LINES 172-312 + +Using Projection to Combine Columns +----------------------------------- + +As we alluded to earlier, it's unlikely that a single column of the raw data +fully captures the definition of a "problem", "application" or "platform". + +Let's make our raw data slightly more complicated, by introducing the notion +that the function of interest is available in both single precision (FP32) +and double precision (FP64). + +.. list-table:: + :widths: 20 20 20 20 20 + :header-rows: 1 + + * - size + - precision + - implementation + - machine + - fom + + * - 128x128x128 + - FP32 + - Library 1 + - Cluster 1 + - 0.5 + + * - 256x256x256 + - FP32 + - Library 1 + - Cluster 1 + - 2.0 + + * - 128x128x128 + - FP32 + - Library 2 + - Cluster 1 + - 0.7 + + * - 256x256x256 + - FP32 + - Library 2 + - Cluster 1 + - 2.1 + + * - 128x128x128 + - FP32 + - Library 1 + - Cluster 2 + - 0.25 + + * - 256x256x256 + - FP32 + - Library 1 + - Cluster 2 + - 1.0 + + * - 128x128x128 + - FP32 + - Library 2 + - Cluster 2 + - 0.125 + + * - 256x256x256 + - FP32 + - Library 2 + - Cluster 2 + - 0.5 + + * - 128x128x128 + - FP64 + - Library 1 + - Cluster 1 + - 1.0 + + * - 256x256x256 + - FP64 + - Library 1 + - Cluster 1 + - 4.0 + + * - 128x128x128 + - FP64 + - Library 2 + - Cluster 1 + - 1.4 + + * - 256x256x256 + - FP64 + - Library 2 + - Cluster 1 + - 4.2 + + * - 128x128x128 + - FP64 + - Library 1 + - Cluster 2 + - 0.5 + + * - 256x256x256 + - FP64 + - Library 1 + - Cluster 2 + - 2.0 + + * - 128x128x128 + - FP64 + - Library 2 + - Cluster 2 + - 0.25 + + * - 256x256x256 + - FP64 + - Library 2 + - Cluster 2 + - 1.0 + +How does this impact our projection? The implementation and machine columns +are still enough to describe the application and platform (respectively), +but what about the problem? The answer is, of course: "It depends". + +Luckily, this dataset is simple enough that we can enumerate our options: + +1. Each unique (size, precision) tuple maps to a different problem, + representing that the problem definition requires the task to be solved to + a specific precision (and that the precision has a material impact on the + verification of results). + +2. Each size maps to a different problem as before, representing that the + problem definition does **not** require the task to be solved to any + specific precision, and that implementations are free to select whichever + precision delivers the best performance. + +Neither of these options is more correct than the other. Rather, they +represent different studies. + +Both projections can be performed with the :py:func:`p3analysis.data.projection` +function, by passing different arguments. + +For the first projection, we now need to specify the names of two columns +("size" and "precision") to define the problem: + +.. GENERATED FROM PYTHON SOURCE LINES 312-322 + +.. code-block:: Python + + + + proj1 = p3analysis.data.projection( + df, + problem=["size", "precision"], + application=["implementation"], + platform=["machine"], + ) + print(proj1) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + application platform fom problem + 0 Library 1 Cluster 1 0.500 128x128x128-FP32 + 1 Library 1 Cluster 1 2.000 256x256x256-FP32 + 2 Library 2 Cluster 1 0.700 128x128x128-FP32 + 3 Library 2 Cluster 1 2.100 256x256x256-FP32 + 4 Library 1 Cluster 2 0.250 128x128x128-FP32 + 5 Library 1 Cluster 2 1.000 256x256x256-FP32 + 6 Library 2 Cluster 2 0.125 128x128x128-FP32 + 7 Library 2 Cluster 2 0.500 256x256x256-FP32 + 8 Library 1 Cluster 1 1.000 128x128x128-FP64 + 9 Library 1 Cluster 1 4.000 256x256x256-FP64 + 10 Library 2 Cluster 1 1.400 128x128x128-FP64 + 11 Library 2 Cluster 1 4.200 256x256x256-FP64 + 12 Library 1 Cluster 2 0.500 128x128x128-FP64 + 13 Library 1 Cluster 2 2.000 256x256x256-FP64 + 14 Library 2 Cluster 2 0.250 128x128x128-FP64 + 15 Library 2 Cluster 2 1.000 256x256x256-FP64 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 334-339 + +The original "size" and "precision" columns have been removed, and their +values have been concatenated to form the new "problem" column. + +For the second projection, we just need to specify "size", exactly as we did +before: + +.. GENERATED FROM PYTHON SOURCE LINES 339-348 + +.. code-block:: Python + + + proj2 = p3analysis.data.projection( + df, + problem=["size"], + application=["implementation"], + platform=["machine"], + ) + print(proj2) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem application platform fom precision + 0 128x128x128 Library 1 Cluster 1 0.500 FP32 + 1 256x256x256 Library 1 Cluster 1 2.000 FP32 + 2 128x128x128 Library 2 Cluster 1 0.700 FP32 + 3 256x256x256 Library 2 Cluster 1 2.100 FP32 + 4 128x128x128 Library 1 Cluster 2 0.250 FP32 + 5 256x256x256 Library 1 Cluster 2 1.000 FP32 + 6 128x128x128 Library 2 Cluster 2 0.125 FP32 + 7 256x256x256 Library 2 Cluster 2 0.500 FP32 + 8 128x128x128 Library 1 Cluster 1 1.000 FP64 + 9 256x256x256 Library 1 Cluster 1 4.000 FP64 + 10 128x128x128 Library 2 Cluster 1 1.400 FP64 + 11 256x256x256 Library 2 Cluster 1 4.200 FP64 + 12 128x128x128 Library 1 Cluster 2 0.500 FP64 + 13 256x256x256 Library 1 Cluster 2 2.000 FP64 + 14 128x128x128 Library 2 Cluster 2 0.250 FP64 + 15 256x256x256 Library 2 Cluster 2 1.000 FP64 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 349-356 + +This time, the original "size" column has been removed, but the "precision" +column remains. + +Clearly, the values provided to the projection function change the structure +of the resulting :py:class:`pandas.DataFrame`. But why does that matter? +Well, let's take a look at what happens if we compute the maximum "fom" for +each (problem, application, platform) tuple in our projected datasets: + +.. GENERATED FROM PYTHON SOURCE LINES 356-360 + +.. code-block:: Python + + + max1 = proj1.groupby(["problem", "application", "platform"])["fom"].max() + print(max1) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem application platform + 128x128x128-FP32 Library 1 Cluster 1 0.500 + Cluster 2 0.250 + Library 2 Cluster 1 0.700 + Cluster 2 0.125 + 128x128x128-FP64 Library 1 Cluster 1 1.000 + Cluster 2 0.500 + Library 2 Cluster 1 1.400 + Cluster 2 0.250 + 256x256x256-FP32 Library 1 Cluster 1 2.000 + Cluster 2 1.000 + Library 2 Cluster 1 2.100 + Cluster 2 0.500 + 256x256x256-FP64 Library 1 Cluster 1 4.000 + Cluster 2 2.000 + Library 2 Cluster 1 4.200 + Cluster 2 1.000 + Name: fom, dtype: float64 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 361-365 + +.. code-block:: Python + + + max2 = proj2.groupby(["problem", "application", "platform"])["fom"].max() + print(max2) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + problem application platform + 128x128x128 Library 1 Cluster 1 1.00 + Cluster 2 0.50 + Library 2 Cluster 1 1.40 + Cluster 2 0.25 + 256x256x256 Library 1 Cluster 1 4.00 + Cluster 2 2.00 + Library 2 Cluster 1 4.20 + Cluster 2 1.00 + Name: fom, dtype: float64 + + + + +.. GENERATED FROM PYTHON SOURCE LINES 366-394 + +Similar :py:func:`pandas.DataFrame.groupby` calls form the backbone of many +functions provided by the P3 Analysis Library, since metrics like +"application efficiency" and "performance portability" ultimately depend on +an understanding of which variable combinations deliver the best performance. + +.. important:: + The selected projection can have significant impact on the results of + subsequent analysis, and it is critical to ensure that the projection + is correct before digging too deep into (or presenting!) any results. + +Next Steps +---------- + +After raw performance data has been projected onto definitions +of "problem", "application", and "platform", it can be passed to +any of the P3 Analysis Library functions that expect a +:py:class:`pandas.DataFrame`. + +The examples below show how to use projected data to compute +and visualize derived metrics like "application efficiency", +"performance portability", and "code divergence". + +.. minigallery:: + :add-heading: Examples + + ../../examples/metrics/application_efficiency.py + ../../examples/cascade/plot_simple_cascade.py + ../../examples/navchart/plot_simple_navchart.py + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.021 seconds) + + +.. _sphx_glr_download_examples_metrics_projection.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: projection.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: projection.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: projection.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/examples/metrics/sg_execution_times.rst.txt b/_sources/examples/metrics/sg_execution_times.rst.txt new file mode 100644 index 0000000..d14a2bd --- /dev/null +++ b/_sources/examples/metrics/sg_execution_times.rst.txt @@ -0,0 +1,43 @@ + +:orphan: + +.. _sphx_glr_examples_metrics_sg_execution_times: + + +Computation times +================= +**00:00.475** total execution time for 3 files **from examples/metrics**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable + + * - Example + - Time + - Mem (MB) + * - :ref:`sphx_glr_examples_metrics_multiple_components.py` (``multiple_components.py``) + - 00:00.244 + - 0.0 + * - :ref:`sphx_glr_examples_metrics_application_efficiency.py` (``application_efficiency.py``) + - 00:00.210 + - 0.0 + * - :ref:`sphx_glr_examples_metrics_projection.py` (``projection.py``) + - 00:00.021 + - 0.0 diff --git a/_sources/examples/navchart/index.rst.txt b/_sources/examples/navchart/index.rst.txt index e7601ec..cf5afb5 100644 --- a/_sources/examples/navchart/index.rst.txt +++ b/_sources/examples/navchart/index.rst.txt @@ -15,6 +15,7 @@ programmer productivity.
+.. thumbnail-parent-div-open .. raw:: html @@ -23,7 +24,7 @@ programmer productivity. .. only:: html .. image:: /examples/navchart/images/thumb/sphx_glr_plot_simple_navchart_thumb.png - :alt: Simple Navigation Chart + :alt: :ref:`sphx_glr_examples_navchart_plot_simple_navchart.py` @@ -33,6 +34,25 @@ programmer productivity.
+.. raw:: html + +
+ +.. only:: html + + .. image:: /examples/navchart/images/thumb/sphx_glr_plot_customized_navchart_thumb.png + :alt: + + :ref:`sphx_glr_examples_navchart_plot_customized_navchart.py` + +.. raw:: html + +
Customized Navigation Chart
+
+ + +.. thumbnail-parent-div-close + .. raw:: html @@ -42,4 +62,5 @@ programmer productivity. :hidden: /examples/navchart/plot_simple_navchart + /examples/navchart/plot_customized_navchart diff --git a/_sources/examples/navchart/plot_customized_navchart.rst.txt b/_sources/examples/navchart/plot_customized_navchart.rst.txt new file mode 100644 index 0000000..a077deb --- /dev/null +++ b/_sources/examples/navchart/plot_customized_navchart.rst.txt @@ -0,0 +1,123 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "examples/navchart/plot_customized_navchart.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_examples_navchart_plot_customized_navchart.py: + + +Customized Navigation Chart +=========================== + +A customized navigation chart. + +In this example, we show how to customize a navigation chart by increasing the +number of axis ticks and by annotating one of the datapoints. Adjusting the +number of axis ticks can improve a reader's ability to discern between two +similar values, while annotations can be useful to draw attention to certain +points and/or provide some additional context. + +Instead of trying to expose all possible customization options as arguments to +:py:func:`p3analysis.plot.navchart`, the function returns a +:py:class:`p3analysis.plot.NavChart` object that provides direct access to library +internals. When using the :py:mod:`matplotlib` backend it is possible to +access the :py:class:`matplotlib.axes.Axes` that were used and subsequently +call any number of :py:mod:`matplotlib` functions. In our example, we can +use :py:meth:`matplotlib.axes.Axes.set_xticks` and +:py:meth:`matplotlib.axes.Axes.set_yticks` to control the ticks, and can use +:py:meth:`matplotlib.axes.Axes.annotate` for annotations. + +.. NOTE:: + :py:mod:`matplotlib` is currently the only backend supported by the P3 + Analysis Library, but this is subject to change. + +.. TIP:: + If you have any trouble customizing a navigation chart, or the + :py:class:`~p3analysis.plot.backend.NavChart` object does not provide access to the + internals you are looking for, then please `open an issue + `_. + +.. GENERATED FROM PYTHON SOURCE LINES 36-62 + + + +.. image-sg:: /examples/navchart/images/sphx_glr_plot_customized_navchart_001.png + :alt: plot customized navchart + :srcset: /examples/navchart/images/sphx_glr_plot_customized_navchart_001.png + :class: sphx-glr-single-img + + + + + +.. code-block:: Python + + + # Initialize synthetic data + # (not shown, but available in script download) + + # Read performance portability and code divergence data into pandas DataFrame + pp = pd.DataFrame(pp_data) + cd = pd.DataFrame(cd_data) + + # Generate a navigation chart with custom style options + legend = p3analysis.plot.Legend(loc="center left", bbox_to_anchor=(1.0, 0.5)) + astyle = p3analysis.plot.ApplicationStyle(markers=["x", "*", "s", "o", "P"]) + navchart = p3analysis.plot.navchart(pp, cd, size=(5, 5), legend=legend, style=astyle) + + # Further customize the navigation chart using matplotlib + # In this example, we add a label and adjust the ticks + ax = navchart.get_axes() + ax.annotate( + "Balances performance and code re-use.", + xy=(0.7, 0.7), + xytext=(0.2, 0.55), + arrowprops=dict(facecolor='black', shrink=0.05), + ) + ax.set_xticks([x * 0.1 for x in range(0, 11)]) + ax.set_yticks([y * 0.1 for y in range(0, 11)]) + + navchart.save("customized-navchart.png") + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.204 seconds) + + +.. _sphx_glr_download_examples_navchart_plot_customized_navchart.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: plot_customized_navchart.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: plot_customized_navchart.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: plot_customized_navchart.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/examples/navchart/plot_simple_navchart.rst.txt b/_sources/examples/navchart/plot_simple_navchart.rst.txt index f7c29a1..06b2e81 100644 --- a/_sources/examples/navchart/plot_simple_navchart.rst.txt +++ b/_sources/examples/navchart/plot_simple_navchart.rst.txt @@ -10,8 +10,8 @@ .. note:: :class: sphx-glr-download-link-note - Click :ref:`here ` - to download the full example code + :ref:`Go to the end ` + to download the full example code. .. rst-class:: sphx-glr-example-title @@ -40,7 +40,7 @@ A navigation chart is a useful way to visualize the trade-offs between performance (portability) and programmer productivity, assisting in navigation of the P3 space and reasoning about how to reach development goals. -.. GENERATED FROM PYTHON SOURCE LINES 27-44 +.. GENERATED FROM PYTHON SOURCE LINES 27-38 @@ -53,14 +53,9 @@ of the P3 space and reasoning about how to reach development goals. -.. code-block:: default +.. code-block:: Python - import matplotlib.pyplot as plt - import pandas as pd - - import p3 - # Initialize synthetic data # (not shown, but available in script download) @@ -69,14 +64,13 @@ of the P3 space and reasoning about how to reach development goals. cd = pd.DataFrame(cd_data) # Generate a navigation chart - fig = plt.figure(figsize=(5, 5)) - ax = p3.plot.navchart(pp, cd) - plt.savefig("navchart.png") + navchart = p3analysis.plot.navchart(pp, cd, size=(5, 5)) + navchart.save("navchart.png") .. rst-class:: sphx-glr-timing - **Total running time of the script:** ( 0 minutes 0.189 seconds) + **Total running time of the script:** (0 minutes 0.140 seconds) .. _sphx_glr_download_examples_navchart_plot_simple_navchart.py: @@ -85,14 +79,17 @@ of the P3 space and reasoning about how to reach development goals. .. container:: sphx-glr-footer sphx-glr-footer-example + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: plot_simple_navchart.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_simple_navchart.py ` - .. container:: sphx-glr-download sphx-glr-download-jupyter + .. container:: sphx-glr-download sphx-glr-download-zip - :download:`Download Jupyter notebook: plot_simple_navchart.ipynb ` + :download:`Download zipped: plot_simple_navchart.zip ` .. only:: html diff --git a/_sources/examples/navchart/sg_execution_times.rst.txt b/_sources/examples/navchart/sg_execution_times.rst.txt index 29754ab..95db27f 100644 --- a/_sources/examples/navchart/sg_execution_times.rst.txt +++ b/_sources/examples/navchart/sg_execution_times.rst.txt @@ -3,10 +3,38 @@ .. _sphx_glr_examples_navchart_sg_execution_times: + Computation times ================= -**00:00.189** total execution time for **examples_navchart** files: +**00:00.344** total execution time for 2 files **from examples/navchart**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable -+-----------------------------------------------------------------------------------------+-----------+--------+ -| :ref:`sphx_glr_examples_navchart_plot_simple_navchart.py` (``plot_simple_navchart.py``) | 00:00.189 | 0.0 MB | -+-----------------------------------------------------------------------------------------+-----------+--------+ + * - Example + - Time + - Mem (MB) + * - :ref:`sphx_glr_examples_navchart_plot_customized_navchart.py` (``plot_customized_navchart.py``) + - 00:00.204 + - 0.0 + * - :ref:`sphx_glr_examples_navchart_plot_simple_navchart.py` (``plot_simple_navchart.py``) + - 00:00.140 + - 0.0 diff --git a/_sources/examples/sg_execution_times.rst.txt b/_sources/examples/sg_execution_times.rst.txt new file mode 100644 index 0000000..f7a0c03 --- /dev/null +++ b/_sources/examples/sg_execution_times.rst.txt @@ -0,0 +1,37 @@ + +:orphan: + +.. _sphx_glr_examples_sg_execution_times: + + +Computation times +================= +**00:00.000** total execution time for 0 files **from examples**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable + + * - Example + - Time + - Mem (MB) + * - N/A + - N/A + - N/A diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt index ca2edcc..7da42e5 100644 --- a/_sources/index.rst.txt +++ b/_sources/index.rst.txt @@ -4,29 +4,96 @@ Performance, Portability, and Productivity Analysis Library .. toctree:: :maxdepth: 1 :hidden: + :caption: Introduction - introduction + Getting Started + analysis data + +.. toctree:: + :maxdepth: 1 + :hidden: + :caption: Tutorial + examples/index case-studies/index - p3 + +.. toctree:: + :maxdepth: 1 + :hidden: + :caption: Reference + + p3analysis + +.. toctree:: + :maxdepth: 1 + :hidden: + :caption: Contributing + + How to Contribute + GitHub notices-and-disclaimers The Performance, Portability, and Productivity Analysis Library (P3 Analysis Library) enables a simpler workflow for the collection and interpretation of P3 data. +- Compute :doc:`metrics ` like "performance portability" and + "code divergence" to quantify the trade-offs between performance, portability + and productivity made by applications targeting multiple platforms. + +- :doc:`Plot ` cascades and navigation charts to visualize and gain + deeper insight into the "performance portability" and "code divergence" + scores achieved by different applications. + + +Installation +############ + +.. warning:: + As we prepare the P3 Analysis Library for its 1.0 release, we have updated + the online documentation to reflect the latest development version. + This documentation should be considered *unstable*. + +The latest release of the P3 Analysis Library is version 0.1.0-alpha. To +download and install this release, run the following:: + + $ git clone --branch v0.1.0-alpha https://github.com/intel/p3-analysis-library.git + $ cd p3-analysis-library + $ pip install . + +We strongly recommend installing the P3 Analysis Library within a `virtual +environment`_. + +.. _`virtual environment`: https://docs.python.org/3/library/venv.html + + Getting Started ############### -- `Introduction to P3 Analysis`_ -- `Collecting P3 Data`_ +As a library, the P3 Analysis Library does not provide user-facing scripts for +preset analyses. Rather, the library provides routines for manipulating and +visualizing data in support of common P3 analysis tasks. + +Using the P3 Analysis Library effectively requires: + +1. **A basic understanding of P3 analysis and related terminology.** -.. _Introduction to P3 Analysis: introduction.html -.. _Collecting P3 Data: data.html + A brief refresher can be found `here `__, and a high-level + overview can be found in papers like + "`Navigating Performance, Portability and Productivity`_". -Examples -######## + .. _here: analysis.html + .. _Navigating Performance, Portability and Productivity: https://doi.org/10.1109/MCSE.2021.3097276 + +2. **P3 data stored in one of the expected formats.** + + The library does not interact with the filesystem, and expects data to be + prepared and stored in `Pandas`_ DataFrames with specific column names. + + A guide for collecting and structuring P3 data can be found `here `__. + + .. _Pandas: https://pandas.pydata.org/ Simple examples of using the library are available on the `examples`_ page. Complete end-to-end examples, using real data, are available on the `case @@ -34,11 +101,3 @@ studies`_ page. .. _examples: examples/index.html .. _case studies: case-studies/index.html - -Documentation -############# - -- `Package Reference`_ - -.. _Package Reference: p3.html - diff --git a/_sources/modules.rst.txt b/_sources/modules.rst.txt new file mode 100644 index 0000000..ada94ff --- /dev/null +++ b/_sources/modules.rst.txt @@ -0,0 +1,7 @@ +p3analysis +========== + +.. toctree:: + :maxdepth: 4 + + p3analysis diff --git a/_sources/p3.data.rst.txt b/_sources/p3.data.rst.txt deleted file mode 100644 index 11d0ae6..0000000 --- a/_sources/p3.data.rst.txt +++ /dev/null @@ -1,10 +0,0 @@ -p3.data package -=============== - -Module contents ---------------- - -.. automodule:: p3.data - :members: - :undoc-members: - :show-inheritance: diff --git a/_sources/p3.metrics.rst.txt b/_sources/p3.metrics.rst.txt deleted file mode 100644 index 9eb5ba8..0000000 --- a/_sources/p3.metrics.rst.txt +++ /dev/null @@ -1,10 +0,0 @@ -p3.metrics package -================== - -Module contents ---------------- - -.. automodule:: p3.metrics - :members: - :undoc-members: - :show-inheritance: diff --git a/_sources/p3.plot.rst.txt b/_sources/p3.plot.rst.txt deleted file mode 100644 index 3a618fa..0000000 --- a/_sources/p3.plot.rst.txt +++ /dev/null @@ -1,10 +0,0 @@ -p3.plot package -=============== - -Module contents ---------------- - -.. automodule:: p3.plot - :members: - :undoc-members: - :show-inheritance: diff --git a/_sources/p3.report.rst.txt b/_sources/p3.report.rst.txt deleted file mode 100644 index e6c97b9..0000000 --- a/_sources/p3.report.rst.txt +++ /dev/null @@ -1,10 +0,0 @@ -p3.report package -================= - -Module contents ---------------- - -.. automodule:: p3.report - :members: - :undoc-members: - :show-inheritance: diff --git a/_sources/p3.rst.txt b/_sources/p3.rst.txt deleted file mode 100644 index 0a8bdfa..0000000 --- a/_sources/p3.rst.txt +++ /dev/null @@ -1,20 +0,0 @@ -p3 package -========== - -Subpackages ------------ - -.. toctree:: - - p3.data - p3.metrics - p3.plot - p3.report - -Module contents ---------------- - -.. automodule:: p3 - :members: - :undoc-members: - :show-inheritance: diff --git a/_sources/p3analysis.data.rst.txt b/_sources/p3analysis.data.rst.txt new file mode 100644 index 0000000..bd82026 --- /dev/null +++ b/_sources/p3analysis.data.rst.txt @@ -0,0 +1,10 @@ +p3analysis.data package +======================= + +Module contents +--------------- + +.. automodule:: p3analysis.data + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/p3analysis.metrics.rst.txt b/_sources/p3analysis.metrics.rst.txt new file mode 100644 index 0000000..aa429f9 --- /dev/null +++ b/_sources/p3analysis.metrics.rst.txt @@ -0,0 +1,10 @@ +p3analysis.metrics package +========================== + +Module contents +--------------- + +.. automodule:: p3analysis.metrics + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/p3analysis.plot.backend.rst.txt b/_sources/p3analysis.plot.backend.rst.txt new file mode 100644 index 0000000..84a13ff --- /dev/null +++ b/_sources/p3analysis.plot.backend.rst.txt @@ -0,0 +1,29 @@ +p3analysis.plot.backend package +=============================== + +Submodules +---------- + +p3analysis.plot.backend.matplotlib module +----------------------------------------- + +.. automodule:: p3analysis.plot.backend.matplotlib + :members: + :undoc-members: + :show-inheritance: + +p3analysis.plot.backend.pgfplots module +--------------------------------------- + +.. automodule:: p3analysis.plot.backend.pgfplots + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: p3analysis.plot.backend + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/p3analysis.plot.rst.txt b/_sources/p3analysis.plot.rst.txt new file mode 100644 index 0000000..6cd7687 --- /dev/null +++ b/_sources/p3analysis.plot.rst.txt @@ -0,0 +1,18 @@ +p3analysis.plot package +======================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + p3analysis.plot.backend + +Module contents +--------------- + +.. automodule:: p3analysis.plot + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/p3analysis.report.rst.txt b/_sources/p3analysis.report.rst.txt new file mode 100644 index 0000000..343ba4c --- /dev/null +++ b/_sources/p3analysis.report.rst.txt @@ -0,0 +1,10 @@ +p3analysis.report package +========================= + +Module contents +--------------- + +.. automodule:: p3analysis.report + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/p3analysis.rst.txt b/_sources/p3analysis.rst.txt new file mode 100644 index 0000000..ceac7b6 --- /dev/null +++ b/_sources/p3analysis.rst.txt @@ -0,0 +1,21 @@ +p3analysis package +================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + p3analysis.data + p3analysis.metrics + p3analysis.plot + p3analysis.report + +Module contents +--------------- + +.. automodule:: p3analysis + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/sg_execution_times.rst.txt b/_sources/sg_execution_times.rst.txt new file mode 100644 index 0000000..246b818 --- /dev/null +++ b/_sources/sg_execution_times.rst.txt @@ -0,0 +1,61 @@ + +:orphan: + +.. _sphx_glr_sg_execution_times: + + +Computation times +================= +**00:03.295** total execution time for 9 files **from all galleries**: + +.. container:: + + .. raw:: html + + + + + + + + .. list-table:: + :header-rows: 1 + :class: table table-striped sg-datatable + + * - Example + - Time + - Mem (MB) + * - :ref:`sphx_glr_examples_cascade_plot_simple_cascade.py` (``../../examples/cascade/plot_simple_cascade.py``) + - 00:00.866 + - 0.0 + * - :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_navchart.py` (``../../case-studies/babelstream/plot_babelstream_navchart.py``) + - 00:00.813 + - 0.0 + * - :ref:`sphx_glr_case-studies_babelstream_plot_babelstream_cascade.py` (``../../case-studies/babelstream/plot_babelstream_cascade.py``) + - 00:00.419 + - 0.0 + * - :ref:`sphx_glr_examples_cascade_plot_customized_cascade.py` (``../../examples/cascade/plot_customized_cascade.py``) + - 00:00.378 + - 0.0 + * - :ref:`sphx_glr_examples_metrics_multiple_components.py` (``../../examples/metrics/multiple_components.py``) + - 00:00.244 + - 0.0 + * - :ref:`sphx_glr_examples_metrics_application_efficiency.py` (``../../examples/metrics/application_efficiency.py``) + - 00:00.210 + - 0.0 + * - :ref:`sphx_glr_examples_navchart_plot_customized_navchart.py` (``../../examples/navchart/plot_customized_navchart.py``) + - 00:00.204 + - 0.0 + * - :ref:`sphx_glr_examples_navchart_plot_simple_navchart.py` (``../../examples/navchart/plot_simple_navchart.py``) + - 00:00.140 + - 0.0 + * - :ref:`sphx_glr_examples_metrics_projection.py` (``../../examples/metrics/projection.py``) + - 00:00.021 + - 0.0 diff --git a/_static/alabaster.css b/_static/alabaster.css deleted file mode 100644 index b6227ad..0000000 --- a/_static/alabaster.css +++ /dev/null @@ -1,613 +0,0 @@ - - - - - - - - - - - - - - - - - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro', serif; - font-size: 17px; - background-color: white; - color: #000; - margin: 0; - padding: 0; -} - - -div.document { - width: 940px; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 220px; -} - -div.sphinxsidebar { - width: 220px; - font-size: 14px; - line-height: 1.5; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -div.body > .section { - text-align: left; -} - -div.footer { - width: 940px; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -p.caption { - font-family: ; - font-size: inherit; -} - - -div.relations { - display: none; -} - - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0; - margin: -10px 0 0 0px; - text-align: center; -} - -div.sphinxsidebarwrapper h1.logo { - margin-top: -10px; - text-align: center; - margin-bottom: 5px; - text-align: left; -} - -div.sphinxsidebarwrapper h1.logo-name { - margin-top: 0px; -} - -div.sphinxsidebarwrapper p.blurb { - margin-top: 0; - font-style: normal; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: 'Garamond', 'Georgia', serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar ul li.toctree-l1 > a { - font-size: 120%; -} - -div.sphinxsidebar ul li.toctree-l2 > a { - font-size: 110%; -} - -div.sphinxsidebar input { - border: 1px solid #CCC; - font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro', serif; - font-size: 1em; -} - -div.sphinxsidebar hr { - border: none; - height: 1px; - color: #AAA; - background: #AAA; - - text-align: left; - margin-left: 0; - width: 50%; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #DDD; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #EAEAEA; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - margin: 20px 0px; - padding: 10px 30px; - background-color: #FCC; - border: 1px solid #FAA; -} - -div.admonition tt.xref, div.admonition a tt { - border-bottom: 1px solid #fafafa; -} - -dd div.admonition { - margin-left: -60px; - padding-left: 60px; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.seealso { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.topic { - background-color: #eee; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt, code { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -.hll { - background-color: #FFC; - margin: 0 -12px; - padding: 0 12px; - display: block; -} - -img.screenshot { -} - -tt.descname, tt.descclassname, code.descname, code.descclassname { - font-size: 0.95em; -} - -tt.descname, code.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #EEE; - background: #FDFDFD; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.field-list p { - margin-bottom: 0.8em; -} - -table.footnote td.label { - width: .1px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - /* Matches the 30px from the narrow-screen "li > ul" selector below */ - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #EEE; - padding: 7px 30px; - margin: 15px 0px; - line-height: 1.3em; -} - -dl pre, blockquote pre, li pre { - margin-left: 0; - padding-left: 30px; -} - -dl dl pre { - margin-left: -90px; - padding-left: 90px; -} - -tt, code { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, code.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid white; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -/* Don't put an underline on images */ -a.image-reference, a.image-reference:hover { - border-bottom: none; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt, a:hover code { - background: #EEE; -} - - -@media screen and (max-width: 870px) { - - div.sphinxsidebar { - display: none; - } - - div.document { - width: 100%; - - } - - div.documentwrapper { - margin-left: 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - } - - div.bodywrapper { - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - margin-left: 0; - } - - ul { - margin-left: 0; - } - - li > ul { - /* Matches the 30px from the "ul, ol" selector above */ - margin-left: 30px; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .bodywrapper { - margin: 0; - } - - .footer { - width: auto; - } - - .github { - display: none; - } - - - -} - - - -@media screen and (max-width: 875px) { - - body { - margin: 0; - padding: 20px 30px; - } - - div.documentwrapper { - float: none; - background: white; - } - - div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: -20px -30px 20px -30px; - padding: 10px 20px; - background: #333; - color: #FFF; - } - - div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, - div.sphinxsidebar h3 a { - color: white; - } - - div.sphinxsidebar a { - color: #AAA; - } - - div.sphinxsidebar p.logo { - display: none; - } - - div.document { - width: 100%; - margin: 0; - } - - div.footer { - display: none; - } - - div.bodywrapper { - margin: 0; - } - - div.body { - min-height: 0; - padding: 0; - } - - .rtd_doc_footer { - display: none; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .footer { - width: auto; - } - - .github { - display: none; - } -} -@media screen and (min-width: 876px) { - div.sphinxsidebar { - position: fixed; - margin-left: 0; - } -} - - -/* misc. */ - -.revsys-inline { - display: none!important; -} - -/* Make nested-list/multi-paragraph items look better in Releases changelog - * pages. Without this, docutils' magical list fuckery causes inconsistent - * formatting between different release sub-lists. - */ -div#changelog > div.section > ul > li > p:only-child { - margin-bottom: 0; -} - -/* Hide fugly table cell borders in ..bibliography:: directive output */ -table.docutils.citation, table.docutils.citation td, table.docutils.citation th { - border: none; - /* Below needed in some edge cases; if not applied, bottom shadows appear */ - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} \ No newline at end of file diff --git a/_static/basic.css b/_static/basic.css index 7577acb..f316efc 100644 --- a/_static/basic.css +++ b/_static/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -237,6 +237,10 @@ a.headerlink { visibility: hidden; } +a:visited { + color: #551A8B; +} + h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, @@ -670,6 +674,16 @@ dd { margin-left: 30px; } +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; @@ -738,6 +752,14 @@ abbr, acronym { cursor: help; } +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + /* -- code displays --------------------------------------------------------- */ pre { diff --git a/_static/custom.css b/_static/custom.css deleted file mode 100644 index 2a924f1..0000000 --- a/_static/custom.css +++ /dev/null @@ -1 +0,0 @@ -/* This file intentionally left blank. */ diff --git a/_static/debug.css b/_static/debug.css new file mode 100644 index 0000000..74d4aec --- /dev/null +++ b/_static/debug.css @@ -0,0 +1,69 @@ +/* + This CSS file should be overridden by the theme authors. It's + meant for debugging and developing the skeleton that this theme provides. +*/ +body { + font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + background: lavender; +} +.sb-announcement { + background: rgb(131, 131, 131); +} +.sb-announcement__inner { + background: black; + color: white; +} +.sb-header { + background: lightskyblue; +} +.sb-header__inner { + background: royalblue; + color: white; +} +.sb-header-secondary { + background: lightcyan; +} +.sb-header-secondary__inner { + background: cornflowerblue; + color: white; +} +.sb-sidebar-primary { + background: lightgreen; +} +.sb-main { + background: blanchedalmond; +} +.sb-main__inner { + background: antiquewhite; +} +.sb-header-article { + background: lightsteelblue; +} +.sb-article-container { + background: snow; +} +.sb-article-main { + background: white; +} +.sb-footer-article { + background: lightpink; +} +.sb-sidebar-secondary { + background: lightgoldenrodyellow; +} +.sb-footer-content { + background: plum; +} +.sb-footer-content__inner { + background: palevioletred; +} +.sb-footer { + background: pink; +} +.sb-footer__inner { + background: salmon; +} +.sb-article { + background: white; +} diff --git a/_static/doctools.js b/_static/doctools.js index d06a71d..4d67807 100644 --- a/_static/doctools.js +++ b/_static/doctools.js @@ -4,7 +4,7 @@ * * Base JavaScript utilities for all Sphinx HTML documentation. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/_static/documentation_options.js b/_static/documentation_options.js index b57ae3b..7e4c114 100644 --- a/_static/documentation_options.js +++ b/_static/documentation_options.js @@ -1,5 +1,4 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), +const DOCUMENTATION_OPTIONS = { VERSION: '', LANGUAGE: 'en', COLLAPSE_INDEX: false, diff --git a/_static/jupyterlite_badge_logo.svg b/_static/jupyterlite_badge_logo.svg new file mode 100644 index 0000000..5de36d7 --- /dev/null +++ b/_static/jupyterlite_badge_logo.svg @@ -0,0 +1,3 @@ + + +launchlaunchlitelite \ No newline at end of file diff --git a/_static/language_data.js b/_static/language_data.js index 250f566..367b8ed 100644 --- a/_static/language_data.js +++ b/_static/language_data.js @@ -5,7 +5,7 @@ * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -13,7 +13,7 @@ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; -/* Non-minified version is copied as a separate JS file, is available */ +/* Non-minified version is copied as a separate JS file, if available */ /** * Porter Stemmer diff --git a/_static/projection_thumbnail.png b/_static/projection_thumbnail.png new file mode 100644 index 0000000..c436246 Binary files /dev/null and b/_static/projection_thumbnail.png differ diff --git a/_static/pygments.css b/_static/pygments.css index 9abe04b..02b4b12 100644 --- a/_static/pygments.css +++ b/_static/pygments.css @@ -1,49 +1,50 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight pre { line-height: 125%; } +.highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +.highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +.highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight { background: #f8f8f8; } .highlight .c { color: #8f5902; font-style: italic } /* Comment */ .highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ .highlight .g { color: #000000 } /* Generic */ -.highlight .k { color: #004461; font-weight: bold } /* Keyword */ +.highlight .k { color: #204a87; font-weight: bold } /* Keyword */ .highlight .l { color: #000000 } /* Literal */ .highlight .n { color: #000000 } /* Name */ -.highlight .o { color: #582800 } /* Operator */ +.highlight .o { color: #ce5c00; font-weight: bold } /* Operator */ .highlight .x { color: #000000 } /* Other */ .highlight .p { color: #000000; font-weight: bold } /* Punctuation */ .highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ .highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #8f5902 } /* Comment.Preproc */ +.highlight .cp { color: #8f5902; font-style: italic } /* Comment.Preproc */ .highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ .highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ .highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ .highlight .gd { color: #a40000 } /* Generic.Deleted */ .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .ges { color: #000000; font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #ef2929 } /* Generic.Error */ .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #745334 } /* Generic.Prompt */ +.highlight .go { color: #000000; font-style: italic } /* Generic.Output */ +.highlight .gp { color: #8f5902 } /* Generic.Prompt */ .highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ .highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ -.highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */ +.highlight .kc { color: #204a87; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #204a87; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #204a87; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #204a87; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #204a87; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #204a87; font-weight: bold } /* Keyword.Type */ .highlight .ld { color: #000000 } /* Literal.Date */ -.highlight .m { color: #990000 } /* Literal.Number */ +.highlight .m { color: #0000cf; font-weight: bold } /* Literal.Number */ .highlight .s { color: #4e9a06 } /* Literal.String */ .highlight .na { color: #c4a000 } /* Name.Attribute */ -.highlight .nb { color: #004461 } /* Name.Builtin */ +.highlight .nb { color: #204a87 } /* Name.Builtin */ .highlight .nc { color: #000000 } /* Name.Class */ .highlight .no { color: #000000 } /* Name.Constant */ -.highlight .nd { color: #888888 } /* Name.Decorator */ +.highlight .nd { color: #5c35cc; font-weight: bold } /* Name.Decorator */ .highlight .ni { color: #ce5c00 } /* Name.Entity */ .highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #000000 } /* Name.Function */ @@ -51,16 +52,16 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: .highlight .nn { color: #000000 } /* Name.Namespace */ .highlight .nx { color: #000000 } /* Name.Other */ .highlight .py { color: #000000 } /* Name.Property */ -.highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */ +.highlight .nt { color: #204a87; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #000000 } /* Name.Variable */ -.highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */ +.highlight .ow { color: #204a87; font-weight: bold } /* Operator.Word */ .highlight .pm { color: #000000; font-weight: bold } /* Punctuation.Marker */ -.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ -.highlight .mb { color: #990000 } /* Literal.Number.Bin */ -.highlight .mf { color: #990000 } /* Literal.Number.Float */ -.highlight .mh { color: #990000 } /* Literal.Number.Hex */ -.highlight .mi { color: #990000 } /* Literal.Number.Integer */ -.highlight .mo { color: #990000 } /* Literal.Number.Oct */ +.highlight .w { color: #f8f8f8 } /* Text.Whitespace */ +.highlight .mb { color: #0000cf; font-weight: bold } /* Literal.Number.Bin */ +.highlight .mf { color: #0000cf; font-weight: bold } /* Literal.Number.Float */ +.highlight .mh { color: #0000cf; font-weight: bold } /* Literal.Number.Hex */ +.highlight .mi { color: #0000cf; font-weight: bold } /* Literal.Number.Integer */ +.highlight .mo { color: #0000cf; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ .highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ .highlight .sc { color: #4e9a06 } /* Literal.String.Char */ @@ -80,4 +81,178 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: .highlight .vg { color: #000000 } /* Name.Variable.Global */ .highlight .vi { color: #000000 } /* Name.Variable.Instance */ .highlight .vm { color: #000000 } /* Name.Variable.Magic */ -.highlight .il { color: #990000 } /* Literal.Number.Integer.Long */ \ No newline at end of file +.highlight .il { color: #0000cf; font-weight: bold } /* Literal.Number.Integer.Long */ +@media not print { +body[data-theme="dark"] .highlight pre { line-height: 125%; } +body[data-theme="dark"] .highlight td.linenos .normal { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; } +body[data-theme="dark"] .highlight span.linenos { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; } +body[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +body[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +body[data-theme="dark"] .highlight .hll { background-color: #404040 } +body[data-theme="dark"] .highlight { background: #202020; color: #d0d0d0 } +body[data-theme="dark"] .highlight .c { color: #ababab; font-style: italic } /* Comment */ +body[data-theme="dark"] .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +body[data-theme="dark"] .highlight .esc { color: #d0d0d0 } /* Escape */ +body[data-theme="dark"] .highlight .g { color: #d0d0d0 } /* Generic */ +body[data-theme="dark"] .highlight .k { color: #6ebf26; font-weight: bold } /* Keyword */ +body[data-theme="dark"] .highlight .l { color: #d0d0d0 } /* Literal */ +body[data-theme="dark"] .highlight .n { color: #d0d0d0 } /* Name */ +body[data-theme="dark"] .highlight .o { color: #d0d0d0 } /* Operator */ +body[data-theme="dark"] .highlight .x { color: #d0d0d0 } /* Other */ +body[data-theme="dark"] .highlight .p { color: #d0d0d0 } /* Punctuation */ +body[data-theme="dark"] .highlight .ch { color: #ababab; font-style: italic } /* Comment.Hashbang */ +body[data-theme="dark"] .highlight .cm { color: #ababab; font-style: italic } /* Comment.Multiline */ +body[data-theme="dark"] .highlight .cp { color: #ff3a3a; font-weight: bold } /* Comment.Preproc */ +body[data-theme="dark"] .highlight .cpf { color: #ababab; font-style: italic } /* Comment.PreprocFile */ +body[data-theme="dark"] .highlight .c1 { color: #ababab; font-style: italic } /* Comment.Single */ +body[data-theme="dark"] .highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */ +body[data-theme="dark"] .highlight .gd { color: #ff3a3a } /* Generic.Deleted */ +body[data-theme="dark"] .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ +body[data-theme="dark"] .highlight .ges { color: #d0d0d0; font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +body[data-theme="dark"] .highlight .gr { color: #ff3a3a } /* Generic.Error */ +body[data-theme="dark"] .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ +body[data-theme="dark"] .highlight .gi { color: #589819 } /* Generic.Inserted */ +body[data-theme="dark"] .highlight .go { color: #cccccc } /* Generic.Output */ +body[data-theme="dark"] .highlight .gp { color: #aaaaaa } /* Generic.Prompt */ +body[data-theme="dark"] .highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */ +body[data-theme="dark"] .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ +body[data-theme="dark"] .highlight .gt { color: #ff3a3a } /* Generic.Traceback */ +body[data-theme="dark"] .highlight .kc { color: #6ebf26; font-weight: bold } /* Keyword.Constant */ +body[data-theme="dark"] .highlight .kd { color: #6ebf26; font-weight: bold } /* Keyword.Declaration */ +body[data-theme="dark"] .highlight .kn { color: #6ebf26; font-weight: bold } /* Keyword.Namespace */ +body[data-theme="dark"] .highlight .kp { color: #6ebf26 } /* Keyword.Pseudo */ +body[data-theme="dark"] .highlight .kr { color: #6ebf26; font-weight: bold } /* Keyword.Reserved */ +body[data-theme="dark"] .highlight .kt { color: #6ebf26; font-weight: bold } /* Keyword.Type */ +body[data-theme="dark"] .highlight .ld { color: #d0d0d0 } /* Literal.Date */ +body[data-theme="dark"] .highlight .m { color: #51b2fd } /* Literal.Number */ +body[data-theme="dark"] .highlight .s { color: #ed9d13 } /* Literal.String */ +body[data-theme="dark"] .highlight .na { color: #bbbbbb } /* Name.Attribute */ +body[data-theme="dark"] .highlight .nb { color: #2fbccd } /* Name.Builtin */ +body[data-theme="dark"] .highlight .nc { color: #71adff; text-decoration: underline } /* Name.Class */ +body[data-theme="dark"] .highlight .no { color: #40ffff } /* Name.Constant */ +body[data-theme="dark"] .highlight .nd { color: #ffa500 } /* Name.Decorator */ +body[data-theme="dark"] .highlight .ni { color: #d0d0d0 } /* Name.Entity */ +body[data-theme="dark"] .highlight .ne { color: #bbbbbb } /* Name.Exception */ +body[data-theme="dark"] .highlight .nf { color: #71adff } /* Name.Function */ +body[data-theme="dark"] .highlight .nl { color: #d0d0d0 } /* Name.Label */ +body[data-theme="dark"] .highlight .nn { color: #71adff; text-decoration: underline } /* Name.Namespace */ +body[data-theme="dark"] .highlight .nx { color: #d0d0d0 } /* Name.Other */ +body[data-theme="dark"] .highlight .py { color: #d0d0d0 } /* Name.Property */ +body[data-theme="dark"] .highlight .nt { color: #6ebf26; font-weight: bold } /* Name.Tag */ +body[data-theme="dark"] .highlight .nv { color: #40ffff } /* Name.Variable */ +body[data-theme="dark"] .highlight .ow { color: #6ebf26; font-weight: bold } /* Operator.Word */ +body[data-theme="dark"] .highlight .pm { color: #d0d0d0 } /* Punctuation.Marker */ +body[data-theme="dark"] .highlight .w { color: #666666 } /* Text.Whitespace */ +body[data-theme="dark"] .highlight .mb { color: #51b2fd } /* Literal.Number.Bin */ +body[data-theme="dark"] .highlight .mf { color: #51b2fd } /* Literal.Number.Float */ +body[data-theme="dark"] .highlight .mh { color: #51b2fd } /* Literal.Number.Hex */ +body[data-theme="dark"] .highlight .mi { color: #51b2fd } /* Literal.Number.Integer */ +body[data-theme="dark"] .highlight .mo { color: #51b2fd } /* Literal.Number.Oct */ +body[data-theme="dark"] .highlight .sa { color: #ed9d13 } /* Literal.String.Affix */ +body[data-theme="dark"] .highlight .sb { color: #ed9d13 } /* Literal.String.Backtick */ +body[data-theme="dark"] .highlight .sc { color: #ed9d13 } /* Literal.String.Char */ +body[data-theme="dark"] .highlight .dl { color: #ed9d13 } /* Literal.String.Delimiter */ +body[data-theme="dark"] .highlight .sd { color: #ed9d13 } /* Literal.String.Doc */ +body[data-theme="dark"] .highlight .s2 { color: #ed9d13 } /* Literal.String.Double */ +body[data-theme="dark"] .highlight .se { color: #ed9d13 } /* Literal.String.Escape */ +body[data-theme="dark"] .highlight .sh { color: #ed9d13 } /* Literal.String.Heredoc */ +body[data-theme="dark"] .highlight .si { color: #ed9d13 } /* Literal.String.Interpol */ +body[data-theme="dark"] .highlight .sx { color: #ffa500 } /* Literal.String.Other */ +body[data-theme="dark"] .highlight .sr { color: #ed9d13 } /* Literal.String.Regex */ +body[data-theme="dark"] .highlight .s1 { color: #ed9d13 } /* Literal.String.Single */ +body[data-theme="dark"] .highlight .ss { color: #ed9d13 } /* Literal.String.Symbol */ +body[data-theme="dark"] .highlight .bp { color: #2fbccd } /* Name.Builtin.Pseudo */ +body[data-theme="dark"] .highlight .fm { color: #71adff } /* Name.Function.Magic */ +body[data-theme="dark"] .highlight .vc { color: #40ffff } /* Name.Variable.Class */ +body[data-theme="dark"] .highlight .vg { color: #40ffff } /* Name.Variable.Global */ +body[data-theme="dark"] .highlight .vi { color: #40ffff } /* Name.Variable.Instance */ +body[data-theme="dark"] .highlight .vm { color: #40ffff } /* Name.Variable.Magic */ +body[data-theme="dark"] .highlight .il { color: #51b2fd } /* Literal.Number.Integer.Long */ +@media (prefers-color-scheme: dark) { +body:not([data-theme="light"]) .highlight pre { line-height: 125%; } +body:not([data-theme="light"]) .highlight td.linenos .normal { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; } +body:not([data-theme="light"]) .highlight span.linenos { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; } +body:not([data-theme="light"]) .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +body:not([data-theme="light"]) .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +body:not([data-theme="light"]) .highlight .hll { background-color: #404040 } +body:not([data-theme="light"]) .highlight { background: #202020; color: #d0d0d0 } +body:not([data-theme="light"]) .highlight .c { color: #ababab; font-style: italic } /* Comment */ +body:not([data-theme="light"]) .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +body:not([data-theme="light"]) .highlight .esc { color: #d0d0d0 } /* Escape */ +body:not([data-theme="light"]) .highlight .g { color: #d0d0d0 } /* Generic */ +body:not([data-theme="light"]) .highlight .k { color: #6ebf26; font-weight: bold } /* Keyword */ +body:not([data-theme="light"]) .highlight .l { color: #d0d0d0 } /* Literal */ +body:not([data-theme="light"]) .highlight .n { color: #d0d0d0 } /* Name */ +body:not([data-theme="light"]) .highlight .o { color: #d0d0d0 } /* Operator */ +body:not([data-theme="light"]) .highlight .x { color: #d0d0d0 } /* Other */ +body:not([data-theme="light"]) .highlight .p { color: #d0d0d0 } /* Punctuation */ +body:not([data-theme="light"]) .highlight .ch { color: #ababab; font-style: italic } /* Comment.Hashbang */ +body:not([data-theme="light"]) .highlight .cm { color: #ababab; font-style: italic } /* Comment.Multiline */ +body:not([data-theme="light"]) .highlight .cp { color: #ff3a3a; font-weight: bold } /* Comment.Preproc */ +body:not([data-theme="light"]) .highlight .cpf { color: #ababab; font-style: italic } /* Comment.PreprocFile */ +body:not([data-theme="light"]) .highlight .c1 { color: #ababab; font-style: italic } /* Comment.Single */ +body:not([data-theme="light"]) .highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */ +body:not([data-theme="light"]) .highlight .gd { color: #ff3a3a } /* Generic.Deleted */ +body:not([data-theme="light"]) .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ +body:not([data-theme="light"]) .highlight .ges { color: #d0d0d0; font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +body:not([data-theme="light"]) .highlight .gr { color: #ff3a3a } /* Generic.Error */ +body:not([data-theme="light"]) .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ +body:not([data-theme="light"]) .highlight .gi { color: #589819 } /* Generic.Inserted */ +body:not([data-theme="light"]) .highlight .go { color: #cccccc } /* Generic.Output */ +body:not([data-theme="light"]) .highlight .gp { color: #aaaaaa } /* Generic.Prompt */ +body:not([data-theme="light"]) .highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */ +body:not([data-theme="light"]) .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ +body:not([data-theme="light"]) .highlight .gt { color: #ff3a3a } /* Generic.Traceback */ +body:not([data-theme="light"]) .highlight .kc { color: #6ebf26; font-weight: bold } /* Keyword.Constant */ +body:not([data-theme="light"]) .highlight .kd { color: #6ebf26; font-weight: bold } /* Keyword.Declaration */ +body:not([data-theme="light"]) .highlight .kn { color: #6ebf26; font-weight: bold } /* Keyword.Namespace */ +body:not([data-theme="light"]) .highlight .kp { color: #6ebf26 } /* Keyword.Pseudo */ +body:not([data-theme="light"]) .highlight .kr { color: #6ebf26; font-weight: bold } /* Keyword.Reserved */ +body:not([data-theme="light"]) .highlight .kt { color: #6ebf26; font-weight: bold } /* Keyword.Type */ +body:not([data-theme="light"]) .highlight .ld { color: #d0d0d0 } /* Literal.Date */ +body:not([data-theme="light"]) .highlight .m { color: #51b2fd } /* Literal.Number */ +body:not([data-theme="light"]) .highlight .s { color: #ed9d13 } /* Literal.String */ +body:not([data-theme="light"]) .highlight .na { color: #bbbbbb } /* Name.Attribute */ +body:not([data-theme="light"]) .highlight .nb { color: #2fbccd } /* Name.Builtin */ +body:not([data-theme="light"]) .highlight .nc { color: #71adff; text-decoration: underline } /* Name.Class */ +body:not([data-theme="light"]) .highlight .no { color: #40ffff } /* Name.Constant */ +body:not([data-theme="light"]) .highlight .nd { color: #ffa500 } /* Name.Decorator */ +body:not([data-theme="light"]) .highlight .ni { color: #d0d0d0 } /* Name.Entity */ +body:not([data-theme="light"]) .highlight .ne { color: #bbbbbb } /* Name.Exception */ +body:not([data-theme="light"]) .highlight .nf { color: #71adff } /* Name.Function */ +body:not([data-theme="light"]) .highlight .nl { color: #d0d0d0 } /* Name.Label */ +body:not([data-theme="light"]) .highlight .nn { color: #71adff; text-decoration: underline } /* Name.Namespace */ +body:not([data-theme="light"]) .highlight .nx { color: #d0d0d0 } /* Name.Other */ +body:not([data-theme="light"]) .highlight .py { color: #d0d0d0 } /* Name.Property */ +body:not([data-theme="light"]) .highlight .nt { color: #6ebf26; font-weight: bold } /* Name.Tag */ +body:not([data-theme="light"]) .highlight .nv { color: #40ffff } /* Name.Variable */ +body:not([data-theme="light"]) .highlight .ow { color: #6ebf26; font-weight: bold } /* Operator.Word */ +body:not([data-theme="light"]) .highlight .pm { color: #d0d0d0 } /* Punctuation.Marker */ +body:not([data-theme="light"]) .highlight .w { color: #666666 } /* Text.Whitespace */ +body:not([data-theme="light"]) .highlight .mb { color: #51b2fd } /* Literal.Number.Bin */ +body:not([data-theme="light"]) .highlight .mf { color: #51b2fd } /* Literal.Number.Float */ +body:not([data-theme="light"]) .highlight .mh { color: #51b2fd } /* Literal.Number.Hex */ +body:not([data-theme="light"]) .highlight .mi { color: #51b2fd } /* Literal.Number.Integer */ +body:not([data-theme="light"]) .highlight .mo { color: #51b2fd } /* Literal.Number.Oct */ +body:not([data-theme="light"]) .highlight .sa { color: #ed9d13 } /* Literal.String.Affix */ +body:not([data-theme="light"]) .highlight .sb { color: #ed9d13 } /* Literal.String.Backtick */ +body:not([data-theme="light"]) .highlight .sc { color: #ed9d13 } /* Literal.String.Char */ +body:not([data-theme="light"]) .highlight .dl { color: #ed9d13 } /* Literal.String.Delimiter */ +body:not([data-theme="light"]) .highlight .sd { color: #ed9d13 } /* Literal.String.Doc */ +body:not([data-theme="light"]) .highlight .s2 { color: #ed9d13 } /* Literal.String.Double */ +body:not([data-theme="light"]) .highlight .se { color: #ed9d13 } /* Literal.String.Escape */ +body:not([data-theme="light"]) .highlight .sh { color: #ed9d13 } /* Literal.String.Heredoc */ +body:not([data-theme="light"]) .highlight .si { color: #ed9d13 } /* Literal.String.Interpol */ +body:not([data-theme="light"]) .highlight .sx { color: #ffa500 } /* Literal.String.Other */ +body:not([data-theme="light"]) .highlight .sr { color: #ed9d13 } /* Literal.String.Regex */ +body:not([data-theme="light"]) .highlight .s1 { color: #ed9d13 } /* Literal.String.Single */ +body:not([data-theme="light"]) .highlight .ss { color: #ed9d13 } /* Literal.String.Symbol */ +body:not([data-theme="light"]) .highlight .bp { color: #2fbccd } /* Name.Builtin.Pseudo */ +body:not([data-theme="light"]) .highlight .fm { color: #71adff } /* Name.Function.Magic */ +body:not([data-theme="light"]) .highlight .vc { color: #40ffff } /* Name.Variable.Class */ +body:not([data-theme="light"]) .highlight .vg { color: #40ffff } /* Name.Variable.Global */ +body:not([data-theme="light"]) .highlight .vi { color: #40ffff } /* Name.Variable.Instance */ +body:not([data-theme="light"]) .highlight .vm { color: #40ffff } /* Name.Variable.Magic */ +body:not([data-theme="light"]) .highlight .il { color: #51b2fd } /* Literal.Number.Integer.Long */ +} +} \ No newline at end of file diff --git a/_static/scripts/furo-extensions.js b/_static/scripts/furo-extensions.js new file mode 100644 index 0000000..e69de29 diff --git a/_static/scripts/furo.js b/_static/scripts/furo.js new file mode 100644 index 0000000..0abb2af --- /dev/null +++ b/_static/scripts/furo.js @@ -0,0 +1,3 @@ +/*! For license information please see furo.js.LICENSE.txt */ +(()=>{var t={856:function(t,e,n){var o,r;r=void 0!==n.g?n.g:"undefined"!=typeof window?window:this,o=function(){return function(t){"use strict";var e={navClass:"active",contentClass:"active",nested:!1,nestedClass:"active",offset:0,reflow:!1,events:!0},n=function(t,e,n){if(n.settings.events){var o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n});e.dispatchEvent(o)}},o=function(t){var e=0;if(t.offsetParent)for(;t;)e+=t.offsetTop,t=t.offsetParent;return e>=0?e:0},r=function(t){t&&t.sort((function(t,e){return o(t.content)=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},l=function(t,e){var n=t[t.length-1];if(function(t,e){return!(!s()||!c(t.content,e,!0))}(n,e))return n;for(var o=t.length-1;o>=0;o--)if(c(t[o].content,e))return t[o]},a=function(t,e){if(e.nested&&t.parentNode){var n=t.parentNode.closest("li");n&&(n.classList.remove(e.nestedClass),a(n,e))}},i=function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.remove(e.navClass),t.content.classList.remove(e.contentClass),a(o,e),n("gumshoeDeactivate",o,{link:t.nav,content:t.content,settings:e}))}},u=function(t,e){if(e.nested){var n=t.parentNode.closest("li");n&&(n.classList.add(e.nestedClass),u(n,e))}};return function(o,c){var s,a,d,f,m,v={setup:function(){s=document.querySelectorAll(o),a=[],Array.prototype.forEach.call(s,(function(t){var e=document.getElementById(decodeURIComponent(t.hash.substr(1)));e&&a.push({nav:t,content:e})})),r(a)},detect:function(){var t=l(a,m);t?d&&t.content===d.content||(i(d,m),function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.add(e.navClass),t.content.classList.add(e.contentClass),u(o,e),n("gumshoeActivate",o,{link:t.nav,content:t.content,settings:e}))}}(t,m),d=t):d&&(i(d,m),d=null)}},h=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame(v.detect)},g=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame((function(){r(a),v.detect()}))};return v.destroy=function(){d&&i(d,m),t.removeEventListener("scroll",h,!1),m.reflow&&t.removeEventListener("resize",g,!1),a=null,s=null,d=null,f=null,m=null},m=function(){var t={};return Array.prototype.forEach.call(arguments,(function(e){for(var n in e){if(!e.hasOwnProperty(n))return;t[n]=e[n]}})),t}(e,c||{}),v.setup(),v.detect(),t.addEventListener("scroll",h,!1),m.reflow&&t.addEventListener("resize",g,!1),v}}(r)}.apply(e,[]),void 0===o||(t.exports=o)}},e={};function n(o){var r=e[o];if(void 0!==r)return r.exports;var c=e[o]={exports:{}};return t[o].call(c.exports,c,c.exports,n),c.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{"use strict";var t=n(856),e=n.n(t),o=null,r=null,c=document.documentElement.scrollTop;const s=64;function l(){const t=localStorage.getItem("theme")||"auto";var e;"light"!==(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"auto"===t?"light":"light"==t?"dark":"auto":"auto"===t?"dark":"dark"==t?"light":"auto")&&"dark"!==e&&"auto"!==e&&(console.error(`Got invalid theme mode: ${e}. Resetting to auto.`),e="auto"),document.body.dataset.theme=e,localStorage.setItem("theme",e),console.log(`Changed to ${e} mode.`)}function a(){!function(){const t=document.getElementsByClassName("theme-toggle");Array.from(t).forEach((t=>{t.addEventListener("click",l)}))}(),function(){let t=0,e=!1;window.addEventListener("scroll",(function(n){t=window.scrollY,e||(window.requestAnimationFrame((function(){var n;(function(t){const e=Math.floor(r.getBoundingClientRect().top);console.log(`headerTop: ${e}`),0==e&&t!=e?r.classList.add("scrolled"):r.classList.remove("scrolled")})(n=t),function(t){tc&&document.documentElement.classList.remove("show-back-to-top"),c=t}(n),function(t){null!==o&&(0==t?o.scrollTo(0,0):Math.ceil(t)>=Math.floor(document.documentElement.scrollHeight-window.innerHeight)?o.scrollTo(0,o.scrollHeight):document.querySelector(".scroll-current"))}(n),e=!1})),e=!0)})),window.scroll()}(),null!==o&&new(e())(".toc-tree a",{reflow:!0,recursive:!0,navClass:"scroll-current",offset:()=>{let t=parseFloat(getComputedStyle(document.documentElement).fontSize);return r.getBoundingClientRect().height+2.5*t+1}})}document.addEventListener("DOMContentLoaded",(function(){document.body.parentNode.classList.remove("no-js"),r=document.querySelector("header"),o=document.querySelector(".toc-scroll"),a()}))})()})(); +//# sourceMappingURL=furo.js.map \ No newline at end of file diff --git a/_static/scripts/furo.js.LICENSE.txt b/_static/scripts/furo.js.LICENSE.txt new file mode 100644 index 0000000..1632189 --- /dev/null +++ b/_static/scripts/furo.js.LICENSE.txt @@ -0,0 +1,7 @@ +/*! + * gumshoejs v5.1.2 (patched by @pradyunsg) + * A simple, framework-agnostic scrollspy script. + * (c) 2019 Chris Ferdinandi + * MIT License + * http://github.com/cferdinandi/gumshoe + */ diff --git a/_static/scripts/furo.js.map b/_static/scripts/furo.js.map new file mode 100644 index 0000000..80ea12b --- /dev/null +++ b/_static/scripts/furo.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scripts/furo.js","mappings":";iCAAA,MAQWA,SAWS,IAAX,EAAAC,EACH,EAAAA,EACkB,oBAAXC,OACLA,OACAC,KAbO,EAAF,WACP,OAaJ,SAAUD,GACR,aAMA,IAAIE,EAAW,CAEbC,SAAU,SACVC,aAAc,SAGdC,QAAQ,EACRC,YAAa,SAGbC,OAAQ,EACRC,QAAQ,EAGRC,QAAQ,GA6BNC,EAAY,SAAUC,EAAMC,EAAMC,GAEpC,GAAKA,EAAOC,SAASL,OAArB,CAGA,IAAIM,EAAQ,IAAIC,YAAYL,EAAM,CAChCM,SAAS,EACTC,YAAY,EACZL,OAAQA,IAIVD,EAAKO,cAAcJ,EAVgB,CAWrC,EAOIK,EAAe,SAAUR,GAC3B,IAAIS,EAAW,EACf,GAAIT,EAAKU,aACP,KAAOV,GACLS,GAAYT,EAAKW,UACjBX,EAAOA,EAAKU,aAGhB,OAAOD,GAAY,EAAIA,EAAW,CACpC,EAMIG,EAAe,SAAUC,GACvBA,GACFA,EAASC,MAAK,SAAUC,EAAOC,GAG7B,OAFcR,EAAaO,EAAME,SACnBT,EAAaQ,EAAMC,UACF,EACxB,CACT,GAEJ,EAwCIC,EAAW,SAAUlB,EAAME,EAAUiB,GACvC,IAAIC,EAASpB,EAAKqB,wBACd1B,EAnCU,SAAUO,GAExB,MAA+B,mBAApBA,EAASP,OACX2B,WAAWpB,EAASP,UAItB2B,WAAWpB,EAASP,OAC7B,CA2Be4B,CAAUrB,GACvB,OAAIiB,EAEAK,SAASJ,EAAOD,OAAQ,KACvB/B,EAAOqC,aAAeC,SAASC,gBAAgBC,cAG7CJ,SAASJ,EAAOS,IAAK,KAAOlC,CACrC,EAMImC,EAAa,WACf,OACEC,KAAKC,KAAK5C,EAAOqC,YAAcrC,EAAO6C,cAnCjCF,KAAKG,IACVR,SAASS,KAAKC,aACdV,SAASC,gBAAgBS,aACzBV,SAASS,KAAKE,aACdX,SAASC,gBAAgBU,aACzBX,SAASS,KAAKP,aACdF,SAASC,gBAAgBC,aAkC7B,EAmBIU,EAAY,SAAUzB,EAAUX,GAClC,IAAIqC,EAAO1B,EAASA,EAAS2B,OAAS,GACtC,GAbgB,SAAUC,EAAMvC,GAChC,SAAI4B,MAAgBZ,EAASuB,EAAKxB,QAASf,GAAU,GAEvD,CAUMwC,CAAYH,EAAMrC,GAAW,OAAOqC,EACxC,IAAK,IAAII,EAAI9B,EAAS2B,OAAS,EAAGG,GAAK,EAAGA,IACxC,GAAIzB,EAASL,EAAS8B,GAAG1B,QAASf,GAAW,OAAOW,EAAS8B,EAEjE,EAOIC,EAAmB,SAAUC,EAAK3C,GAEpC,GAAKA,EAAST,QAAWoD,EAAIC,WAA7B,CAGA,IAAIC,EAAKF,EAAIC,WAAWE,QAAQ,MAC3BD,IAGLA,EAAGE,UAAUC,OAAOhD,EAASR,aAG7BkD,EAAiBG,EAAI7C,GAV0B,CAWjD,EAOIiD,EAAa,SAAUC,EAAOlD,GAEhC,GAAKkD,EAAL,CAGA,IAAIL,EAAKK,EAAMP,IAAIG,QAAQ,MACtBD,IAGLA,EAAGE,UAAUC,OAAOhD,EAASX,UAC7B6D,EAAMnC,QAAQgC,UAAUC,OAAOhD,EAASV,cAGxCoD,EAAiBG,EAAI7C,GAGrBJ,EAAU,oBAAqBiD,EAAI,CACjCM,KAAMD,EAAMP,IACZ5B,QAASmC,EAAMnC,QACff,SAAUA,IAjBM,CAmBpB,EAOIoD,EAAiB,SAAUT,EAAK3C,GAElC,GAAKA,EAAST,OAAd,CAGA,IAAIsD,EAAKF,EAAIC,WAAWE,QAAQ,MAC3BD,IAGLA,EAAGE,UAAUM,IAAIrD,EAASR,aAG1B4D,EAAeP,EAAI7C,GAVS,CAW9B,EA6LA,OA1JkB,SAAUsD,EAAUC,GAKpC,IACIC,EAAU7C,EAAU8C,EAASC,EAAS1D,EADtC2D,EAAa,CAUjBA,MAAmB,WAEjBH,EAAWhC,SAASoC,iBAAiBN,GAGrC3C,EAAW,GAGXkD,MAAMC,UAAUC,QAAQC,KAAKR,GAAU,SAAUjB,GAE/C,IAAIxB,EAAUS,SAASyC,eACrBC,mBAAmB3B,EAAK4B,KAAKC,OAAO,KAEjCrD,GAGLJ,EAAS0D,KAAK,CACZ1B,IAAKJ,EACLxB,QAASA,GAEb,IAGAL,EAAaC,EACf,EAKAgD,OAAoB,WAElB,IAAIW,EAASlC,EAAUzB,EAAUX,GAG5BsE,EASDb,GAAWa,EAAOvD,UAAY0C,EAAQ1C,UAG1CkC,EAAWQ,EAASzD,GAzFT,SAAUkD,EAAOlD,GAE9B,GAAKkD,EAAL,CAGA,IAAIL,EAAKK,EAAMP,IAAIG,QAAQ,MACtBD,IAGLA,EAAGE,UAAUM,IAAIrD,EAASX,UAC1B6D,EAAMnC,QAAQgC,UAAUM,IAAIrD,EAASV,cAGrC8D,EAAeP,EAAI7C,GAGnBJ,EAAU,kBAAmBiD,EAAI,CAC/BM,KAAMD,EAAMP,IACZ5B,QAASmC,EAAMnC,QACff,SAAUA,IAjBM,CAmBpB,CAqEIuE,CAASD,EAAQtE,GAGjByD,EAAUa,GAfJb,IACFR,EAAWQ,EAASzD,GACpByD,EAAU,KAchB,GAMIe,EAAgB,SAAUvE,GAExByD,GACFxE,EAAOuF,qBAAqBf,GAI9BA,EAAUxE,EAAOwF,sBAAsBf,EAAWgB,OACpD,EAMIC,EAAgB,SAAU3E,GAExByD,GACFxE,EAAOuF,qBAAqBf,GAI9BA,EAAUxE,EAAOwF,uBAAsB,WACrChE,EAAaC,GACbgD,EAAWgB,QACb,GACF,EAkDA,OA7CAhB,EAAWkB,QAAU,WAEfpB,GACFR,EAAWQ,EAASzD,GAItBd,EAAO4F,oBAAoB,SAAUN,GAAe,GAChDxE,EAASN,QACXR,EAAO4F,oBAAoB,SAAUF,GAAe,GAItDjE,EAAW,KACX6C,EAAW,KACXC,EAAU,KACVC,EAAU,KACV1D,EAAW,IACb,EAOEA,EA3XS,WACX,IAAI+E,EAAS,CAAC,EAOd,OANAlB,MAAMC,UAAUC,QAAQC,KAAKgB,WAAW,SAAUC,GAChD,IAAK,IAAIC,KAAOD,EAAK,CACnB,IAAKA,EAAIE,eAAeD,GAAM,OAC9BH,EAAOG,GAAOD,EAAIC,EACpB,CACF,IACOH,CACT,CAkXeK,CAAOhG,EAAUmE,GAAW,CAAC,GAGxCI,EAAW0B,QAGX1B,EAAWgB,SAGXzF,EAAOoG,iBAAiB,SAAUd,GAAe,GAC7CxE,EAASN,QACXR,EAAOoG,iBAAiB,SAAUV,GAAe,GAS9CjB,CACT,CAOF,CArcW4B,CAAQvG,EAChB,UAFM,SAEN,uBCXDwG,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaE,QAGrB,IAAIC,EAASN,EAAyBE,GAAY,CAGjDG,QAAS,CAAC,GAOX,OAHAE,EAAoBL,GAAU1B,KAAK8B,EAAOD,QAASC,EAAQA,EAAOD,QAASJ,GAGpEK,EAAOD,OACf,CCrBAJ,EAAoBO,EAAKF,IACxB,IAAIG,EAASH,GAAUA,EAAOI,WAC7B,IAAOJ,EAAiB,QACxB,IAAM,EAEP,OADAL,EAAoBU,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,CAAM,ECLdR,EAAoBU,EAAI,CAACN,EAASQ,KACjC,IAAI,IAAInB,KAAOmB,EACXZ,EAAoBa,EAAED,EAAYnB,KAASO,EAAoBa,EAAET,EAASX,IAC5EqB,OAAOC,eAAeX,EAASX,EAAK,CAAEuB,YAAY,EAAMC,IAAKL,EAAWnB,IAE1E,ECNDO,EAAoBxG,EAAI,WACvB,GAA0B,iBAAf0H,WAAyB,OAAOA,WAC3C,IACC,OAAOxH,MAAQ,IAAIyH,SAAS,cAAb,EAChB,CAAE,MAAOC,GACR,GAAsB,iBAAX3H,OAAqB,OAAOA,MACxC,CACA,CAPuB,GCAxBuG,EAAoBa,EAAI,CAACrB,EAAK6B,IAAUP,OAAOzC,UAAUqB,eAAenB,KAAKiB,EAAK6B,4CCK9EC,EAAY,KACZC,EAAS,KACTC,EAAgBzF,SAASC,gBAAgByF,UAC7C,MAAMC,EAAmB,GA8EzB,SAASC,IACP,MAAMC,EAAeC,aAAaC,QAAQ,UAAY,OAZxD,IAAkBC,EACH,WADGA,EAaItI,OAAOuI,WAAW,gCAAgCC,QAI/C,SAAjBL,EACO,QACgB,SAAhBA,EACA,OAEA,OAIU,SAAjBA,EACO,OACgB,QAAhBA,EACA,QAEA,SA9BoB,SAATG,GAA4B,SAATA,IACzCG,QAAQC,MAAM,2BAA2BJ,yBACzCA,EAAO,QAGThG,SAASS,KAAK4F,QAAQC,MAAQN,EAC9BF,aAAaS,QAAQ,QAASP,GAC9BG,QAAQK,IAAI,cAAcR,UA0B5B,CAkDA,SAASnC,KART,WAEE,MAAM4C,EAAUzG,SAAS0G,uBAAuB,gBAChDrE,MAAMsE,KAAKF,GAASlE,SAASqE,IAC3BA,EAAI9C,iBAAiB,QAAS8B,EAAe,GAEjD,CAGEiB,GA9CF,WAEE,IAAIC,EAA6B,EAC7BC,GAAU,EAEdrJ,OAAOoG,iBAAiB,UAAU,SAAUuB,GAC1CyB,EAA6BpJ,OAAOsJ,QAE/BD,IACHrJ,OAAOwF,uBAAsB,WAzDnC,IAAuB+D,GAxDvB,SAAgCA,GAC9B,MAAMC,EAAY7G,KAAK8G,MAAM3B,EAAO7F,wBAAwBQ,KAE5DgG,QAAQK,IAAI,cAAcU,KACT,GAAbA,GAAkBD,GAAaC,EACjC1B,EAAOjE,UAAUM,IAAI,YAErB2D,EAAOjE,UAAUC,OAAO,WAE5B,EAgDE4F,CADqBH,EA0DDH,GAvGtB,SAAmCG,GAC7BA,EAAYtB,EACd3F,SAASC,gBAAgBsB,UAAUC,OAAO,oBAEtCyF,EAAYxB,EACdzF,SAASC,gBAAgBsB,UAAUM,IAAI,oBAC9BoF,EAAYxB,GACrBzF,SAASC,gBAAgBsB,UAAUC,OAAO,oBAG9CiE,EAAgBwB,CAClB,CAoCEI,CAA0BJ,GAlC5B,SAA6BA,GACT,OAAd1B,IAKa,GAAb0B,EACF1B,EAAU+B,SAAS,EAAG,GAGtBjH,KAAKC,KAAK2G,IACV5G,KAAK8G,MAAMnH,SAASC,gBAAgBS,aAAehD,OAAOqC,aAE1DwF,EAAU+B,SAAS,EAAG/B,EAAU7E,cAGhBV,SAASuH,cAAc,mBAc3C,CAKEC,CAAoBP,GAwDdF,GAAU,CACZ,IAEAA,GAAU,EAEd,IACArJ,OAAO+J,QACT,CA6BEC,GA1BkB,OAAdnC,GAKJ,IAAI,IAAJ,CAAY,cAAe,CACzBrH,QAAQ,EACRyJ,WAAW,EACX9J,SAAU,iBACVI,OAAQ,KACN,IAAI2J,EAAMhI,WAAWiI,iBAAiB7H,SAASC,iBAAiB6H,UAChE,OAAOtC,EAAO7F,wBAAwBoI,OAAS,IAAMH,EAAM,CAAC,GAiBlE,CAcA5H,SAAS8D,iBAAiB,oBAT1B,WACE9D,SAASS,KAAKW,WAAWG,UAAUC,OAAO,SAE1CgE,EAASxF,SAASuH,cAAc,UAChChC,EAAYvF,SAASuH,cAAc,eAEnC1D,GACF","sources":["webpack:///./src/furo/assets/scripts/gumshoe-patched.js","webpack:///webpack/bootstrap","webpack:///webpack/runtime/compat get default export","webpack:///webpack/runtime/define property getters","webpack:///webpack/runtime/global","webpack:///webpack/runtime/hasOwnProperty shorthand","webpack:///./src/furo/assets/scripts/furo.js"],"sourcesContent":["/*!\n * gumshoejs v5.1.2 (patched by @pradyunsg)\n * A simple, framework-agnostic scrollspy script.\n * (c) 2019 Chris Ferdinandi\n * MIT License\n * http://github.com/cferdinandi/gumshoe\n */\n\n(function (root, factory) {\n if (typeof define === \"function\" && define.amd) {\n define([], function () {\n return factory(root);\n });\n } else if (typeof exports === \"object\") {\n module.exports = factory(root);\n } else {\n root.Gumshoe = factory(root);\n }\n})(\n typeof global !== \"undefined\"\n ? global\n : typeof window !== \"undefined\"\n ? window\n : this,\n function (window) {\n \"use strict\";\n\n //\n // Defaults\n //\n\n var defaults = {\n // Active classes\n navClass: \"active\",\n contentClass: \"active\",\n\n // Nested navigation\n nested: false,\n nestedClass: \"active\",\n\n // Offset & reflow\n offset: 0,\n reflow: false,\n\n // Event support\n events: true,\n };\n\n //\n // Methods\n //\n\n /**\n * Merge two or more objects together.\n * @param {Object} objects The objects to merge together\n * @returns {Object} Merged values of defaults and options\n */\n var extend = function () {\n var merged = {};\n Array.prototype.forEach.call(arguments, function (obj) {\n for (var key in obj) {\n if (!obj.hasOwnProperty(key)) return;\n merged[key] = obj[key];\n }\n });\n return merged;\n };\n\n /**\n * Emit a custom event\n * @param {String} type The event type\n * @param {Node} elem The element to attach the event to\n * @param {Object} detail Any details to pass along with the event\n */\n var emitEvent = function (type, elem, detail) {\n // Make sure events are enabled\n if (!detail.settings.events) return;\n\n // Create a new event\n var event = new CustomEvent(type, {\n bubbles: true,\n cancelable: true,\n detail: detail,\n });\n\n // Dispatch the event\n elem.dispatchEvent(event);\n };\n\n /**\n * Get an element's distance from the top of the Document.\n * @param {Node} elem The element\n * @return {Number} Distance from the top in pixels\n */\n var getOffsetTop = function (elem) {\n var location = 0;\n if (elem.offsetParent) {\n while (elem) {\n location += elem.offsetTop;\n elem = elem.offsetParent;\n }\n }\n return location >= 0 ? location : 0;\n };\n\n /**\n * Sort content from first to last in the DOM\n * @param {Array} contents The content areas\n */\n var sortContents = function (contents) {\n if (contents) {\n contents.sort(function (item1, item2) {\n var offset1 = getOffsetTop(item1.content);\n var offset2 = getOffsetTop(item2.content);\n if (offset1 < offset2) return -1;\n return 1;\n });\n }\n };\n\n /**\n * Get the offset to use for calculating position\n * @param {Object} settings The settings for this instantiation\n * @return {Float} The number of pixels to offset the calculations\n */\n var getOffset = function (settings) {\n // if the offset is a function run it\n if (typeof settings.offset === \"function\") {\n return parseFloat(settings.offset());\n }\n\n // Otherwise, return it as-is\n return parseFloat(settings.offset);\n };\n\n /**\n * Get the document element's height\n * @private\n * @returns {Number}\n */\n var getDocumentHeight = function () {\n return Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight,\n document.body.offsetHeight,\n document.documentElement.offsetHeight,\n document.body.clientHeight,\n document.documentElement.clientHeight,\n );\n };\n\n /**\n * Determine if an element is in view\n * @param {Node} elem The element\n * @param {Object} settings The settings for this instantiation\n * @param {Boolean} bottom If true, check if element is above bottom of viewport instead\n * @return {Boolean} Returns true if element is in the viewport\n */\n var isInView = function (elem, settings, bottom) {\n var bounds = elem.getBoundingClientRect();\n var offset = getOffset(settings);\n if (bottom) {\n return (\n parseInt(bounds.bottom, 10) <\n (window.innerHeight || document.documentElement.clientHeight)\n );\n }\n return parseInt(bounds.top, 10) <= offset;\n };\n\n /**\n * Check if at the bottom of the viewport\n * @return {Boolean} If true, page is at the bottom of the viewport\n */\n var isAtBottom = function () {\n if (\n Math.ceil(window.innerHeight + window.pageYOffset) >=\n getDocumentHeight()\n )\n return true;\n return false;\n };\n\n /**\n * Check if the last item should be used (even if not at the top of the page)\n * @param {Object} item The last item\n * @param {Object} settings The settings for this instantiation\n * @return {Boolean} If true, use the last item\n */\n var useLastItem = function (item, settings) {\n if (isAtBottom() && isInView(item.content, settings, true)) return true;\n return false;\n };\n\n /**\n * Get the active content\n * @param {Array} contents The content areas\n * @param {Object} settings The settings for this instantiation\n * @return {Object} The content area and matching navigation link\n */\n var getActive = function (contents, settings) {\n var last = contents[contents.length - 1];\n if (useLastItem(last, settings)) return last;\n for (var i = contents.length - 1; i >= 0; i--) {\n if (isInView(contents[i].content, settings)) return contents[i];\n }\n };\n\n /**\n * Deactivate parent navs in a nested navigation\n * @param {Node} nav The starting navigation element\n * @param {Object} settings The settings for this instantiation\n */\n var deactivateNested = function (nav, settings) {\n // If nesting isn't activated, bail\n if (!settings.nested || !nav.parentNode) return;\n\n // Get the parent navigation\n var li = nav.parentNode.closest(\"li\");\n if (!li) return;\n\n // Remove the active class\n li.classList.remove(settings.nestedClass);\n\n // Apply recursively to any parent navigation elements\n deactivateNested(li, settings);\n };\n\n /**\n * Deactivate a nav and content area\n * @param {Object} items The nav item and content to deactivate\n * @param {Object} settings The settings for this instantiation\n */\n var deactivate = function (items, settings) {\n // Make sure there are items to deactivate\n if (!items) return;\n\n // Get the parent list item\n var li = items.nav.closest(\"li\");\n if (!li) return;\n\n // Remove the active class from the nav and content\n li.classList.remove(settings.navClass);\n items.content.classList.remove(settings.contentClass);\n\n // Deactivate any parent navs in a nested navigation\n deactivateNested(li, settings);\n\n // Emit a custom event\n emitEvent(\"gumshoeDeactivate\", li, {\n link: items.nav,\n content: items.content,\n settings: settings,\n });\n };\n\n /**\n * Activate parent navs in a nested navigation\n * @param {Node} nav The starting navigation element\n * @param {Object} settings The settings for this instantiation\n */\n var activateNested = function (nav, settings) {\n // If nesting isn't activated, bail\n if (!settings.nested) return;\n\n // Get the parent navigation\n var li = nav.parentNode.closest(\"li\");\n if (!li) return;\n\n // Add the active class\n li.classList.add(settings.nestedClass);\n\n // Apply recursively to any parent navigation elements\n activateNested(li, settings);\n };\n\n /**\n * Activate a nav and content area\n * @param {Object} items The nav item and content to activate\n * @param {Object} settings The settings for this instantiation\n */\n var activate = function (items, settings) {\n // Make sure there are items to activate\n if (!items) return;\n\n // Get the parent list item\n var li = items.nav.closest(\"li\");\n if (!li) return;\n\n // Add the active class to the nav and content\n li.classList.add(settings.navClass);\n items.content.classList.add(settings.contentClass);\n\n // Activate any parent navs in a nested navigation\n activateNested(li, settings);\n\n // Emit a custom event\n emitEvent(\"gumshoeActivate\", li, {\n link: items.nav,\n content: items.content,\n settings: settings,\n });\n };\n\n /**\n * Create the Constructor object\n * @param {String} selector The selector to use for navigation items\n * @param {Object} options User options and settings\n */\n var Constructor = function (selector, options) {\n //\n // Variables\n //\n\n var publicAPIs = {};\n var navItems, contents, current, timeout, settings;\n\n //\n // Methods\n //\n\n /**\n * Set variables from DOM elements\n */\n publicAPIs.setup = function () {\n // Get all nav items\n navItems = document.querySelectorAll(selector);\n\n // Create contents array\n contents = [];\n\n // Loop through each item, get it's matching content, and push to the array\n Array.prototype.forEach.call(navItems, function (item) {\n // Get the content for the nav item\n var content = document.getElementById(\n decodeURIComponent(item.hash.substr(1)),\n );\n if (!content) return;\n\n // Push to the contents array\n contents.push({\n nav: item,\n content: content,\n });\n });\n\n // Sort contents by the order they appear in the DOM\n sortContents(contents);\n };\n\n /**\n * Detect which content is currently active\n */\n publicAPIs.detect = function () {\n // Get the active content\n var active = getActive(contents, settings);\n\n // if there's no active content, deactivate and bail\n if (!active) {\n if (current) {\n deactivate(current, settings);\n current = null;\n }\n return;\n }\n\n // If the active content is the one currently active, do nothing\n if (current && active.content === current.content) return;\n\n // Deactivate the current content and activate the new content\n deactivate(current, settings);\n activate(active, settings);\n\n // Update the currently active content\n current = active;\n };\n\n /**\n * Detect the active content on scroll\n * Debounced for performance\n */\n var scrollHandler = function (event) {\n // If there's a timer, cancel it\n if (timeout) {\n window.cancelAnimationFrame(timeout);\n }\n\n // Setup debounce callback\n timeout = window.requestAnimationFrame(publicAPIs.detect);\n };\n\n /**\n * Update content sorting on resize\n * Debounced for performance\n */\n var resizeHandler = function (event) {\n // If there's a timer, cancel it\n if (timeout) {\n window.cancelAnimationFrame(timeout);\n }\n\n // Setup debounce callback\n timeout = window.requestAnimationFrame(function () {\n sortContents(contents);\n publicAPIs.detect();\n });\n };\n\n /**\n * Destroy the current instantiation\n */\n publicAPIs.destroy = function () {\n // Undo DOM changes\n if (current) {\n deactivate(current, settings);\n }\n\n // Remove event listeners\n window.removeEventListener(\"scroll\", scrollHandler, false);\n if (settings.reflow) {\n window.removeEventListener(\"resize\", resizeHandler, false);\n }\n\n // Reset variables\n contents = null;\n navItems = null;\n current = null;\n timeout = null;\n settings = null;\n };\n\n /**\n * Initialize the current instantiation\n */\n var init = function () {\n // Merge user options into defaults\n settings = extend(defaults, options || {});\n\n // Setup variables based on the current DOM\n publicAPIs.setup();\n\n // Find the currently active content\n publicAPIs.detect();\n\n // Setup event listeners\n window.addEventListener(\"scroll\", scrollHandler, false);\n if (settings.reflow) {\n window.addEventListener(\"resize\", resizeHandler, false);\n }\n };\n\n //\n // Initialize and return the public APIs\n //\n\n init();\n return publicAPIs;\n };\n\n //\n // Return the Constructor\n //\n\n return Constructor;\n },\n);\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","import Gumshoe from \"./gumshoe-patched.js\";\n\n////////////////////////////////////////////////////////////////////////////////\n// Scroll Handling\n////////////////////////////////////////////////////////////////////////////////\nvar tocScroll = null;\nvar header = null;\nvar lastScrollTop = document.documentElement.scrollTop;\nconst GO_TO_TOP_OFFSET = 64;\n\nfunction scrollHandlerForHeader(positionY) {\n const headerTop = Math.floor(header.getBoundingClientRect().top);\n\n console.log(`headerTop: ${headerTop}`);\n if (headerTop == 0 && positionY != headerTop) {\n header.classList.add(\"scrolled\");\n } else {\n header.classList.remove(\"scrolled\");\n }\n}\n\nfunction scrollHandlerForBackToTop(positionY) {\n if (positionY < GO_TO_TOP_OFFSET) {\n document.documentElement.classList.remove(\"show-back-to-top\");\n } else {\n if (positionY < lastScrollTop) {\n document.documentElement.classList.add(\"show-back-to-top\");\n } else if (positionY > lastScrollTop) {\n document.documentElement.classList.remove(\"show-back-to-top\");\n }\n }\n lastScrollTop = positionY;\n}\n\nfunction scrollHandlerForTOC(positionY) {\n if (tocScroll === null) {\n return;\n }\n\n // top of page.\n if (positionY == 0) {\n tocScroll.scrollTo(0, 0);\n } else if (\n // bottom of page.\n Math.ceil(positionY) >=\n Math.floor(document.documentElement.scrollHeight - window.innerHeight)\n ) {\n tocScroll.scrollTo(0, tocScroll.scrollHeight);\n } else {\n // somewhere in the middle.\n const current = document.querySelector(\".scroll-current\");\n if (current == null) {\n return;\n }\n\n // https://github.com/pypa/pip/issues/9159 This breaks scroll behaviours.\n // // scroll the currently \"active\" heading in toc, into view.\n // const rect = current.getBoundingClientRect();\n // if (0 > rect.top) {\n // current.scrollIntoView(true); // the argument is \"alignTop\"\n // } else if (rect.bottom > window.innerHeight) {\n // current.scrollIntoView(false);\n // }\n }\n}\n\nfunction scrollHandler(positionY) {\n scrollHandlerForHeader(positionY);\n scrollHandlerForBackToTop(positionY);\n scrollHandlerForTOC(positionY);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Theme Toggle\n////////////////////////////////////////////////////////////////////////////////\nfunction setTheme(mode) {\n if (mode !== \"light\" && mode !== \"dark\" && mode !== \"auto\") {\n console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`);\n mode = \"auto\";\n }\n\n document.body.dataset.theme = mode;\n localStorage.setItem(\"theme\", mode);\n console.log(`Changed to ${mode} mode.`);\n}\n\nfunction cycleThemeOnce() {\n const currentTheme = localStorage.getItem(\"theme\") || \"auto\";\n const prefersDark = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n\n if (prefersDark) {\n // Auto (dark) -> Light -> Dark\n if (currentTheme === \"auto\") {\n setTheme(\"light\");\n } else if (currentTheme == \"light\") {\n setTheme(\"dark\");\n } else {\n setTheme(\"auto\");\n }\n } else {\n // Auto (light) -> Dark -> Light\n if (currentTheme === \"auto\") {\n setTheme(\"dark\");\n } else if (currentTheme == \"dark\") {\n setTheme(\"light\");\n } else {\n setTheme(\"auto\");\n }\n }\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Setup\n////////////////////////////////////////////////////////////////////////////////\nfunction setupScrollHandler() {\n // Taken from https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event\n let last_known_scroll_position = 0;\n let ticking = false;\n\n window.addEventListener(\"scroll\", function (e) {\n last_known_scroll_position = window.scrollY;\n\n if (!ticking) {\n window.requestAnimationFrame(function () {\n scrollHandler(last_known_scroll_position);\n ticking = false;\n });\n\n ticking = true;\n }\n });\n window.scroll();\n}\n\nfunction setupScrollSpy() {\n if (tocScroll === null) {\n return;\n }\n\n // Scrollspy -- highlight table on contents, based on scroll\n new Gumshoe(\".toc-tree a\", {\n reflow: true,\n recursive: true,\n navClass: \"scroll-current\",\n offset: () => {\n let rem = parseFloat(getComputedStyle(document.documentElement).fontSize);\n return header.getBoundingClientRect().height + 2.5 * rem + 1;\n },\n });\n}\n\nfunction setupTheme() {\n // Attach event handlers for toggling themes\n const buttons = document.getElementsByClassName(\"theme-toggle\");\n Array.from(buttons).forEach((btn) => {\n btn.addEventListener(\"click\", cycleThemeOnce);\n });\n}\n\nfunction setup() {\n setupTheme();\n setupScrollHandler();\n setupScrollSpy();\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Main entrypoint\n////////////////////////////////////////////////////////////////////////////////\nfunction main() {\n document.body.parentNode.classList.remove(\"no-js\");\n\n header = document.querySelector(\"header\");\n tocScroll = document.querySelector(\".toc-scroll\");\n\n setup();\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", main);\n"],"names":["root","g","window","this","defaults","navClass","contentClass","nested","nestedClass","offset","reflow","events","emitEvent","type","elem","detail","settings","event","CustomEvent","bubbles","cancelable","dispatchEvent","getOffsetTop","location","offsetParent","offsetTop","sortContents","contents","sort","item1","item2","content","isInView","bottom","bounds","getBoundingClientRect","parseFloat","getOffset","parseInt","innerHeight","document","documentElement","clientHeight","top","isAtBottom","Math","ceil","pageYOffset","max","body","scrollHeight","offsetHeight","getActive","last","length","item","useLastItem","i","deactivateNested","nav","parentNode","li","closest","classList","remove","deactivate","items","link","activateNested","add","selector","options","navItems","current","timeout","publicAPIs","querySelectorAll","Array","prototype","forEach","call","getElementById","decodeURIComponent","hash","substr","push","active","activate","scrollHandler","cancelAnimationFrame","requestAnimationFrame","detect","resizeHandler","destroy","removeEventListener","merged","arguments","obj","key","hasOwnProperty","extend","setup","addEventListener","factory","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","n","getter","__esModule","d","a","definition","o","Object","defineProperty","enumerable","get","globalThis","Function","e","prop","tocScroll","header","lastScrollTop","scrollTop","GO_TO_TOP_OFFSET","cycleThemeOnce","currentTheme","localStorage","getItem","mode","matchMedia","matches","console","error","dataset","theme","setItem","log","buttons","getElementsByClassName","from","btn","setupTheme","last_known_scroll_position","ticking","scrollY","positionY","headerTop","floor","scrollHandlerForHeader","scrollHandlerForBackToTop","scrollTo","querySelector","scrollHandlerForTOC","scroll","setupScrollHandler","recursive","rem","getComputedStyle","fontSize","height"],"sourceRoot":""} \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js index 97d56a7..b08d58c 100644 --- a/_static/searchtools.js +++ b/_static/searchtools.js @@ -4,7 +4,7 @@ * * Sphinx JavaScript utilities for the full-text search. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -57,12 +57,12 @@ const _removeChildren = (element) => { const _escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -const _displayItem = (item, searchTerms) => { +const _displayItem = (item, searchTerms, highlightTerms) => { const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; const [docName, title, anchor, descr, score, _filename] = item; @@ -75,28 +75,35 @@ const _displayItem = (item, searchTerms) => { if (dirname.match(/\/index\/$/)) dirname = dirname.substring(0, dirname.length - 6); else if (dirname === "index/") dirname = ""; - requestUrl = docUrlRoot + dirname; + requestUrl = contentRoot + dirname; linkUrl = requestUrl; } else { // normal html builders - requestUrl = docUrlRoot + docName + docFileSuffix; + requestUrl = contentRoot + docName + docFileSuffix; linkUrl = docName + docLinkSuffix; } let linkEl = listItem.appendChild(document.createElement("a")); linkEl.href = linkUrl + anchor; linkEl.dataset.score = score; linkEl.innerHTML = title; - if (descr) + if (descr) { listItem.appendChild(document.createElement("span")).innerHTML = " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } else if (showSearchSummary) fetch(requestUrl) .then((responseData) => responseData.text()) .then((data) => { if (data) listItem.appendChild( - Search.makeSearchSummary(data, searchTerms) + Search.makeSearchSummary(data, searchTerms, anchor) ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); }); Search.output.appendChild(listItem); }; @@ -109,26 +116,43 @@ const _finishSearch = (resultCount) => { ); else Search.status.innerText = _( - `Search finished, found ${resultCount} page(s) matching the search query.` - ); + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( results, resultCount, - searchTerms + searchTerms, + highlightTerms, ) => { // results left, load the summary and display it // this is intended to be dynamic (don't sub resultsCount) if (results.length) { - _displayItem(results.pop(), searchTerms); + _displayItem(results.pop(), searchTerms, highlightTerms); setTimeout( - () => _displayNextItem(results, resultCount, searchTerms), + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), 5 ); } // search finished, update title and status message else _finishSearch(resultCount); }; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; /** * Default splitQuery function. Can be overridden in ``sphinx.search`` with a @@ -152,13 +176,26 @@ const Search = { _queued_query: null, _pulse_status: -1, - htmlToText: (htmlString) => { + htmlToText: (htmlString, anchor) => { const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent !== undefined) return docContent.textContent; + if (docContent) return docContent.textContent; + console.warn( - "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." ); return ""; }, @@ -231,16 +268,7 @@ const Search = { else Search.deferQuery(query); }, - /** - * execute search (requires search index to be loaded) - */ - query: (query) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - + _parseQuery: (query) => { // stem the search terms and add them to the correct list const stemmer = new Stemmer(); const searchTerms = new Set(); @@ -276,21 +304,38 @@ const Search = { // console.info("required: ", [...searchTerms]); // console.info("excluded: ", [...excludedTerms]); - // array of [docname, title, anchor, descr, score, filename] - let results = []; + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + _removeChildren(document.getElementById("search-progress")); - const queryLower = query.toLowerCase(); + const queryLower = query.toLowerCase().trim(); for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { for (const [file, id] of foundTitles) { - let score = Math.round(100 * queryLower.length / title.length) - results.push([ + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ docNames[file], titles[file] !== title ? `${titles[file]} > ${title}` : title, id !== null ? "#" + id : "", null, - score, + score + boost, filenames[file], ]); } @@ -300,46 +345,47 @@ const Search = { // search for explicit entries in index directives for (const [entry, foundEntries] of Object.entries(indexEntries)) { if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id] of foundEntries) { - let score = Math.round(100 * queryLower.length / entry.length) - results.push([ + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ docNames[file], titles[file], id ? "#" + id : "", null, score, filenames[file], - ]); + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } } } } // lookup as object objectTerms.forEach((term) => - results.push(...Search.performObjectSearch(term, objectTerms)) + normalResults.push(...Search.performObjectSearch(term, objectTerms)) ); // lookup as search terms in fulltext - results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); // let the scorer override scores with a custom scoring function - if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); - - // now sort the results by score (in opposite order of appearance, since the - // display function below uses pop() to retrieve items) and then - // alphabetically - results.sort((a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; - }); + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; // remove duplicate search results // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept @@ -353,14 +399,19 @@ const Search = { return acc; }, []); - results = results.reverse(); + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); // for debugging //Search.lastresults = results.slice(); // a copy // console.info("search results:", Search.lastresults); // print the results - _displayNextItem(results, results.length, searchTerms); + _displayNextItem(results, results.length, searchTerms, highlightTerms); }, /** @@ -458,14 +509,18 @@ const Search = { // add support for partial matches if (word.length > 2) { const escapedWord = _escapeRegExp(word); - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord) && !terms[word]) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord) && !titleTerms[word]) - arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); - }); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } } // no match but word was a required one @@ -488,9 +543,8 @@ const Search = { // create the mapping files.forEach((file) => { - if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) - fileMap.get(file).push(word); - else fileMap.set(file, [word]); + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); }); }); @@ -541,8 +595,8 @@ const Search = { * search summary for a given text. keywords is a list * of stemmed words. */ - makeSearchSummary: (htmlText, keywords) => { - const text = Search.htmlToText(htmlText); + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); if (text === "") return null; const textLower = text.toLowerCase(); diff --git a/_static/sg_gallery-binder.css b/_static/sg_gallery-binder.css index a33aa42..420005d 100644 --- a/_static/sg_gallery-binder.css +++ b/_static/sg_gallery-binder.css @@ -4,3 +4,8 @@ div.binder-badge { margin: 1em auto; vertical-align: middle; } + +div.lite-badge { + margin: 1em auto; + vertical-align: middle; +} diff --git a/_static/sg_gallery-dataframe.css b/_static/sg_gallery-dataframe.css index 25be730..fac74c4 100644 --- a/_static/sg_gallery-dataframe.css +++ b/_static/sg_gallery-dataframe.css @@ -19,6 +19,7 @@ table.dataframe { color: var(--sg-text-color); font-size: 12px; table-layout: fixed; + width: auto; } table.dataframe thead { border-bottom: 1px solid var(--sg-text-color); diff --git a/_static/sg_gallery.css b/_static/sg_gallery.css index 7222783..9bcd33c 100644 --- a/_static/sg_gallery.css +++ b/_static/sg_gallery.css @@ -178,23 +178,44 @@ thumbnail with its default link Background color */ max-height: 112px; max-width: 160px; } -.sphx-glr-thumbcontainer[tooltip]:hover:after { - background: var(--sg-tooltip-background); + +.sphx-glr-thumbcontainer[tooltip]::before { + content: ""; + position: absolute; + pointer-events: none; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 97; + background-color: var(--sg-tooltip-background); + backdrop-filter: blur(3px); + opacity: 0; + transition: opacity 0.3s; +} + +.sphx-glr-thumbcontainer[tooltip]:hover::before { + opacity: 1; +} + +.sphx-glr-thumbcontainer[tooltip]:hover::after { -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; color: var(--sg-tooltip-foreground); content: attr(tooltip); - padding: 10px; + padding: 10px 10px 5px; z-index: 98; width: 100%; - height: 100%; + max-height: 100%; position: absolute; pointer-events: none; top: 0; box-sizing: border-box; overflow: hidden; - backdrop-filter: blur(3px); + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 6; } .sphx-glr-script-out { @@ -283,6 +304,10 @@ div.sphx-glr-download a:hover { background-color: var(--sg-download-a-hover-background-color); } +div.sphx-glr-sidebar-item img { + max-height: 20px; +} + .sphx-glr-example-title:target::before { display: block; content: ""; diff --git a/_static/skeleton.css b/_static/skeleton.css new file mode 100644 index 0000000..467c878 --- /dev/null +++ b/_static/skeleton.css @@ -0,0 +1,296 @@ +/* Some sane resets. */ +html { + height: 100%; +} + +body { + margin: 0; + min-height: 100%; +} + +/* All the flexbox magic! */ +body, +.sb-announcement, +.sb-content, +.sb-main, +.sb-container, +.sb-container__inner, +.sb-article-container, +.sb-footer-content, +.sb-header, +.sb-header-secondary, +.sb-footer { + display: flex; +} + +/* These order things vertically */ +body, +.sb-main, +.sb-article-container { + flex-direction: column; +} + +/* Put elements in the center */ +.sb-header, +.sb-header-secondary, +.sb-container, +.sb-content, +.sb-footer, +.sb-footer-content { + justify-content: center; +} +/* Put elements at the ends */ +.sb-article-container { + justify-content: space-between; +} + +/* These elements grow. */ +.sb-main, +.sb-content, +.sb-container, +article { + flex-grow: 1; +} + +/* Because padding making this wider is not fun */ +article { + box-sizing: border-box; +} + +/* The announcements element should never be wider than the page. */ +.sb-announcement { + max-width: 100%; +} + +.sb-sidebar-primary, +.sb-sidebar-secondary { + flex-shrink: 0; + width: 17rem; +} + +.sb-announcement__inner { + justify-content: center; + + box-sizing: border-box; + height: 3rem; + + overflow-x: auto; + white-space: nowrap; +} + +/* Sidebars, with checkbox-based toggle */ +.sb-sidebar-primary, +.sb-sidebar-secondary { + position: fixed; + height: 100%; + top: 0; +} + +.sb-sidebar-primary { + left: -17rem; + transition: left 250ms ease-in-out; +} +.sb-sidebar-secondary { + right: -17rem; + transition: right 250ms ease-in-out; +} + +.sb-sidebar-toggle { + display: none; +} +.sb-sidebar-overlay { + position: fixed; + top: 0; + width: 0; + height: 0; + + transition: width 0ms ease 250ms, height 0ms ease 250ms, opacity 250ms ease; + + opacity: 0; + background-color: rgba(0, 0, 0, 0.54); +} + +#sb-sidebar-toggle--primary:checked + ~ .sb-sidebar-overlay[for="sb-sidebar-toggle--primary"], +#sb-sidebar-toggle--secondary:checked + ~ .sb-sidebar-overlay[for="sb-sidebar-toggle--secondary"] { + width: 100%; + height: 100%; + opacity: 1; + transition: width 0ms ease, height 0ms ease, opacity 250ms ease; +} + +#sb-sidebar-toggle--primary:checked ~ .sb-container .sb-sidebar-primary { + left: 0; +} +#sb-sidebar-toggle--secondary:checked ~ .sb-container .sb-sidebar-secondary { + right: 0; +} + +/* Full-width mode */ +.drop-secondary-sidebar-for-full-width-content + .hide-when-secondary-sidebar-shown { + display: none !important; +} +.drop-secondary-sidebar-for-full-width-content .sb-sidebar-secondary { + display: none !important; +} + +/* Mobile views */ +.sb-page-width { + width: 100%; +} + +.sb-article-container, +.sb-footer-content__inner, +.drop-secondary-sidebar-for-full-width-content .sb-article, +.drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 100vw; +} + +.sb-article, +.match-content-width { + padding: 0 1rem; + box-sizing: border-box; +} + +@media (min-width: 32rem) { + .sb-article, + .match-content-width { + padding: 0 2rem; + } +} + +/* Tablet views */ +@media (min-width: 42rem) { + .sb-article-container { + width: auto; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 42rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} +@media (min-width: 46rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 46rem; + } + .sb-article, + .match-content-width { + width: 46rem; + } +} +@media (min-width: 50rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 50rem; + } + .sb-article, + .match-content-width { + width: 50rem; + } +} + +/* Tablet views */ +@media (min-width: 59rem) { + .sb-sidebar-secondary { + position: static; + } + .hide-when-secondary-sidebar-shown { + display: none !important; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 59rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} +@media (min-width: 63rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 63rem; + } + .sb-article, + .match-content-width { + width: 46rem; + } +} +@media (min-width: 67rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } + .sb-article, + .match-content-width { + width: 50rem; + } +} + +/* Desktop views */ +@media (min-width: 76rem) { + .sb-sidebar-primary { + position: static; + } + .hide-when-primary-sidebar-shown { + display: none !important; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 59rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} + +/* Full desktop views */ +@media (min-width: 80rem) { + .sb-article, + .match-content-width { + width: 46rem; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 63rem; + } +} + +@media (min-width: 84rem) { + .sb-article, + .match-content-width { + width: 50rem; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } +} + +@media (min-width: 88rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } + .sb-page-width { + width: 88rem; + } +} diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js index aae669d..8a96c69 100644 --- a/_static/sphinx_highlight.js +++ b/_static/sphinx_highlight.js @@ -29,14 +29,19 @@ const _highlight = (node, addItems, text, className) => { } span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); parent.insertBefore( span, parent.insertBefore( - document.createTextNode(val.substr(pos + text.length)), + rest, node.nextSibling ) ); node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); if (isInSVG) { const rect = document.createElementNS( @@ -140,5 +145,10 @@ const SphinxHighlight = { }, }; -_ready(SphinxHighlight.highlightSearchWords); -_ready(SphinxHighlight.initEscapeListener); +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/_static/styles/furo-extensions.css b/_static/styles/furo-extensions.css new file mode 100644 index 0000000..8229587 --- /dev/null +++ b/_static/styles/furo-extensions.css @@ -0,0 +1,2 @@ +#furo-sidebar-ad-placement{padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)}#furo-sidebar-ad-placement .ethical-sidebar{background:var(--color-background-secondary);border:none;box-shadow:none}#furo-sidebar-ad-placement .ethical-sidebar:hover{background:var(--color-background-hover)}#furo-sidebar-ad-placement .ethical-sidebar a{color:var(--color-foreground-primary)}#furo-sidebar-ad-placement .ethical-callout a{color:var(--color-foreground-secondary)!important}#furo-readthedocs-versions{background:transparent;display:block;position:static;width:100%}#furo-readthedocs-versions .rst-versions{background:#1a1c1e}#furo-readthedocs-versions .rst-current-version{background:var(--color-sidebar-item-background);cursor:unset}#furo-readthedocs-versions .rst-current-version:hover{background:var(--color-sidebar-item-background)}#furo-readthedocs-versions .rst-current-version .fa-book{color:var(--color-foreground-primary)}#furo-readthedocs-versions>.rst-other-versions{padding:0}#furo-readthedocs-versions>.rst-other-versions small{opacity:1}#furo-readthedocs-versions .injected .rst-versions{position:unset}#furo-readthedocs-versions:focus-within,#furo-readthedocs-versions:hover{box-shadow:0 0 0 1px var(--color-sidebar-background-border)}#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:hover .rst-current-version{background:#1a1c1e;font-size:inherit;height:auto;line-height:inherit;padding:12px;text-align:right}#furo-readthedocs-versions:focus-within .rst-current-version .fa-book,#furo-readthedocs-versions:hover .rst-current-version .fa-book{color:#fff;float:left}#furo-readthedocs-versions:focus-within .fa-caret-down,#furo-readthedocs-versions:hover .fa-caret-down{display:none}#furo-readthedocs-versions:focus-within .injected,#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:focus-within .rst-other-versions,#furo-readthedocs-versions:hover .injected,#furo-readthedocs-versions:hover .rst-current-version,#furo-readthedocs-versions:hover .rst-other-versions{display:block}#furo-readthedocs-versions:focus-within>.rst-current-version,#furo-readthedocs-versions:hover>.rst-current-version{display:none}.highlight:hover button.copybtn{color:var(--color-code-foreground)}.highlight button.copybtn{align-items:center;background-color:var(--color-code-background);border:none;color:var(--color-background-item);cursor:pointer;height:1.25em;right:.5rem;top:.625rem;transition:color .3s,opacity .3s;width:1.25em}.highlight button.copybtn:hover{background-color:var(--color-code-background);color:var(--color-brand-content)}.highlight button.copybtn:after{background-color:transparent;color:var(--color-code-foreground);display:none}.highlight button.copybtn.success{color:#22863a;transition:color 0ms}.highlight button.copybtn.success:after{display:block}.highlight button.copybtn svg{padding:0}body{--sd-color-primary:var(--color-brand-primary);--sd-color-primary-highlight:var(--color-brand-content);--sd-color-primary-text:var(--color-background-primary);--sd-color-shadow:rgba(0,0,0,.05);--sd-color-card-border:var(--color-card-border);--sd-color-card-border-hover:var(--color-brand-content);--sd-color-card-background:var(--color-card-background);--sd-color-card-text:var(--color-foreground-primary);--sd-color-card-header:var(--color-card-marginals-background);--sd-color-card-footer:var(--color-card-marginals-background);--sd-color-tabs-label-active:var(--color-brand-content);--sd-color-tabs-label-hover:var(--color-foreground-muted);--sd-color-tabs-label-inactive:var(--color-foreground-muted);--sd-color-tabs-underline-active:var(--color-brand-content);--sd-color-tabs-underline-hover:var(--color-foreground-border);--sd-color-tabs-underline-inactive:var(--color-background-border);--sd-color-tabs-overline:var(--color-background-border);--sd-color-tabs-underline:var(--color-background-border)}.sd-tab-content{box-shadow:0 -2px var(--sd-color-tabs-overline),0 1px var(--sd-color-tabs-underline)}.sd-card{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)}.sd-shadow-sm{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-md{box-shadow:0 .3rem .75rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-lg{box-shadow:0 .6rem 1.5rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-card-hover:hover{transform:none}.sd-cards-carousel{gap:.25rem;padding:.25rem}body{--tabs--label-text:var(--color-foreground-muted);--tabs--label-text--hover:var(--color-foreground-muted);--tabs--label-text--active:var(--color-brand-content);--tabs--label-text--active--hover:var(--color-brand-content);--tabs--label-background:transparent;--tabs--label-background--hover:transparent;--tabs--label-background--active:transparent;--tabs--label-background--active--hover:transparent;--tabs--padding-x:0.25em;--tabs--margin-x:1em;--tabs--border:var(--color-background-border);--tabs--label-border:transparent;--tabs--label-border--hover:var(--color-foreground-muted);--tabs--label-border--active:var(--color-brand-content);--tabs--label-border--active--hover:var(--color-brand-content)}[role=main] .container{max-width:none;padding-left:0;padding-right:0}.shadow.docutils{border:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)!important}.sphinx-bs .card{background-color:var(--color-background-secondary);color:var(--color-foreground)} +/*# sourceMappingURL=furo-extensions.css.map*/ \ No newline at end of file diff --git a/_static/styles/furo-extensions.css.map b/_static/styles/furo-extensions.css.map new file mode 100644 index 0000000..c26eac7 --- /dev/null +++ b/_static/styles/furo-extensions.css.map @@ -0,0 +1 @@ +{"version":3,"file":"styles/furo-extensions.css","mappings":"AAGA,2BACE,oFACA,4CAKE,6CAHA,YACA,eAEA,CACA,kDACE,yCAEF,8CACE,sCAEJ,8CACE,kDAEJ,2BAGE,uBACA,cAHA,gBACA,UAEA,CAGA,yCACE,mBAEF,gDAEE,gDADA,YACA,CACA,sDACE,gDACF,yDACE,sCAEJ,+CACE,UACA,qDACE,UAGF,mDACE,eAEJ,yEAEE,4DAEA,mHASE,mBAPA,kBAEA,YADA,oBAGA,aADA,gBAIA,CAEA,qIAEE,WADA,UACA,CAEJ,uGACE,aAEF,iUAGE,cAEF,mHACE,aC1EJ,gCACE,mCAEF,0BAEE,mBAUA,8CACA,YAFA,mCAKA,eAZA,cAIA,YADA,YAYA,iCAdA,YAcA,CAEA,gCAEE,8CADA,gCACA,CAEF,gCAGE,6BADA,mCADA,YAEA,CAEF,kCAEE,cADA,oBACA,CACA,wCACE,cAEJ,8BACE,UCzCN,KAEE,6CAA8C,CAC9C,uDAAwD,CACxD,uDAAwD,CAGxD,iCAAsC,CAGtC,+CAAgD,CAChD,uDAAwD,CACxD,uDAAwD,CACxD,oDAAqD,CACrD,6DAA8D,CAC9D,6DAA8D,CAG9D,uDAAwD,CACxD,yDAA0D,CAC1D,4DAA6D,CAC7D,2DAA4D,CAC5D,8DAA+D,CAC/D,iEAAkE,CAClE,uDAAwD,CACxD,wDAAyD,CAG3D,gBACE,qFAGF,SACE,6EAEF,cACE,uFAEF,cACE,uFAEF,cACE,uFAGF,qBACE,eAEF,mBACE,WACA,eChDF,KACE,gDAAiD,CACjD,uDAAwD,CACxD,qDAAsD,CACtD,4DAA6D,CAC7D,oCAAqC,CACrC,2CAA4C,CAC5C,4CAA6C,CAC7C,mDAAoD,CACpD,wBAAyB,CACzB,oBAAqB,CACrB,6CAA8C,CAC9C,gCAAiC,CACjC,yDAA0D,CAC1D,uDAAwD,CACxD,8DAA+D,CCbjE,uBACE,eACA,eACA,gBAGF,iBACE,YACA,+EAGF,iBACE,mDACA","sources":["webpack:///./src/furo/assets/styles/extensions/_readthedocs.sass","webpack:///./src/furo/assets/styles/extensions/_copybutton.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-design.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-inline-tabs.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-panels.sass"],"sourcesContent":["// This file contains the styles used for tweaking how ReadTheDoc's embedded\n// contents would show up inside the theme.\n\n#furo-sidebar-ad-placement\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n .ethical-sidebar\n // Remove the border and box-shadow.\n border: none\n box-shadow: none\n // Manage the background colors.\n background: var(--color-background-secondary)\n &:hover\n background: var(--color-background-hover)\n // Ensure the text is legible.\n a\n color: var(--color-foreground-primary)\n\n .ethical-callout a\n color: var(--color-foreground-secondary) !important\n\n#furo-readthedocs-versions\n position: static\n width: 100%\n background: transparent\n display: block\n\n // Make the background color fit with the theme's aesthetic.\n .rst-versions\n background: rgb(26, 28, 30)\n\n .rst-current-version\n cursor: unset\n background: var(--color-sidebar-item-background)\n &:hover\n background: var(--color-sidebar-item-background)\n .fa-book\n color: var(--color-foreground-primary)\n\n > .rst-other-versions\n padding: 0\n small\n opacity: 1\n\n .injected\n .rst-versions\n position: unset\n\n &:hover,\n &:focus-within\n box-shadow: 0 0 0 1px var(--color-sidebar-background-border)\n\n .rst-current-version\n // Undo the tweaks done in RTD's CSS\n font-size: inherit\n line-height: inherit\n height: auto\n text-align: right\n padding: 12px\n\n // Match the rest of the body\n background: #1a1c1e\n\n .fa-book\n float: left\n color: white\n\n .fa-caret-down\n display: none\n\n .rst-current-version,\n .rst-other-versions,\n .injected\n display: block\n\n > .rst-current-version\n display: none\n",".highlight\n &:hover button.copybtn\n color: var(--color-code-foreground)\n\n button.copybtn\n // Align things correctly\n align-items: center\n\n height: 1.25em\n width: 1.25em\n\n top: 0.625rem // $code-spacing-vertical\n right: 0.5rem\n\n // Make it look better\n color: var(--color-background-item)\n background-color: var(--color-code-background)\n border: none\n\n // Change to cursor to make it obvious that you can click on it\n cursor: pointer\n\n // Transition smoothly, for aesthetics\n transition: color 300ms, opacity 300ms\n\n &:hover\n color: var(--color-brand-content)\n background-color: var(--color-code-background)\n\n &::after\n display: none\n color: var(--color-code-foreground)\n background-color: transparent\n\n &.success\n transition: color 0ms\n color: #22863a\n &::after\n display: block\n\n svg\n padding: 0\n","body\n // Colors\n --sd-color-primary: var(--color-brand-primary)\n --sd-color-primary-highlight: var(--color-brand-content)\n --sd-color-primary-text: var(--color-background-primary)\n\n // Shadows\n --sd-color-shadow: rgba(0, 0, 0, 0.05)\n\n // Cards\n --sd-color-card-border: var(--color-card-border)\n --sd-color-card-border-hover: var(--color-brand-content)\n --sd-color-card-background: var(--color-card-background)\n --sd-color-card-text: var(--color-foreground-primary)\n --sd-color-card-header: var(--color-card-marginals-background)\n --sd-color-card-footer: var(--color-card-marginals-background)\n\n // Tabs\n --sd-color-tabs-label-active: var(--color-brand-content)\n --sd-color-tabs-label-hover: var(--color-foreground-muted)\n --sd-color-tabs-label-inactive: var(--color-foreground-muted)\n --sd-color-tabs-underline-active: var(--color-brand-content)\n --sd-color-tabs-underline-hover: var(--color-foreground-border)\n --sd-color-tabs-underline-inactive: var(--color-background-border)\n --sd-color-tabs-overline: var(--color-background-border)\n --sd-color-tabs-underline: var(--color-background-border)\n\n// Tabs\n.sd-tab-content\n box-shadow: 0 -2px var(--sd-color-tabs-overline), 0 1px var(--sd-color-tabs-underline)\n\n// Shadows\n.sd-card // Have a shadow by default\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n.sd-shadow-sm\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-md\n box-shadow: 0 0.3rem 0.75rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-lg\n box-shadow: 0 0.6rem 1.5rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Cards\n.sd-card-hover:hover // Don't change scale on hover\n transform: none\n\n.sd-cards-carousel // Have a bit of gap in the carousel by default\n gap: 0.25rem\n padding: 0.25rem\n","// This file contains styles to tweak sphinx-inline-tabs to work well with Furo.\n\nbody\n --tabs--label-text: var(--color-foreground-muted)\n --tabs--label-text--hover: var(--color-foreground-muted)\n --tabs--label-text--active: var(--color-brand-content)\n --tabs--label-text--active--hover: var(--color-brand-content)\n --tabs--label-background: transparent\n --tabs--label-background--hover: transparent\n --tabs--label-background--active: transparent\n --tabs--label-background--active--hover: transparent\n --tabs--padding-x: 0.25em\n --tabs--margin-x: 1em\n --tabs--border: var(--color-background-border)\n --tabs--label-border: transparent\n --tabs--label-border--hover: var(--color-foreground-muted)\n --tabs--label-border--active: var(--color-brand-content)\n --tabs--label-border--active--hover: var(--color-brand-content)\n","// This file contains styles to tweak sphinx-panels to work well with Furo.\n\n// sphinx-panels includes Bootstrap 4, which uses .container which can conflict\n// with docutils' `.. container::` directive.\n[role=\"main\"] .container\n max-width: initial\n padding-left: initial\n padding-right: initial\n\n// Make the panels look nicer!\n.shadow.docutils\n border: none\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Make panel colors respond to dark mode\n.sphinx-bs .card\n background-color: var(--color-background-secondary)\n color: var(--color-foreground)\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/_static/styles/furo.css b/_static/styles/furo.css new file mode 100644 index 0000000..05a56b1 --- /dev/null +++ b/_static/styles/furo.css @@ -0,0 +1,2 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}@media print{.content-icon-container,.headerlink,.mobile-header,.related-pages{display:none!important}.highlight{border:.1pt solid var(--color-foreground-border)}a,blockquote,dl,ol,p,pre,table,ul{page-break-inside:avoid}caption,figure,h1,h2,h3,h4,h5,h6,img{page-break-after:avoid;page-break-inside:avoid}dl,ol,ul{page-break-before:avoid}}.visually-hidden{height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;clip:rect(0,0,0,0)!important;background:var(--color-background-primary);border:0!important;color:var(--color-foreground-primary);white-space:nowrap!important}:-moz-focusring{outline:auto}body{--font-stack:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;--font-stack--monospace:"SFMono-Regular",Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;--font-stack--headings:var(--font-stack);--font-size--normal:100%;--font-size--small:87.5%;--font-size--small--2:81.25%;--font-size--small--3:75%;--font-size--small--4:62.5%;--sidebar-caption-font-size:var(--font-size--small--2);--sidebar-item-font-size:var(--font-size--small);--sidebar-search-input-font-size:var(--font-size--small);--toc-font-size:var(--font-size--small--3);--toc-font-size--mobile:var(--font-size--normal);--toc-title-font-size:var(--font-size--small--4);--admonition-font-size:0.8125rem;--admonition-title-font-size:0.8125rem;--code-font-size:var(--font-size--small--2);--api-font-size:var(--font-size--small);--header-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*4);--header-padding:0.5rem;--sidebar-tree-space-above:1.5rem;--sidebar-caption-space-above:1rem;--sidebar-item-line-height:1rem;--sidebar-item-spacing-vertical:0.5rem;--sidebar-item-spacing-horizontal:1rem;--sidebar-item-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*2);--sidebar-expander-width:var(--sidebar-item-height);--sidebar-search-space-above:0.5rem;--sidebar-search-input-spacing-vertical:0.5rem;--sidebar-search-input-spacing-horizontal:0.5rem;--sidebar-search-input-height:1rem;--sidebar-search-icon-size:var(--sidebar-search-input-height);--toc-title-padding:0.25rem 0;--toc-spacing-vertical:1.5rem;--toc-spacing-horizontal:1.5rem;--toc-item-spacing-vertical:0.4rem;--toc-item-spacing-horizontal:1rem;--icon-search:url('data:image/svg+xml;charset=utf-8,');--icon-pencil:url('data:image/svg+xml;charset=utf-8,');--icon-abstract:url('data:image/svg+xml;charset=utf-8,');--icon-info:url('data:image/svg+xml;charset=utf-8,');--icon-flame:url('data:image/svg+xml;charset=utf-8,');--icon-question:url('data:image/svg+xml;charset=utf-8,');--icon-warning:url('data:image/svg+xml;charset=utf-8,');--icon-failure:url('data:image/svg+xml;charset=utf-8,');--icon-spark:url('data:image/svg+xml;charset=utf-8,');--color-admonition-title--caution:#ff9100;--color-admonition-title-background--caution:rgba(255,145,0,.2);--color-admonition-title--warning:#ff9100;--color-admonition-title-background--warning:rgba(255,145,0,.2);--color-admonition-title--danger:#ff5252;--color-admonition-title-background--danger:rgba(255,82,82,.2);--color-admonition-title--attention:#ff5252;--color-admonition-title-background--attention:rgba(255,82,82,.2);--color-admonition-title--error:#ff5252;--color-admonition-title-background--error:rgba(255,82,82,.2);--color-admonition-title--hint:#00c852;--color-admonition-title-background--hint:rgba(0,200,82,.2);--color-admonition-title--tip:#00c852;--color-admonition-title-background--tip:rgba(0,200,82,.2);--color-admonition-title--important:#00bfa5;--color-admonition-title-background--important:rgba(0,191,165,.2);--color-admonition-title--note:#00b0ff;--color-admonition-title-background--note:rgba(0,176,255,.2);--color-admonition-title--seealso:#448aff;--color-admonition-title-background--seealso:rgba(68,138,255,.2);--color-admonition-title--admonition-todo:grey;--color-admonition-title-background--admonition-todo:hsla(0,0%,50%,.2);--color-admonition-title:#651fff;--color-admonition-title-background:rgba(101,31,255,.2);--icon-admonition-default:var(--icon-abstract);--color-topic-title:#14b8a6;--color-topic-title-background:rgba(20,184,166,.2);--icon-topic-default:var(--icon-pencil);--color-problematic:#b30000;--color-foreground-primary:#000;--color-foreground-secondary:#5a5c63;--color-foreground-muted:#6b6f76;--color-foreground-border:#878787;--color-background-primary:#fff;--color-background-secondary:#f8f9fb;--color-background-hover:#efeff4;--color-background-hover--transparent:#efeff400;--color-background-border:#eeebee;--color-background-item:#ccc;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#0a4bff;--color-brand-content:#2757dd;--color-brand-visited:#872ee0;--color-api-background:var(--color-background-hover--transparent);--color-api-background-hover:var(--color-background-hover);--color-api-overall:var(--color-foreground-secondary);--color-api-name:var(--color-problematic);--color-api-pre-name:var(--color-problematic);--color-api-paren:var(--color-foreground-secondary);--color-api-keyword:var(--color-foreground-primary);--color-api-added:#21632c;--color-api-added-border:#38a84d;--color-api-changed:#046172;--color-api-changed-border:#06a1bc;--color-api-deprecated:#605706;--color-api-deprecated-border:#f0d90f;--color-api-removed:#b30000;--color-api-removed-border:#ff5c5c;--color-highlight-on-target:#ffc;--color-inline-code-background:var(--color-background-secondary);--color-highlighted-background:#def;--color-highlighted-text:var(--color-foreground-primary);--color-guilabel-background:#ddeeff80;--color-guilabel-border:#bedaf580;--color-guilabel-text:var(--color-foreground-primary);--color-admonition-background:transparent;--color-table-header-background:var(--color-background-secondary);--color-table-border:var(--color-background-border);--color-card-border:var(--color-background-secondary);--color-card-background:transparent;--color-card-marginals-background:var(--color-background-secondary);--color-header-background:var(--color-background-primary);--color-header-border:var(--color-background-border);--color-header-text:var(--color-foreground-primary);--color-sidebar-background:var(--color-background-secondary);--color-sidebar-background-border:var(--color-background-border);--color-sidebar-brand-text:var(--color-foreground-primary);--color-sidebar-caption-text:var(--color-foreground-muted);--color-sidebar-link-text:var(--color-foreground-secondary);--color-sidebar-link-text--top-level:var(--color-brand-primary);--color-sidebar-item-background:var(--color-sidebar-background);--color-sidebar-item-background--current:var( --color-sidebar-item-background );--color-sidebar-item-background--hover:linear-gradient(90deg,var(--color-background-hover--transparent) 0%,var(--color-background-hover) var(--sidebar-item-spacing-horizontal),var(--color-background-hover) 100%);--color-sidebar-item-expander-background:transparent;--color-sidebar-item-expander-background--hover:var( --color-background-hover );--color-sidebar-search-text:var(--color-foreground-primary);--color-sidebar-search-background:var(--color-background-secondary);--color-sidebar-search-background--focus:var(--color-background-primary);--color-sidebar-search-border:var(--color-background-border);--color-sidebar-search-icon:var(--color-foreground-muted);--color-toc-background:var(--color-background-primary);--color-toc-title-text:var(--color-foreground-muted);--color-toc-item-text:var(--color-foreground-secondary);--color-toc-item-text--hover:var(--color-foreground-primary);--color-toc-item-text--active:var(--color-brand-primary);--color-content-foreground:var(--color-foreground-primary);--color-content-background:transparent;--color-link:var(--color-brand-content);--color-link-underline:var(--color-background-border);--color-link--hover:var(--color-brand-content);--color-link-underline--hover:var(--color-foreground-border);--color-link--visited:var(--color-brand-visited);--color-link-underline--visited:var(--color-background-border);--color-link--visited--hover:var(--color-brand-visited);--color-link-underline--visited--hover:var(--color-foreground-border)}.only-light{display:block!important}html body .only-dark{display:none!important}@media not print{body[data-theme=dark]{--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body[data-theme=dark] .only-light{display:none!important}body[data-theme=dark] .only-dark{display:block!important}@media(prefers-color-scheme:dark){body:not([data-theme=light]){--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body:not([data-theme=light]) .only-light{display:none!important}body:not([data-theme=light]) .only-dark{display:block!important}}}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:block}@media(prefers-color-scheme:dark){body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-dark{display:block}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:none}}body[data-theme=dark] .theme-toggle svg.theme-icon-when-dark,body[data-theme=light] .theme-toggle svg.theme-icon-when-light{display:block}body{font-family:var(--font-stack)}code,kbd,pre,samp{font-family:var(--font-stack--monospace)}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}article{line-height:1.5}h1,h2,h3,h4,h5,h6{border-radius:.5rem;font-family:var(--font-stack--headings);font-weight:700;line-height:1.25;margin:.5rem -.5rem;padding-left:.5rem;padding-right:.5rem}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p{margin-top:0}h1{font-size:2.5em;margin-bottom:1rem}h1,h2{margin-top:1.75rem}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.25em}h5{font-size:1.125em}h6{font-size:1em}small{font-size:80%;opacity:75%}p{margin-bottom:.75rem;margin-top:.5rem}hr.docutils{background-color:var(--color-background-border);border:0;height:1px;margin:2rem 0;padding:0}.centered{text-align:center}a{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}a:visited{color:var(--color-link--visited);text-decoration-color:var(--color-link-underline--visited)}a:visited:hover{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}a:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link{color:inherit}a.muted-link:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link:hover:visited{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}html{overflow-x:hidden;overflow-y:scroll;scroll-behavior:smooth}.sidebar-scroll,.toc-scroll,article[role=main] *{scrollbar-color:var(--color-foreground-border) transparent;scrollbar-width:thin}.sidebar-scroll::-webkit-scrollbar,.toc-scroll::-webkit-scrollbar,article[role=main] ::-webkit-scrollbar{height:.25rem;width:.25rem}.sidebar-scroll::-webkit-scrollbar-thumb,.toc-scroll::-webkit-scrollbar-thumb,article[role=main] ::-webkit-scrollbar-thumb{background-color:var(--color-foreground-border);border-radius:.125rem}body,html{height:100%}.skip-to-content,body,html{background:var(--color-background-primary);color:var(--color-foreground-primary)}.skip-to-content{border-radius:1rem;left:.25rem;padding:1rem;position:fixed;top:.25rem;transform:translateY(-200%);transition:transform .3s ease-in-out;z-index:40}.skip-to-content:focus-within{transform:translateY(0)}article{background:var(--color-content-background);color:var(--color-content-foreground);overflow-wrap:break-word}.page{display:flex;min-height:100%}.mobile-header{background-color:var(--color-header-background);border-bottom:1px solid var(--color-header-border);color:var(--color-header-text);display:none;height:var(--header-height);width:100%;z-index:10}.mobile-header.scrolled{border-bottom:none;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.mobile-header .header-center a{color:var(--color-header-text);text-decoration:none}.main{display:flex;flex:1}.sidebar-drawer{background:var(--color-sidebar-background);border-right:1px solid var(--color-sidebar-background-border);box-sizing:border-box;display:flex;justify-content:flex-end;min-width:15em;width:calc(50% - 26em)}.sidebar-container,.toc-drawer{box-sizing:border-box;width:15em}.toc-drawer{background:var(--color-toc-background);padding-right:1rem}.sidebar-sticky,.toc-sticky{display:flex;flex-direction:column;height:min(100%,100vh);height:100vh;position:sticky;top:0}.sidebar-scroll,.toc-scroll{flex-grow:1;flex-shrink:1;overflow:auto;scroll-behavior:smooth}.content{display:flex;flex-direction:column;justify-content:space-between;padding:0 3em;width:46em}.icon{display:inline-block;height:1rem;width:1rem}.icon svg{height:100%;width:100%}.announcement{align-items:center;background-color:var(--color-announcement-background);color:var(--color-announcement-text);display:flex;height:var(--header-height);overflow-x:auto}.announcement+.page{min-height:calc(100% - var(--header-height))}.announcement-content{box-sizing:border-box;min-width:100%;padding:.5rem;text-align:center;white-space:nowrap}.announcement-content a{color:var(--color-announcement-text);text-decoration-color:var(--color-announcement-text)}.announcement-content a:hover{color:var(--color-announcement-text);text-decoration-color:var(--color-link--hover)}.no-js .theme-toggle-container{display:none}.theme-toggle-container{display:flex}.theme-toggle{background:transparent;border:none;cursor:pointer;display:flex;padding:0}.theme-toggle svg{color:var(--color-foreground-primary);display:none;height:1.25rem;width:1.25rem}.theme-toggle-header{align-items:center;display:flex;justify-content:center}.nav-overlay-icon,.toc-overlay-icon{cursor:pointer;display:none}.nav-overlay-icon .icon,.toc-overlay-icon .icon{color:var(--color-foreground-secondary);height:1.5rem;width:1.5rem}.nav-overlay-icon,.toc-header-icon{align-items:center;justify-content:center}.toc-content-icon{height:1.5rem;width:1.5rem}.content-icon-container{display:flex;float:right;gap:.5rem;margin-bottom:1rem;margin-left:1rem;margin-top:1.5rem}.content-icon-container .edit-this-page svg,.content-icon-container .view-this-page svg{color:inherit;height:1.25rem;width:1.25rem}.sidebar-toggle{display:none;position:absolute}.sidebar-toggle[name=__toc]{left:20px}.sidebar-toggle:checked{left:40px}.overlay{background-color:rgba(0,0,0,.54);height:0;opacity:0;position:fixed;top:0;transition:width 0ms,height 0ms,opacity .25s ease-out;width:0}.sidebar-overlay{z-index:20}.toc-overlay{z-index:40}.sidebar-drawer{transition:left .25s ease-in-out;z-index:30}.toc-drawer{transition:right .25s ease-in-out;z-index:50}#__navigation:checked~.sidebar-overlay{height:100%;opacity:1;width:100%}#__navigation:checked~.page .sidebar-drawer{left:0;top:0}#__toc:checked~.toc-overlay{height:100%;opacity:1;width:100%}#__toc:checked~.page .toc-drawer{right:0;top:0}.back-to-top{background:var(--color-background-primary);border-radius:1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 1px 0 hsla(220,9%,46%,.502);display:none;font-size:.8125rem;left:0;margin-left:50%;padding:.5rem .75rem .5rem .5rem;position:fixed;text-decoration:none;top:1rem;transform:translateX(-50%);z-index:10}.back-to-top svg{height:1rem;width:1rem;fill:currentColor;display:inline-block}.back-to-top span{margin-left:.25rem}.show-back-to-top .back-to-top{align-items:center;display:flex}@media(min-width:97em){html{font-size:110%}}@media(max-width:82em){.toc-content-icon{display:flex}.toc-drawer{border-left:1px solid var(--color-background-muted);height:100vh;position:fixed;right:-15em;top:0}.toc-tree{border-left:none;font-size:var(--toc-font-size--mobile)}.sidebar-drawer{width:calc(50% - 18.5em)}}@media(max-width:67em){.content{margin-left:auto;margin-right:auto;padding:0 1em}}@media(max-width:63em){.nav-overlay-icon{display:flex}.sidebar-drawer{height:100vh;left:-15em;position:fixed;top:0;width:15em}.theme-toggle-header,.toc-header-icon{display:flex}.theme-toggle-content,.toc-content-icon{display:none}.mobile-header{align-items:center;display:flex;justify-content:space-between;position:sticky;top:0}.mobile-header .header-left,.mobile-header .header-right{display:flex;height:var(--header-height);padding:0 var(--header-padding)}.mobile-header .header-left label,.mobile-header .header-right label{height:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.nav-overlay-icon .icon,.theme-toggle svg{height:1.5rem;width:1.5rem}:target{scroll-margin-top:calc(var(--header-height) + 2.5rem)}.back-to-top{top:calc(var(--header-height) + .5rem)}.page{flex-direction:column;justify-content:center}}@media(max-width:48em){.content{overflow-x:auto;width:100%}}@media(max-width:46em){article[role=main] aside.sidebar{float:none;margin:1rem 0;width:100%}}.admonition,.topic{background:var(--color-admonition-background);border-radius:.2rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1);font-size:var(--admonition-font-size);margin:1rem auto;overflow:hidden;padding:0 .5rem .5rem;page-break-inside:avoid}.admonition>:nth-child(2),.topic>:nth-child(2){margin-top:0}.admonition>:last-child,.topic>:last-child{margin-bottom:0}.admonition p.admonition-title,p.topic-title{font-size:var(--admonition-title-font-size);font-weight:500;line-height:1.3;margin:0 -.5rem .5rem;padding:.4rem .5rem .4rem 2rem;position:relative}.admonition p.admonition-title:before,p.topic-title:before{content:"";height:1rem;left:.5rem;position:absolute;width:1rem}p.admonition-title{background-color:var(--color-admonition-title-background)}p.admonition-title:before{background-color:var(--color-admonition-title);-webkit-mask-image:var(--icon-admonition-default);mask-image:var(--icon-admonition-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}p.topic-title{background-color:var(--color-topic-title-background)}p.topic-title:before{background-color:var(--color-topic-title);-webkit-mask-image:var(--icon-topic-default);mask-image:var(--icon-topic-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.admonition{border-left:.2rem solid var(--color-admonition-title)}.admonition.caution{border-left-color:var(--color-admonition-title--caution)}.admonition.caution>.admonition-title{background-color:var(--color-admonition-title-background--caution)}.admonition.caution>.admonition-title:before{background-color:var(--color-admonition-title--caution);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.warning{border-left-color:var(--color-admonition-title--warning)}.admonition.warning>.admonition-title{background-color:var(--color-admonition-title-background--warning)}.admonition.warning>.admonition-title:before{background-color:var(--color-admonition-title--warning);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.danger{border-left-color:var(--color-admonition-title--danger)}.admonition.danger>.admonition-title{background-color:var(--color-admonition-title-background--danger)}.admonition.danger>.admonition-title:before{background-color:var(--color-admonition-title--danger);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.attention{border-left-color:var(--color-admonition-title--attention)}.admonition.attention>.admonition-title{background-color:var(--color-admonition-title-background--attention)}.admonition.attention>.admonition-title:before{background-color:var(--color-admonition-title--attention);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.error{border-left-color:var(--color-admonition-title--error)}.admonition.error>.admonition-title{background-color:var(--color-admonition-title-background--error)}.admonition.error>.admonition-title:before{background-color:var(--color-admonition-title--error);-webkit-mask-image:var(--icon-failure);mask-image:var(--icon-failure)}.admonition.hint{border-left-color:var(--color-admonition-title--hint)}.admonition.hint>.admonition-title{background-color:var(--color-admonition-title-background--hint)}.admonition.hint>.admonition-title:before{background-color:var(--color-admonition-title--hint);-webkit-mask-image:var(--icon-question);mask-image:var(--icon-question)}.admonition.tip{border-left-color:var(--color-admonition-title--tip)}.admonition.tip>.admonition-title{background-color:var(--color-admonition-title-background--tip)}.admonition.tip>.admonition-title:before{background-color:var(--color-admonition-title--tip);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.important{border-left-color:var(--color-admonition-title--important)}.admonition.important>.admonition-title{background-color:var(--color-admonition-title-background--important)}.admonition.important>.admonition-title:before{background-color:var(--color-admonition-title--important);-webkit-mask-image:var(--icon-flame);mask-image:var(--icon-flame)}.admonition.note{border-left-color:var(--color-admonition-title--note)}.admonition.note>.admonition-title{background-color:var(--color-admonition-title-background--note)}.admonition.note>.admonition-title:before{background-color:var(--color-admonition-title--note);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition.seealso{border-left-color:var(--color-admonition-title--seealso)}.admonition.seealso>.admonition-title{background-color:var(--color-admonition-title-background--seealso)}.admonition.seealso>.admonition-title:before{background-color:var(--color-admonition-title--seealso);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.admonition-todo{border-left-color:var(--color-admonition-title--admonition-todo)}.admonition.admonition-todo>.admonition-title{background-color:var(--color-admonition-title-background--admonition-todo)}.admonition.admonition-todo>.admonition-title:before{background-color:var(--color-admonition-title--admonition-todo);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition-todo>.admonition-title{text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd{margin-left:2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:first-child{margin-top:.125rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list,dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:last-child{margin-bottom:.75rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list>dt{font-size:var(--font-size--small);text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd:empty{margin-bottom:.5rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul{margin-left:-1.2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p:nth-child(2){margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p+p:last-child:empty{margin-bottom:0;margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{color:var(--color-api-overall)}.sig:not(.sig-inline){background:var(--color-api-background);border-radius:.25rem;font-family:var(--font-stack--monospace);font-size:var(--api-font-size);font-weight:700;margin-left:-.25rem;margin-right:-.25rem;padding:.25rem .5rem .25rem 3em;text-indent:-2.5em;transition:background .1s ease-out}.sig:not(.sig-inline):hover{background:var(--color-api-background-hover)}.sig:not(.sig-inline) a.reference .viewcode-link{font-weight:400;width:4.25rem}em.property{font-style:normal}em.property:first-child{color:var(--color-api-keyword)}.sig-name{color:var(--color-api-name)}.sig-prename{color:var(--color-api-pre-name);font-weight:400}.sig-paren{color:var(--color-api-paren)}.sig-param{font-style:normal}div.deprecated,div.versionadded,div.versionchanged,div.versionremoved{border-left:.1875rem solid;border-radius:.125rem;padding-left:.75rem}div.deprecated p,div.versionadded p,div.versionchanged p,div.versionremoved p{margin-bottom:.125rem;margin-top:.125rem}div.versionadded{border-color:var(--color-api-added-border)}div.versionadded .versionmodified{color:var(--color-api-added)}div.versionchanged{border-color:var(--color-api-changed-border)}div.versionchanged .versionmodified{color:var(--color-api-changed)}div.deprecated{border-color:var(--color-api-deprecated-border)}div.deprecated .versionmodified{color:var(--color-api-deprecated)}div.versionremoved{border-color:var(--color-api-removed-border)}div.versionremoved .versionmodified{color:var(--color-api-removed)}.viewcode-back,.viewcode-link{float:right;text-align:right}.line-block{margin-bottom:.75rem;margin-top:.5rem}.line-block .line-block{margin-bottom:0;margin-top:0;padding-left:1rem}.code-block-caption,article p.caption,table>caption{font-size:var(--font-size--small);text-align:center}.toctree-wrapper.compound .caption,.toctree-wrapper.compound :not(.caption)>.caption-text{font-size:var(--font-size--small);margin-bottom:0;text-align:initial;text-transform:uppercase}.toctree-wrapper.compound>ul{margin-bottom:0;margin-top:0}.sig-inline,code.literal{background:var(--color-inline-code-background);border-radius:.2em;font-size:var(--font-size--small--2);padding:.1em .2em}pre.literal-block .sig-inline,pre.literal-block code.literal{font-size:inherit;padding:0}p .sig-inline,p code.literal{border:1px solid var(--color-background-border)}.sig-inline{font-family:var(--font-stack--monospace)}div[class*=" highlight-"],div[class^=highlight-]{display:flex;margin:1em 0}div[class*=" highlight-"] .table-wrapper,div[class^=highlight-] .table-wrapper,pre{margin:0;padding:0}pre{overflow:auto}article[role=main] .highlight pre{line-height:1.5}.highlight pre,pre.literal-block{font-size:var(--code-font-size);padding:.625rem .875rem}pre.literal-block{background-color:var(--color-code-background);border-radius:.2rem;color:var(--color-code-foreground);margin-bottom:1rem;margin-top:1rem}.highlight{border-radius:.2rem;width:100%}.highlight .gp,.highlight span.linenos{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.highlight .hll{display:block;margin-left:-.875rem;margin-right:-.875rem;padding-left:.875rem;padding-right:.875rem}.code-block-caption{background-color:var(--color-code-background);border-bottom:1px solid;border-radius:.25rem;border-bottom-left-radius:0;border-bottom-right-radius:0;border-color:var(--color-background-border);color:var(--color-code-foreground);display:flex;font-weight:300;padding:.625rem .875rem}.code-block-caption+div[class]{margin-top:0}.code-block-caption+div[class] pre{border-top-left-radius:0;border-top-right-radius:0}.highlighttable{display:block;width:100%}.highlighttable tbody{display:block}.highlighttable tr{display:flex}.highlighttable td.linenos{background-color:var(--color-code-background);border-bottom-left-radius:.2rem;border-top-left-radius:.2rem;color:var(--color-code-foreground);padding:.625rem 0 .625rem .875rem}.highlighttable .linenodiv{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;font-size:var(--code-font-size);padding-right:.875rem}.highlighttable td.code{display:block;flex:1;overflow:hidden;padding:0}.highlighttable td.code .highlight{border-bottom-left-radius:0;border-top-left-radius:0}.highlight span.linenos{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;display:inline-block;margin-right:.875rem;padding-left:0;padding-right:.875rem}.footnote-reference{font-size:var(--font-size--small--4);vertical-align:super}dl.footnote.brackets{color:var(--color-foreground-secondary);display:grid;font-size:var(--font-size--small);grid-template-columns:max-content auto}dl.footnote.brackets dt{margin:0}dl.footnote.brackets dt>.fn-backref{margin-left:.25rem}dl.footnote.brackets dt:after{content:":"}dl.footnote.brackets dt .brackets:before{content:"["}dl.footnote.brackets dt .brackets:after{content:"]"}dl.footnote.brackets dd{margin:0;padding:0 1rem}aside.footnote{color:var(--color-foreground-secondary);font-size:var(--font-size--small)}aside.footnote>span,div.citation>span{float:left;font-weight:500;padding-right:.25rem}aside.footnote>:not(span),div.citation>p{margin-left:2rem}img{box-sizing:border-box;height:auto;max-width:100%}article .figure,article figure{border-radius:.2rem;margin:0}article .figure :last-child,article figure :last-child{margin-bottom:0}article .align-left{clear:left;float:left;margin:0 1rem 1rem}article .align-right{clear:right;float:right;margin:0 1rem 1rem}article .align-center,article .align-default{display:block;margin-left:auto;margin-right:auto;text-align:center}article table.align-default{display:table;text-align:initial}.domainindex-jumpbox,.genindex-jumpbox{border-bottom:1px solid var(--color-background-border);border-top:1px solid var(--color-background-border);padding:.25rem}.domainindex-section h2,.genindex-section h2{margin-bottom:.5rem;margin-top:.75rem}.domainindex-section ul,.genindex-section ul{margin-bottom:0;margin-top:0}ol,ul{margin-bottom:1rem;margin-top:1rem;padding-left:1.2rem}ol li>p:first-child,ul li>p:first-child{margin-bottom:.25rem;margin-top:.25rem}ol li>p:last-child,ul li>p:last-child{margin-top:.25rem}ol li>ol,ol li>ul,ul li>ol,ul li>ul{margin-bottom:.5rem;margin-top:.5rem}ol.arabic{list-style:decimal}ol.loweralpha{list-style:lower-alpha}ol.upperalpha{list-style:upper-alpha}ol.lowerroman{list-style:lower-roman}ol.upperroman{list-style:upper-roman}.simple li>ol,.simple li>ul,.toctree-wrapper li>ol,.toctree-wrapper li>ul{margin-bottom:0;margin-top:0}.field-list dt,.option-list dt,dl.footnote dt,dl.glossary dt,dl.simple dt,dl:not([class]) dt{font-weight:500;margin-top:.25rem}.field-list dt+dt,.option-list dt+dt,dl.footnote dt+dt,dl.glossary dt+dt,dl.simple dt+dt,dl:not([class]) dt+dt{margin-top:0}.field-list dt .classifier:before,.option-list dt .classifier:before,dl.footnote dt .classifier:before,dl.glossary dt .classifier:before,dl.simple dt .classifier:before,dl:not([class]) dt .classifier:before{content:":";margin-left:.2rem;margin-right:.2rem}.field-list dd ul,.field-list dd>p:first-child,.option-list dd ul,.option-list dd>p:first-child,dl.footnote dd ul,dl.footnote dd>p:first-child,dl.glossary dd ul,dl.glossary dd>p:first-child,dl.simple dd ul,dl.simple dd>p:first-child,dl:not([class]) dd ul,dl:not([class]) dd>p:first-child{margin-top:.125rem}.field-list dd ul,.option-list dd ul,dl.footnote dd ul,dl.glossary dd ul,dl.simple dd ul,dl:not([class]) dd ul{margin-bottom:.125rem}.math-wrapper{overflow-x:auto;width:100%}div.math{position:relative;text-align:center}div.math .headerlink,div.math:focus .headerlink{display:none}div.math:hover .headerlink{display:inline-block}div.math span.eqno{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);z-index:1}abbr[title]{cursor:help}.problematic{color:var(--color-problematic)}kbd:not(.compound){background-color:var(--color-background-secondary);border:1px solid var(--color-foreground-border);border-radius:.2rem;box-shadow:0 .0625rem 0 rgba(0,0,0,.2),inset 0 0 0 .125rem var(--color-background-primary);color:var(--color-foreground-primary);display:inline-block;font-size:var(--font-size--small--3);margin:0 .2rem;padding:0 .2rem;vertical-align:text-bottom}blockquote{background:var(--color-background-secondary);border-left:4px solid var(--color-background-border);margin-left:0;margin-right:0;padding:.5rem 1rem}blockquote .attribution{font-weight:600;text-align:right}blockquote.highlights,blockquote.pull-quote{font-size:1.25em}blockquote.epigraph,blockquote.pull-quote{border-left-width:0;border-radius:.5rem}blockquote.highlights{background:transparent;border-left-width:0}p .reference img{vertical-align:middle}p.rubric{font-size:1.125em;font-weight:700;line-height:1.25}dd p.rubric{font-size:var(--font-size--small);font-weight:inherit;line-height:inherit;text-transform:uppercase}article .sidebar{background-color:var(--color-background-secondary);border:1px solid var(--color-background-border);border-radius:.2rem;clear:right;float:right;margin-left:1rem;margin-right:0;width:30%}article .sidebar>*{padding-left:1rem;padding-right:1rem}article .sidebar>ol,article .sidebar>ul{padding-left:2.2rem}article .sidebar .sidebar-title{border-bottom:1px solid var(--color-background-border);font-weight:500;margin:0;padding:.5rem 1rem}[role=main] .table-wrapper.container{margin-bottom:.5rem;margin-top:1rem;overflow-x:auto;padding:.2rem .2rem .75rem;width:100%}table.docutils{border-collapse:collapse;border-radius:.2rem;border-spacing:0;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)}table.docutils th{background:var(--color-table-header-background)}table.docutils td,table.docutils th{border-bottom:1px solid var(--color-table-border);border-left:1px solid var(--color-table-border);border-right:1px solid var(--color-table-border);padding:0 .25rem}table.docutils td p,table.docutils th p{margin:.25rem}table.docutils td:first-child,table.docutils th:first-child{border-left:none}table.docutils td:last-child,table.docutils th:last-child{border-right:none}table.docutils td.text-left,table.docutils th.text-left{text-align:left}table.docutils td.text-right,table.docutils th.text-right{text-align:right}table.docutils td.text-center,table.docutils th.text-center{text-align:center}:target{scroll-margin-top:2.5rem}@media(max-width:67em){:target{scroll-margin-top:calc(2.5rem + var(--header-height))}section>span:target{scroll-margin-top:calc(2.8rem + var(--header-height))}}.headerlink{font-weight:100;-webkit-user-select:none;-moz-user-select:none;user-select:none}.code-block-caption>.headerlink,dl dt>.headerlink,figcaption p>.headerlink,h1>.headerlink,h2>.headerlink,h3>.headerlink,h4>.headerlink,h5>.headerlink,h6>.headerlink,p.caption>.headerlink,table>caption>.headerlink{margin-left:.5rem;visibility:hidden}.code-block-caption:hover>.headerlink,dl dt:hover>.headerlink,figcaption p:hover>.headerlink,h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,p.caption:hover>.headerlink,table>caption:hover>.headerlink{visibility:visible}.code-block-caption>.toc-backref,dl dt>.toc-backref,figcaption p>.toc-backref,h1>.toc-backref,h2>.toc-backref,h3>.toc-backref,h4>.toc-backref,h5>.toc-backref,h6>.toc-backref,p.caption>.toc-backref,table>caption>.toc-backref{color:inherit;text-decoration-line:none}figure:hover>figcaption>p>.headerlink,table:hover>caption>.headerlink{visibility:visible}:target>h1:first-of-type,:target>h2:first-of-type,:target>h3:first-of-type,:target>h4:first-of-type,:target>h5:first-of-type,:target>h6:first-of-type,span:target~h1:first-of-type,span:target~h2:first-of-type,span:target~h3:first-of-type,span:target~h4:first-of-type,span:target~h5:first-of-type,span:target~h6:first-of-type{background-color:var(--color-highlight-on-target)}:target>h1:first-of-type code.literal,:target>h2:first-of-type code.literal,:target>h3:first-of-type code.literal,:target>h4:first-of-type code.literal,:target>h5:first-of-type code.literal,:target>h6:first-of-type code.literal,span:target~h1:first-of-type code.literal,span:target~h2:first-of-type code.literal,span:target~h3:first-of-type code.literal,span:target~h4:first-of-type code.literal,span:target~h5:first-of-type code.literal,span:target~h6:first-of-type code.literal{background-color:transparent}.literal-block-wrapper:target .code-block-caption,.this-will-duplicate-information-and-it-is-still-useful-here li :target,figure:target,table:target>caption{background-color:var(--color-highlight-on-target)}dt:target{background-color:var(--color-highlight-on-target)!important}.footnote-reference:target,.footnote>dt:target+dd{background-color:var(--color-highlight-on-target)}.guilabel{background-color:var(--color-guilabel-background);border:1px solid var(--color-guilabel-border);border-radius:.5em;color:var(--color-guilabel-text);font-size:.9em;padding:0 .3em}footer{display:flex;flex-direction:column;font-size:var(--font-size--small);margin-top:2rem}.bottom-of-page{align-items:center;border-top:1px solid var(--color-background-border);color:var(--color-foreground-secondary);display:flex;justify-content:space-between;line-height:1.5;margin-top:1rem;padding-bottom:1rem;padding-top:1rem}@media(max-width:46em){.bottom-of-page{flex-direction:column-reverse;gap:.25rem;text-align:center}}.bottom-of-page .left-details{font-size:var(--font-size--small)}.bottom-of-page .right-details{display:flex;flex-direction:column;gap:.25rem;text-align:right}.bottom-of-page .icons{display:flex;font-size:1rem;gap:.25rem;justify-content:flex-end}.bottom-of-page .icons a{text-decoration:none}.bottom-of-page .icons img,.bottom-of-page .icons svg{font-size:1.125rem;height:1em;width:1em}.related-pages a{align-items:center;display:flex;text-decoration:none}.related-pages a:hover .page-info .title{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}.related-pages a svg.furo-related-icon,.related-pages a svg.furo-related-icon>use{color:var(--color-foreground-border);flex-shrink:0;height:.75rem;margin:0 .5rem;width:.75rem}.related-pages a.next-page{clear:right;float:right;max-width:50%;text-align:right}.related-pages a.prev-page{clear:left;float:left;max-width:50%}.related-pages a.prev-page svg{transform:rotate(180deg)}.page-info{display:flex;flex-direction:column;overflow-wrap:anywhere}.next-page .page-info{align-items:flex-end}.page-info .context{align-items:center;color:var(--color-foreground-muted);display:flex;font-size:var(--font-size--small);padding-bottom:.1rem;text-decoration:none}ul.search{list-style:none;padding-left:0}ul.search li{border-bottom:1px solid var(--color-background-border);padding:1rem 0}[role=main] .highlighted{background-color:var(--color-highlighted-background);color:var(--color-highlighted-text)}.sidebar-brand{display:flex;flex-direction:column;flex-shrink:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none}.sidebar-brand-text{color:var(--color-sidebar-brand-text);font-size:1.5rem;overflow-wrap:break-word}.sidebar-brand-text,.sidebar-logo-container{margin:var(--sidebar-item-spacing-vertical) 0}.sidebar-logo{display:block;margin:0 auto;max-width:100%}.sidebar-search-container{align-items:center;background:var(--color-sidebar-search-background);display:flex;margin-top:var(--sidebar-search-space-above);position:relative}.sidebar-search-container:focus-within,.sidebar-search-container:hover{background:var(--color-sidebar-search-background--focus)}.sidebar-search-container:before{background-color:var(--color-sidebar-search-icon);content:"";height:var(--sidebar-search-icon-size);left:var(--sidebar-item-spacing-horizontal);-webkit-mask-image:var(--icon-search);mask-image:var(--icon-search);position:absolute;width:var(--sidebar-search-icon-size)}.sidebar-search{background:transparent;border:none;border-bottom:1px solid var(--color-sidebar-search-border);border-top:1px solid var(--color-sidebar-search-border);box-sizing:border-box;color:var(--color-sidebar-search-foreground);padding:var(--sidebar-search-input-spacing-vertical) var(--sidebar-search-input-spacing-horizontal) var(--sidebar-search-input-spacing-vertical) calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size));width:100%;z-index:10}.sidebar-search:focus{outline:none}.sidebar-search::-moz-placeholder{font-size:var(--sidebar-search-input-font-size)}.sidebar-search::placeholder{font-size:var(--sidebar-search-input-font-size)}#searchbox .highlight-link{margin:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0;text-align:center}#searchbox .highlight-link a{color:var(--color-sidebar-search-icon);font-size:var(--font-size--small--2)}.sidebar-tree{font-size:var(--sidebar-item-font-size);margin-bottom:var(--sidebar-item-spacing-vertical);margin-top:var(--sidebar-tree-space-above)}.sidebar-tree ul{display:flex;flex-direction:column;list-style:none;margin-bottom:0;margin-top:0;padding:0}.sidebar-tree li{margin:0;position:relative}.sidebar-tree li>ul{margin-left:var(--sidebar-item-spacing-horizontal)}.sidebar-tree .icon,.sidebar-tree .reference{color:var(--color-sidebar-link-text)}.sidebar-tree .reference{box-sizing:border-box;display:inline-block;height:100%;line-height:var(--sidebar-item-line-height);overflow-wrap:anywhere;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none;width:100%}.sidebar-tree .reference:hover{background:var(--color-sidebar-item-background--hover);color:var(--color-sidebar-link-text)}.sidebar-tree .reference.external:after{color:var(--color-sidebar-link-text);content:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23607D8B' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' viewBox='0 0 24 24'%3E%3Cpath stroke='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M11 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-5M10 14 20 4M15 4h5v5'/%3E%3C/svg%3E");margin:0 .25rem;vertical-align:middle}.sidebar-tree .current-page>.reference{font-weight:700}.sidebar-tree label{align-items:center;cursor:pointer;display:flex;height:var(--sidebar-item-height);justify-content:center;position:absolute;right:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:var(--sidebar-expander-width)}.sidebar-tree .caption,.sidebar-tree :not(.caption)>.caption-text{color:var(--color-sidebar-caption-text);font-size:var(--sidebar-caption-font-size);font-weight:700;margin:var(--sidebar-caption-space-above) 0 0 0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-transform:uppercase}.sidebar-tree li.has-children>.reference{padding-right:var(--sidebar-expander-width)}.sidebar-tree .toctree-l1>.reference,.sidebar-tree .toctree-l1>label .icon{color:var(--color-sidebar-link-text--top-level)}.sidebar-tree label{background:var(--color-sidebar-item-expander-background)}.sidebar-tree label:hover{background:var(--color-sidebar-item-expander-background--hover)}.sidebar-tree .current>.reference{background:var(--color-sidebar-item-background--current)}.sidebar-tree .current>.reference:hover{background:var(--color-sidebar-item-background--hover)}.toctree-checkbox{display:none;position:absolute}.toctree-checkbox~ul{display:none}.toctree-checkbox~label .icon svg{transform:rotate(90deg)}.toctree-checkbox:checked~ul{display:block}.toctree-checkbox:checked~label .icon svg{transform:rotate(-90deg)}.toc-title-container{padding:var(--toc-title-padding);padding-top:var(--toc-spacing-vertical)}.toc-title{color:var(--color-toc-title-text);font-size:var(--toc-title-font-size);padding-left:var(--toc-spacing-horizontal);text-transform:uppercase}.no-toc{display:none}.toc-tree-container{padding-bottom:var(--toc-spacing-vertical)}.toc-tree{border-left:1px solid var(--color-background-border);font-size:var(--toc-font-size);line-height:1.3;padding-left:calc(var(--toc-spacing-horizontal) - var(--toc-item-spacing-horizontal))}.toc-tree>ul>li:first-child{padding-top:0}.toc-tree>ul>li:first-child>ul{padding-left:0}.toc-tree>ul>li:first-child>a{display:none}.toc-tree ul{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:var(--toc-item-spacing-horizontal)}.toc-tree li{padding-top:var(--toc-item-spacing-vertical)}.toc-tree li.scroll-current>.reference{color:var(--color-toc-item-text--active);font-weight:700}.toc-tree a.reference{color:var(--color-toc-item-text);overflow-wrap:anywhere;text-decoration:none}.toc-scroll{max-height:100vh;overflow-y:scroll}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here){background:rgba(255,0,0,.25);color:var(--color-problematic)}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here):before{content:"ERROR: Adding a table of contents in Furo-based documentation is unnecessary, and does not work well with existing styling. Add a 'this-will-duplicate-information-and-it-is-still-useful-here' class, if you want an escape hatch."}.text-align\:left>p{text-align:left}.text-align\:center>p{text-align:center}.text-align\:right>p{text-align:right} +/*# sourceMappingURL=furo.css.map*/ \ No newline at end of file diff --git a/_static/styles/furo.css.map b/_static/styles/furo.css.map new file mode 100644 index 0000000..3ecc371 --- /dev/null +++ b/_static/styles/furo.css.map @@ -0,0 +1 @@ +{"version":3,"file":"styles/furo.css","mappings":"AAAA,2EAA2E,CAU3E,KACE,gBAAiB,CACjB,6BACF,CASA,KACE,QACF,CAMA,KACE,aACF,CAOA,GACE,aAAc,CACd,cACF,CAUA,GACE,sBAAuB,CACvB,QAAS,CACT,gBACF,CAOA,IACE,+BAAiC,CACjC,aACF,CASA,EACE,4BACF,CAOA,YACE,kBAAmB,CACnB,yBAA0B,CAC1B,gCACF,CAMA,SAEE,kBACF,CAOA,cAGE,+BAAiC,CACjC,aACF,CAeA,QAEE,aAAc,CACd,aAAc,CACd,iBAAkB,CAClB,uBACF,CAEA,IACE,aACF,CAEA,IACE,SACF,CASA,IACE,iBACF,CAUA,sCAKE,mBAAoB,CACpB,cAAe,CACf,gBAAiB,CACjB,QACF,CAOA,aAEE,gBACF,CAOA,cAEE,mBACF,CAMA,gDAIE,yBACF,CAMA,wHAIE,iBAAkB,CAClB,SACF,CAMA,4GAIE,6BACF,CAMA,SACE,0BACF,CASA,OACE,qBAAsB,CACtB,aAAc,CACd,aAAc,CACd,cAAe,CACf,SAAU,CACV,kBACF,CAMA,SACE,uBACF,CAMA,SACE,aACF,CAOA,6BAEE,qBAAsB,CACtB,SACF,CAMA,kFAEE,WACF,CAOA,cACE,4BAA6B,CAC7B,mBACF,CAMA,yCACE,uBACF,CAOA,6BACE,yBAA0B,CAC1B,YACF,CASA,QACE,aACF,CAMA,QACE,iBACF,CAiBA,kBACE,YACF,CCvVA,aAcE,kEACE,uBAOF,WACE,iDAMF,kCACE,wBAEF,qCAEE,uBADA,uBACA,CAEF,SACE,wBAtBA,CCpBJ,iBAGE,qBAEA,sBACA,0BAFA,oBAHA,4BACA,oBAKA,6BAIA,2CAFA,mBACA,sCAFA,4BAGA,CAEF,gBACE,aCTF,KCGE,mHAEA,wGAEA,wCAAyC,CAEzC,wBAAyB,CACzB,wBAAyB,CACzB,4BAA6B,CAC7B,yBAA0B,CAC1B,2BAA4B,CAG5B,sDAAuD,CACvD,gDAAiD,CACjD,wDAAyD,CAGzD,0CAA2C,CAC3C,gDAAiD,CACjD,gDAAiD,CAKjD,gCAAiC,CACjC,sCAAuC,CAGvC,2CAA4C,CAG5C,uCAAwC,CCjCxC,+FAGA,uBAAwB,CAGxB,iCAAkC,CAClC,kCAAmC,CAEnC,+BAAgC,CAChC,sCAAuC,CACvC,sCAAuC,CACvC,qGAIA,mDAAoD,CAEpD,mCAAoC,CACpC,8CAA+C,CAC/C,gDAAiD,CACjD,kCAAmC,CACnC,6DAA8D,CAG9D,6BAA8B,CAC9B,6BAA8B,CAC9B,+BAAgC,CAChC,kCAAmC,CACnC,kCAAmC,CCPjC,+jBCYA,iqCAZF,iaCVA,8KAOA,4SAWA,4SAUA,0CACA,gEAGA,0CAGA,gEAGA,yCACA,+DAIA,4CACA,kEAGA,wCAUA,8DACA,uCAGA,4DACA,sCACA,2DAGA,4CACA,kEACA,uCAGA,6DACA,2GAGA,sHAEA,yFAEA,+CACA,+EAGA,4MAOA,gCACA,sHAIA,kCACA,uEACA,gEACA,4DACA,kEAGA,2DACA,sDACA,0CACA,8CACA,wGAGA,0BACA,iCAGA,+DACA,+BACA,sCACA,+DAEA,kGACA,oCACA,yDACA,sCL7HF,kCAEA,sDAIA,0CK2HE,kEAIA,oDACA,sDAGA,oCACA,oEAEA,0DACA,qDAIA,oDACA,6DAIA,iEAIA,2DAIA,2DAGA,4DACA,gEAIA,gEAEA,gFAEA,oNASA,qDLxKE,gFAGE,4DAIF,oEKkHF,yEAEA,6DAGA,0DAEA,uDACA,qDACA,wDAIA,6DAIA,yDACA,2DAIA,uCAGA,wCACA,sDAGA,+CAGA,6DAEA,iDACA,+DAEA,wDAEA,sEAMA,0DACA,sBACA,mEL9JI,wEAEA,iCACE,+BAMN,wEAGA,iCACE,kFAEA,uEAIF,gEACE,8BAGF,qEMvDA,sCAKA,wFAKA,iCAIA,0BAWA,iCACA,4BACA,mCAGA,+BAEA,sCACA,4BAEA,mCAEA,sCAKA,sDAIA,gCAEA,gEAQF,wCAME,sBACA,kCAKA,uBAEA,gEAIA,2BAIA,mCAEA,qCACA,iCAGE,+BACA,wEAEE,iCACA,kFAGF,6BACA,0CACF,kCAEE,8BACE,8BACA,qEAEE,sCACA,wFCnFN,iCAGF,2DAEE,4BACA,oCAGA,mIAGA,4HACE,gEAMJ,+CAGE,sBACA,yCAEF,uBAEE,sEAKA,gDACA,kEAGA,iFAGE,YAGF,EACA,4HAQF,mBACE,6BACA,mBACA,wCACA,wCACA,2CAIA,eAGA,mBAKE,mBAGA,CAJA,uCACA,iBAFF,gBACE,CAKE,mBACA,mBAGJ,oBAIF,+BAGE,kDACA,OADA,kBAGA,CAFA,gBAEA,mBACA,oBAEA,sCACA,OAGF,cAHE,WAGF,GAEE,oBACA,CAHF,gBAGE,CC9Gc,YDiHd,+CAIF,SAEE,CAPF,UACE,wBAMA,4BAEA,GAGA,uBACA,CAJA,yBAGA,CACA,iDAKA,2CAGA,2DAQA,iBACA,uCAGA,kEAKE,SAKJ,8BACE,yDACA,2BAEA,oBACA,8BAEA,yDAEE,4BAEJ,uCACE,CACA,iEAGA,CAEA,wCACE,uBACA,kDAEA,0DAEE,CAJF,oBAIE,0GAWN,aACE,CAHA,YAGA,4HASA,+CAGF,sBACE,WACA,WAQA,4BAFF,0CAEE,CARA,qCAsBA,CAdA,iBAEA,kBACE,aADF,4BACE,WAMF,2BAGF,qCAEE,CAXE,UAWF,+BAGA,uBAEA,SAEA,0CAIE,CANF,qCAEA,CAIE,2DACE,gBAIN,+CAIA,CAEA,kDAKE,CAPF,8BAEA,CAOE,YACA,CAjBI,2BAGN,CAHM,WAcJ,UAGA,CAEA,2GAIF,iCAGE,8BAIA,qBACA,oBACF,uBAOI,0CAIA,CATF,6DAKE,CALF,sBASE,qCAKF,CACE,cACA,CAFF,sBAEE,CACA,+BAEA,qBAEE,WAKN,aACE,sCAGA,mBAEA,6BAMA,kCACA,CAJA,sBACA,aAEA,CAJA,eACA,MAIA,2FAEA,UAGA,YACA,sBACE,8BAEA,CALF,aACA,WAIE,OACA,oBAEF,uBACE,WAEF,YAFE,UAEF,eAgBA,kBACE,CAhBA,qDAQF,qCAGF,CAGI,YACF,CAJF,2BAGI,CAEA,eACA,qBAGA,mEAEA,qBACA,8BAIA,kBADF,kBACE,yBAEJ,oCAGI,qDAIJ,+BAGI,oCAEA,+CAQF,4CACE,yBACF,2BAOE,sBACA,CAHA,WACA,CAFF,cACE,CAJA,YAGF,CAEE,SAEA,mBAGA,kDAEE,CAJF,cAEA,cAEE,sBAEA,mBADA,YACA,uBACA,mDACE,CADF,YACE,iDAEA,uCAEN,+DAOE,mBADF,sBACE,mBAGF,aACE,sCAIA,aADF,WACE,CAKF,SACE,CAHJ,kBAEE,CAJE,gBAEJ,CAHI,iBAMA,yFAKA,aACA,eACA,cElbJ,iBAEE,aADA,iBACA,6BAEA,kCAEA,SACA,UAIA,gCACA,CALA,SAEA,SAEA,CAJA,0EAEA,CAFA,OAKA,CAGA,mDACE,iBAGF,gCACE,CADF,UACE,aAEJ,iCAEE,CAFF,UAEE,wCAEA,WACA,WADA,UACA,CACA,4CAGA,MACA,CADA,KACA,wCACA,UAGA,CAJA,UAIA,6DAUA,0CACE,CAFF,mBAEE,wEACA,CAVA,YACA,CAMF,mBAJE,OAOA,gBAJJ,gCACE,CANE,cACA,CAHA,oBACA,CAGA,QAGJ,CAII,0BACA,CADA,UACA,wCAEJ,kBACE,0DACA,gCACE,kBACA,CADA,YACA,oEACA,2CAMF,mDAII,CALN,YACE,CANE,cAKJ,CACE,iBAII,kEACA,yCACE,kDACA,yDACE,+CACA,uBANN,CAMM,+BANN,uCACE,qDACA,4BAEE,mBADA,0CACA,CADA,qBACA,0DACE,wCACA,sGALJ,oCACA,sBACE,kBAFF,UAEE,2CACA,wFACE,cACA,kEANN,uBACE,iDACA,CADA,UACA,0DACE,wDAEE,iEACA,qEANN,sCACE,CAGE,iBAHF,gBAGE,qBACE,CAJJ,uBACA,gDACE,wDACA,6DAHF,2CACA,CADA,gBACA,eACE,CAGE,sBANN,8BACE,CAII,iBAFF,4DACA,WACE,YADF,uCACE,6EACA,2BANN,8CACE,kDACA,0CACE,8BACA,yFACE,sBACA,sFALJ,mEACA,sBACE,kEACA,6EACE,uCACA,kEALJ,qGAEE,kEACA,6EACE,uCACA,kEALJ,8CACA,uDACE,sEACA,2EACE,sCACA,iEALJ,mGACA,qCACE,oDACA,0DACE,6GACA,gDAGR,yDCrEA,sEACE,CACA,6GACE,gEACF,iGAIF,wFACE,qDAGA,mGAEE,2CAEF,4FACE,gCACF,wGACE,8DAEE,6FAIA,iJAKN,6GACE,gDAKF,yDACA,qCAGA,6BACA,kBACA,qDAKA,oCAEA,+DAGA,2CAGE,oDAIA,oEAEE,qBAGJ,wDAEE,uCAEF,kEAGA,8CAEA,uDAIF,gEAIE,6BACA,gEAIA,+CACE,0EAIF,sDAEE,+DAGF,sCACA,8BACE,oCAEJ,wBACE,4FAEE,gBAEJ,yGAGI,kBAGJ,CCnHE,2MCFF,oBAGE,wGAKA,iCACE,CADF,wBACE,8GAQA,mBCjBJ,2GAIE,mBACA,6HAMA,YACE,mIAYF,eACA,CAHF,YAGE,4FAGE,8BAKF,uBAkBE,sCACA,CADA,qBAbA,wCAIA,CALF,8BACE,CADF,gBAKE,wCACA,CAOA,kDACA,CACA,kCAKF,6BAGA,4CACE,kDACA,eAGF,cACE,aACA,iBACA,yBACA,8BACA,WAGJ,2BACE,cAGA,+BACA,CAHA,eAGA,wCACA,YACA,iBACA,uEAGA,0BACA,2CAEA,8EAGI,qBACA,CAFF,kBAEE,kBAGN,0CAGE,mCAGA,4BAIA,gEACE,qCACA,8BAEA,gBACA,+CACA,iCAEF,iCAEE,gEACA,qCAGF,8BAEE,+BAIA,yCAEE,qBADA,gBACA,yBAKF,eACA,CAFF,YACE,CACA,iBACA,qDAEA,mDCvIJ,2FAOE,iCACA,CAEA,eACA,CAHA,kBAEA,CAFA,wBAGA,8BACA,eACE,CAFF,YAEE,0BACA,8CAGA,oBACE,oCAGA,kBACE,8DAEA,iBAEN,UACE,8BAIJ,+CAEE,qDAEF,kDAIE,YAEF,CAFE,YAEF,CCpCE,mFADA,kBAKE,CAJF,IAGA,aACE,mCAGA,iDACE,+BAEJ,wBAEE,mBAMA,6CAEF,CAJE,mBAEA,CAEF,kCAGE,CARF,kBACE,CAHA,eAUA,YACA,mBACA,CADA,UACA,wCC9BF,oBDkCE,wBCnCJ,uCACE,+BACA,+DACA,sBAGA,qBCDA,6CAIE,CAPF,uBAGA,CDGE,oBACF,yDAEE,CCDE,2CAGF,CAJA,kCACE,CDJJ,YACE,CAIA,eCTF,CDKE,uBCMA,gCACE,YAEF,oCAEE,wBACA,0BAIF,iBAEA,cADF,UACE,uBAEA,iCAEA,wCAEA,6CAMA,CAYF,gCATI,4BASJ,CAZE,mCAEE,iCAUJ,4BAGE,4DADA,+BACA,CAHF,qBAGE,sCACE,OAEF,iBAHA,SAGA,iHACE,2DAKF,CANA,8EAMA,uSAEE,kBAEF,+FACE,yCCjEJ,WACA,yBAGA,uBACA,gBAEA,uCAIA,CAJA,iCAIA,uCAGA,UACE,gBACA,qBAEA,0CClBJ,gBACE,KAGF,qBACE,YAGF,CAHE,cAGF,gCAEE,mBACA,iEAEA,oCACA,wCAEA,sBACA,WAEA,CAFA,YAEA,8EAEA,mCAFA,iBAEA,6BAIA,wEAKA,sDAIE,CARF,mDAIA,CAIE,cAEF,8CAIA,oBAFE,iBAEF,8CAGE,eAEF,CAFE,YAEF,OAEE,kBAGJ,CAJI,eACA,CAFF,mBAKF,yCCjDE,oBACA,CAFA,iBAEA,uCAKE,iBACA,qCAGA,mBCZJ,CDWI,gBCXJ,6BAEE,eACA,sBAGA,eAEA,sBACA,oDACA,iGAMA,gBAFE,YAEF,8FAME,iJCnBF,YACA,gNAWE,gDAEF,iSAaE,kBACE,gHAKF,oCACE,eACF,CADE,UACF,8CACE,gDACF,wCACE,oBCxCJ,oBAEF,6BACE,QACE,kDAGF,yBACE,kDAmBA,kDAEF,CAhBA,+CAaA,CAbA,oBAaA,0FACE,CADF,gGAfF,cACE,gBACA,CAaA,0BAGA,mQACE,gBAGF,oMACE,iBACA,CAFF,eACE,CADF,gBAEE,aAGJ,iCAEE,CAFF,wCAEE,wBAUE,+VAIE,uEAHA,2BAGA,wXAKJ,iDAGF,CARM,+CACE,iDAIN,CALI,gBAQN,mHACE,gBAGF,2DACE,0EAOA,0EAGF,gBAEE,6DC/EA,kDACA,gCACA,qDAGA,qBACA,qDCFA,cACA,eAEA,yBAGF,sBAEE,iBACA,sNAWA,iBACE,kBACA,wRAgBA,kBAEA,iOAgBA,uCACE,uEAEA,kBAEF,qUAuBE,iDAIJ,CACA,geCxFF,4BAEE,CAQA,6JACA,iDAIA,sEAGA,mDAOF,iDAGE,4DAIA,8CACA,qDAEE,eAFF,cAEE,oBAEF,uBAFE,kCAGA,eACA,iBACA,mBAIA,mDACA,CAHA,uCAEA,CAJA,0CACA,CAIA,gBAJA,gBACA,oBADA,gBAIA,wBAEJ,gBAGE,6BACA,YAHA,iBAGA,gCACA,iEAEA,6CACA,sDACA,0BADA,wBACA,0BACA,oIAIA,mBAFA,YAEA,qBACA,0CAIE,uBAEF,CAHA,yBACE,CAEF,iDACE,mFAKJ,oCACE,CANE,aAKJ,CACE,qEAIA,YAFA,WAEA,CAHA,aACA,CAEA,gBACE,4BACA,sBADA,aACA,gCAMF,oCACA,yDACA,2CAEA,qBAGE,kBAEA,CACA,mCAIF,CARE,YACA,CAOF,iCAEE,CAPA,oBACA,CAQA,oBACE,uDAEJ,sDAGA,CAHA,cAGA,0BACE,oDAIA,oCACA,4BACA,sBAGA,cAEA,oFAGA,sBAEA,yDACE,CAIF,iBAJE,wBAIF,6CAHE,6CAKA,eACA,aACA,CADA,cACA,yCAGJ,kBACE,CAKA,iDAEA,CARF,aACE,4CAGA,kBAIA,wEAGA,wDAGA,kCAOA,iDAGA,CAPF,WAEE,sCAEA,CAJF,2CACE,CAMA,qCACA,+BARF,kBACE,qCAOA,iBAsBA,sBACE,CAvBF,WAKA,CACE,0DAIF,CALA,uDACE,CANF,sBAqBA,4CACA,CALA,gRAIA,YAEE,6CAEN,mCAEE,+CASA,6EAIA,4BChNA,SDmNA,qFCnNA,gDACA,sCAGA,qCACA,sDACA,CAKA,kDAGA,CARA,0CAQA,kBAGA,YACA,sBACA,iBAFA,gBADF,YACE,CAHA,SAKA,kBAEA,SAFA,iBAEA,uEAGA,CAEE,6CAFF,oCAgBI,CAdF,yBACE,qBACF,CAGF,oBACE,CAIF,WACE,CALA,2CAGA,uBACF,CACE,mFAGE,CALF,qBAEA,UAGE,gCAIF,sDAEA,CALE,oCAKF,yCC7CJ,oCACE,CD+CA,yXAQE,sCCrDJ,wCAGA,oCACE","sources":["webpack:///./node_modules/normalize.css/normalize.css","webpack:///./src/furo/assets/styles/base/_print.sass","webpack:///./src/furo/assets/styles/base/_screen-readers.sass","webpack:///./src/furo/assets/styles/base/_theme.sass","webpack:///./src/furo/assets/styles/variables/_fonts.scss","webpack:///./src/furo/assets/styles/variables/_spacing.scss","webpack:///./src/furo/assets/styles/variables/_icons.scss","webpack:///./src/furo/assets/styles/variables/_admonitions.scss","webpack:///./src/furo/assets/styles/variables/_colors.scss","webpack:///./src/furo/assets/styles/base/_typography.sass","webpack:///./src/furo/assets/styles/_scaffold.sass","webpack:///./src/furo/assets/styles/variables/_layout.scss","webpack:///./src/furo/assets/styles/content/_admonitions.sass","webpack:///./src/furo/assets/styles/content/_api.sass","webpack:///./src/furo/assets/styles/content/_blocks.sass","webpack:///./src/furo/assets/styles/content/_captions.sass","webpack:///./src/furo/assets/styles/content/_code.sass","webpack:///./src/furo/assets/styles/content/_footnotes.sass","webpack:///./src/furo/assets/styles/content/_images.sass","webpack:///./src/furo/assets/styles/content/_indexes.sass","webpack:///./src/furo/assets/styles/content/_lists.sass","webpack:///./src/furo/assets/styles/content/_math.sass","webpack:///./src/furo/assets/styles/content/_misc.sass","webpack:///./src/furo/assets/styles/content/_rubrics.sass","webpack:///./src/furo/assets/styles/content/_sidebar.sass","webpack:///./src/furo/assets/styles/content/_tables.sass","webpack:///./src/furo/assets/styles/content/_target.sass","webpack:///./src/furo/assets/styles/content/_gui-labels.sass","webpack:///./src/furo/assets/styles/components/_footer.sass","webpack:///./src/furo/assets/styles/components/_sidebar.sass","webpack:///./src/furo/assets/styles/components/_table_of_contents.sass","webpack:///./src/furo/assets/styles/_shame.sass"],"sourcesContent":["/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n","// This file contains styles for managing print media.\n\n////////////////////////////////////////////////////////////////////////////////\n// Hide elements not relevant to print media.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n // Hide icon container.\n .content-icon-container\n display: none !important\n\n // Hide showing header links if hovering over when printing.\n .headerlink\n display: none !important\n\n // Hide mobile header.\n .mobile-header\n display: none !important\n\n // Hide navigation links.\n .related-pages\n display: none !important\n\n////////////////////////////////////////////////////////////////////////////////\n// Tweaks related to decolorization.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n // Apply a border around code which no longer have a color background.\n .highlight\n border: 0.1pt solid var(--color-foreground-border)\n\n////////////////////////////////////////////////////////////////////////////////\n// Avoid page break in some relevant cases.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n ul, ol, dl, a, table, pre, blockquote, p\n page-break-inside: avoid\n\n h1, h2, h3, h4, h5, h6, img, figure, caption\n page-break-inside: avoid\n page-break-after: avoid\n\n ul, ol, dl\n page-break-before: avoid\n",".visually-hidden\n position: absolute !important\n width: 1px !important\n height: 1px !important\n padding: 0 !important\n margin: -1px !important\n overflow: hidden !important\n clip: rect(0,0,0,0) !important\n white-space: nowrap !important\n border: 0 !important\n color: var(--color-foreground-primary)\n background: var(--color-background-primary)\n\n:-moz-focusring\n outline: auto\n","// This file serves as the \"skeleton\" of the theming logic.\n//\n// This contains the bulk of the logic for handling dark mode, color scheme\n// toggling and the handling of color-scheme-specific hiding of elements.\n\nbody\n @include fonts\n @include spacing\n @include icons\n @include admonitions\n @include default-admonition(#651fff, \"abstract\")\n @include default-topic(#14B8A6, \"pencil\")\n\n @include colors\n\n.only-light\n display: block !important\nhtml body .only-dark\n display: none !important\n\n// Ignore dark-mode hints if print media.\n@media not print\n // Enable dark-mode, if requested.\n body[data-theme=\"dark\"]\n @include colors-dark\n\n html & .only-light\n display: none !important\n .only-dark\n display: block !important\n\n // Enable dark mode, unless explicitly told to avoid.\n @media (prefers-color-scheme: dark)\n body:not([data-theme=\"light\"])\n @include colors-dark\n\n html & .only-light\n display: none !important\n .only-dark\n display: block !important\n\n//\n// Theme toggle presentation\n//\nbody[data-theme=\"auto\"]\n .theme-toggle svg.theme-icon-when-auto-light\n display: block\n\n @media (prefers-color-scheme: dark)\n .theme-toggle svg.theme-icon-when-auto-dark\n display: block\n .theme-toggle svg.theme-icon-when-auto-light\n display: none\n\nbody[data-theme=\"dark\"]\n .theme-toggle svg.theme-icon-when-dark\n display: block\n\nbody[data-theme=\"light\"]\n .theme-toggle svg.theme-icon-when-light\n display: block\n","// Fonts used by this theme.\n//\n// There are basically two things here -- using the system font stack and\n// defining sizes for various elements in %ages. We could have also used `em`\n// but %age is easier to reason about for me.\n\n@mixin fonts {\n // These are adapted from https://systemfontstack.com/\n --font-stack: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji;\n --font-stack--monospace: \"SFMono-Regular\", Menlo, Consolas, Monaco,\n Liberation Mono, Lucida Console, monospace;\n --font-stack--headings: var(--font-stack);\n\n --font-size--normal: 100%;\n --font-size--small: 87.5%;\n --font-size--small--2: 81.25%;\n --font-size--small--3: 75%;\n --font-size--small--4: 62.5%;\n\n // Sidebar\n --sidebar-caption-font-size: var(--font-size--small--2);\n --sidebar-item-font-size: var(--font-size--small);\n --sidebar-search-input-font-size: var(--font-size--small);\n\n // Table of Contents\n --toc-font-size: var(--font-size--small--3);\n --toc-font-size--mobile: var(--font-size--normal);\n --toc-title-font-size: var(--font-size--small--4);\n\n // Admonitions\n //\n // These aren't defined in terms of %ages, since nesting these is permitted.\n --admonition-font-size: 0.8125rem;\n --admonition-title-font-size: 0.8125rem;\n\n // Code\n --code-font-size: var(--font-size--small--2);\n\n // API\n --api-font-size: var(--font-size--small);\n}\n","// Spacing for various elements on the page\n//\n// If the user wants to tweak things in a certain way, they are permitted to.\n// They also have to deal with the consequences though!\n\n@mixin spacing {\n // Header!\n --header-height: calc(\n var(--sidebar-item-line-height) + 4 * #{var(--sidebar-item-spacing-vertical)}\n );\n --header-padding: 0.5rem;\n\n // Sidebar\n --sidebar-tree-space-above: 1.5rem;\n --sidebar-caption-space-above: 1rem;\n\n --sidebar-item-line-height: 1rem;\n --sidebar-item-spacing-vertical: 0.5rem;\n --sidebar-item-spacing-horizontal: 1rem;\n --sidebar-item-height: calc(\n var(--sidebar-item-line-height) + 2 *#{var(--sidebar-item-spacing-vertical)}\n );\n\n --sidebar-expander-width: var(--sidebar-item-height); // be square\n\n --sidebar-search-space-above: 0.5rem;\n --sidebar-search-input-spacing-vertical: 0.5rem;\n --sidebar-search-input-spacing-horizontal: 0.5rem;\n --sidebar-search-input-height: 1rem;\n --sidebar-search-icon-size: var(--sidebar-search-input-height);\n\n // Table of Contents\n --toc-title-padding: 0.25rem 0;\n --toc-spacing-vertical: 1.5rem;\n --toc-spacing-horizontal: 1.5rem;\n --toc-item-spacing-vertical: 0.4rem;\n --toc-item-spacing-horizontal: 1rem;\n}\n","// Expose theme icons as CSS variables.\n\n$icons: (\n // Adapted from tabler-icons\n // url: https://tablericons.com/\n \"search\":\n url('data:image/svg+xml;charset=utf-8,'),\n // Factored out from mkdocs-material on 24-Aug-2020.\n // url: https://squidfunk.github.io/mkdocs-material/reference/admonitions/\n \"pencil\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"abstract\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"info\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"flame\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"question\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"warning\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"failure\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"spark\":\n url('data:image/svg+xml;charset=utf-8,')\n);\n\n@mixin icons {\n @each $name, $glyph in $icons {\n --icon-#{$name}: #{$glyph};\n }\n}\n","// Admonitions\n\n// Structure of these is:\n// admonition-class: color \"icon-name\";\n//\n// The colors are translated into CSS variables below. The icons are\n// used directly in the main declarations to set the `mask-image` in\n// the title.\n\n// prettier-ignore\n$admonitions: (\n // Each of these has an reST directives for it.\n \"caution\": #ff9100 \"spark\",\n \"warning\": #ff9100 \"warning\",\n \"danger\": #ff5252 \"spark\",\n \"attention\": #ff5252 \"warning\",\n \"error\": #ff5252 \"failure\",\n \"hint\": #00c852 \"question\",\n \"tip\": #00c852 \"info\",\n \"important\": #00bfa5 \"flame\",\n \"note\": #00b0ff \"pencil\",\n \"seealso\": #448aff \"info\",\n \"admonition-todo\": #808080 \"pencil\"\n);\n\n@mixin default-admonition($color, $icon-name) {\n --color-admonition-title: #{$color};\n --color-admonition-title-background: #{rgba($color, 0.2)};\n\n --icon-admonition-default: var(--icon-#{$icon-name});\n}\n\n@mixin default-topic($color, $icon-name) {\n --color-topic-title: #{$color};\n --color-topic-title-background: #{rgba($color, 0.2)};\n\n --icon-topic-default: var(--icon-#{$icon-name});\n}\n\n@mixin admonitions {\n @each $name, $values in $admonitions {\n --color-admonition-title--#{$name}: #{nth($values, 1)};\n --color-admonition-title-background--#{$name}: #{rgba(\n nth($values, 1),\n 0.2\n )};\n }\n}\n","// Colors used throughout this theme.\n//\n// The aim is to give the user more control. Thus, instead of hard-coding colors\n// in various parts of the stylesheet, the approach taken is to define all\n// colors as CSS variables and reusing them in all the places.\n//\n// `colors-dark` depends on `colors` being included at a lower specificity.\n\n@mixin colors {\n --color-problematic: #b30000;\n\n // Base Colors\n --color-foreground-primary: black; // for main text and headings\n --color-foreground-secondary: #5a5c63; // for secondary text\n --color-foreground-muted: #6b6f76; // for muted text\n --color-foreground-border: #878787; // for content borders\n\n --color-background-primary: white; // for content\n --color-background-secondary: #f8f9fb; // for navigation + ToC\n --color-background-hover: #efeff4ff; // for navigation-item hover\n --color-background-hover--transparent: #efeff400;\n --color-background-border: #eeebee; // for UI borders\n --color-background-item: #ccc; // for \"background\" items (eg: copybutton)\n\n // Announcements\n --color-announcement-background: #000000dd;\n --color-announcement-text: #eeebee;\n\n // Brand colors\n --color-brand-primary: #0a4bff;\n --color-brand-content: #2757dd;\n --color-brand-visited: #872ee0;\n\n // API documentation\n --color-api-background: var(--color-background-hover--transparent);\n --color-api-background-hover: var(--color-background-hover);\n --color-api-overall: var(--color-foreground-secondary);\n --color-api-name: var(--color-problematic);\n --color-api-pre-name: var(--color-problematic);\n --color-api-paren: var(--color-foreground-secondary);\n --color-api-keyword: var(--color-foreground-primary);\n\n --color-api-added: #21632c;\n --color-api-added-border: #38a84d;\n --color-api-changed: #046172;\n --color-api-changed-border: #06a1bc;\n --color-api-deprecated: #605706;\n --color-api-deprecated-border: #f0d90f;\n --color-api-removed: #b30000;\n --color-api-removed-border: #ff5c5c;\n\n --color-highlight-on-target: #ffffcc;\n\n // Inline code background\n --color-inline-code-background: var(--color-background-secondary);\n\n // Highlighted text (search)\n --color-highlighted-background: #ddeeff;\n --color-highlighted-text: var(--color-foreground-primary);\n\n // GUI Labels\n --color-guilabel-background: #ddeeff80;\n --color-guilabel-border: #bedaf580;\n --color-guilabel-text: var(--color-foreground-primary);\n\n // Admonitions!\n --color-admonition-background: transparent;\n\n //////////////////////////////////////////////////////////////////////////////\n // Everything below this should be one of:\n // - var(...)\n // - *-gradient(...)\n // - special literal values (eg: transparent, none)\n //////////////////////////////////////////////////////////////////////////////\n\n // Tables\n --color-table-header-background: var(--color-background-secondary);\n --color-table-border: var(--color-background-border);\n\n // Cards\n --color-card-border: var(--color-background-secondary);\n --color-card-background: transparent;\n --color-card-marginals-background: var(--color-background-secondary);\n\n // Header\n --color-header-background: var(--color-background-primary);\n --color-header-border: var(--color-background-border);\n --color-header-text: var(--color-foreground-primary);\n\n // Sidebar (left)\n --color-sidebar-background: var(--color-background-secondary);\n --color-sidebar-background-border: var(--color-background-border);\n\n --color-sidebar-brand-text: var(--color-foreground-primary);\n --color-sidebar-caption-text: var(--color-foreground-muted);\n --color-sidebar-link-text: var(--color-foreground-secondary);\n --color-sidebar-link-text--top-level: var(--color-brand-primary);\n\n --color-sidebar-item-background: var(--color-sidebar-background);\n --color-sidebar-item-background--current: var(\n --color-sidebar-item-background\n );\n --color-sidebar-item-background--hover: linear-gradient(\n 90deg,\n var(--color-background-hover--transparent) 0%,\n var(--color-background-hover) var(--sidebar-item-spacing-horizontal),\n var(--color-background-hover) 100%\n );\n\n --color-sidebar-item-expander-background: transparent;\n --color-sidebar-item-expander-background--hover: var(\n --color-background-hover\n );\n\n --color-sidebar-search-text: var(--color-foreground-primary);\n --color-sidebar-search-background: var(--color-background-secondary);\n --color-sidebar-search-background--focus: var(--color-background-primary);\n --color-sidebar-search-border: var(--color-background-border);\n --color-sidebar-search-icon: var(--color-foreground-muted);\n\n // Table of Contents (right)\n --color-toc-background: var(--color-background-primary);\n --color-toc-title-text: var(--color-foreground-muted);\n --color-toc-item-text: var(--color-foreground-secondary);\n --color-toc-item-text--hover: var(--color-foreground-primary);\n --color-toc-item-text--active: var(--color-brand-primary);\n\n // Actual page contents\n --color-content-foreground: var(--color-foreground-primary);\n --color-content-background: transparent;\n\n // Links\n --color-link: var(--color-brand-content);\n --color-link-underline: var(--color-background-border);\n --color-link--hover: var(--color-brand-content);\n --color-link-underline--hover: var(--color-foreground-border);\n\n --color-link--visited: var(--color-brand-visited);\n --color-link-underline--visited: var(--color-background-border);\n --color-link--visited--hover: var(--color-brand-visited);\n --color-link-underline--visited--hover: var(--color-foreground-border);\n}\n\n@mixin colors-dark {\n --color-problematic: #ee5151;\n\n // Base Colors\n --color-foreground-primary: #cfd0d0; // for main text and headings\n --color-foreground-secondary: #9ca0a5; // for secondary text\n --color-foreground-muted: #81868d; // for muted text\n --color-foreground-border: #666666; // for content borders\n\n --color-background-primary: #131416; // for content\n --color-background-secondary: #1a1c1e; // for navigation + ToC\n --color-background-hover: #1e2124ff; // for navigation-item hover\n --color-background-hover--transparent: #1e212400;\n --color-background-border: #303335; // for UI borders\n --color-background-item: #444; // for \"background\" items (eg: copybutton)\n\n // Announcements\n --color-announcement-background: #000000dd;\n --color-announcement-text: #eeebee;\n\n // Brand colors\n --color-brand-primary: #3d94ff;\n --color-brand-content: #5ca5ff;\n --color-brand-visited: #b27aeb;\n\n // Highlighted text (search)\n --color-highlighted-background: #083563;\n\n // GUI Labels\n --color-guilabel-background: #08356380;\n --color-guilabel-border: #13395f80;\n\n // API documentation\n --color-api-keyword: var(--color-foreground-secondary);\n --color-highlight-on-target: #333300;\n\n --color-api-added: #3db854;\n --color-api-added-border: #267334;\n --color-api-changed: #09b0ce;\n --color-api-changed-border: #056d80;\n --color-api-deprecated: #b1a10b;\n --color-api-deprecated-border: #6e6407;\n --color-api-removed: #ff7575;\n --color-api-removed-border: #b03b3b;\n\n // Admonitions\n --color-admonition-background: #18181a;\n\n // Cards\n --color-card-border: var(--color-background-secondary);\n --color-card-background: #18181a;\n --color-card-marginals-background: var(--color-background-hover);\n}\n","// This file contains the styling for making the content throughout the page,\n// including fonts, paragraphs, headings and spacing among these elements.\n\nbody\n font-family: var(--font-stack)\npre,\ncode,\nkbd,\nsamp\n font-family: var(--font-stack--monospace)\n\n// Make fonts look slightly nicer.\nbody\n -webkit-font-smoothing: antialiased\n -moz-osx-font-smoothing: grayscale\n\n// Line height from Bootstrap 4.1\narticle\n line-height: 1.5\n\n//\n// Headings\n//\nh1,\nh2,\nh3,\nh4,\nh5,\nh6\n line-height: 1.25\n font-family: var(--font-stack--headings)\n font-weight: bold\n\n border-radius: 0.5rem\n margin-top: 0.5rem\n margin-bottom: 0.5rem\n margin-left: -0.5rem\n margin-right: -0.5rem\n padding-left: 0.5rem\n padding-right: 0.5rem\n\n + p\n margin-top: 0\n\nh1\n font-size: 2.5em\n margin-top: 1.75rem\n margin-bottom: 1rem\nh2\n font-size: 2em\n margin-top: 1.75rem\nh3\n font-size: 1.5em\nh4\n font-size: 1.25em\nh5\n font-size: 1.125em\nh6\n font-size: 1em\n\nsmall\n opacity: 75%\n font-size: 80%\n\n// Paragraph\np\n margin-top: 0.5rem\n margin-bottom: 0.75rem\n\n// Horizontal rules\nhr.docutils\n height: 1px\n padding: 0\n margin: 2rem 0\n background-color: var(--color-background-border)\n border: 0\n\n.centered\n text-align: center\n\n// Links\na\n text-decoration: underline\n\n color: var(--color-link)\n text-decoration-color: var(--color-link-underline)\n\n &:visited\n color: var(--color-link--visited)\n text-decoration-color: var(--color-link-underline--visited)\n &:hover\n color: var(--color-link--visited--hover)\n text-decoration-color: var(--color-link-underline--visited--hover)\n\n &:hover\n color: var(--color-link--hover)\n text-decoration-color: var(--color-link-underline--hover)\n &.muted-link\n color: inherit\n &:hover\n color: var(--color-link--hover)\n text-decoration-color: var(--color-link-underline--hover)\n &:visited\n color: var(--color-link--visited--hover)\n text-decoration-color: var(--color-link-underline--visited--hover)\n","// This file contains the styles for the overall layouting of the documentation\n// skeleton, including the responsive changes as well as sidebar toggles.\n//\n// This is implemented as a mobile-last design, which isn't ideal, but it is\n// reasonably good-enough and I got pretty tired by the time I'd finished this\n// to move the rules around to fix this. Shouldn't take more than 3-4 hours,\n// if you know what you're doing tho.\n\n// HACK: Not all browsers account for the scrollbar width in media queries.\n// This results in horizontal scrollbars in the breakpoint where we go\n// from displaying everything to hiding the ToC. We accomodate for this by\n// adding a bit of padding to the TOC drawer, disabling the horizontal\n// scrollbar and allowing the scrollbars to cover the padding.\n// https://www.456bereastreet.com/archive/201301/media_query_width_and_vertical_scrollbars/\n\n// HACK: Always having the scrollbar visible, prevents certain browsers from\n// causing the content to stutter horizontally between taller-than-viewport and\n// not-taller-than-viewport pages.\n\nhtml\n overflow-x: hidden\n overflow-y: scroll\n scroll-behavior: smooth\n\n.sidebar-scroll, .toc-scroll, article[role=main] *\n // Override Firefox scrollbar style\n scrollbar-width: thin\n scrollbar-color: var(--color-foreground-border) transparent\n\n // Override Chrome scrollbar styles\n &::-webkit-scrollbar\n width: 0.25rem\n height: 0.25rem\n &::-webkit-scrollbar-thumb\n background-color: var(--color-foreground-border)\n border-radius: 0.125rem\n\n//\n// Overalls\n//\nhtml,\nbody\n height: 100%\n color: var(--color-foreground-primary)\n background: var(--color-background-primary)\n\n.skip-to-content\n position: fixed\n padding: 1rem\n border-radius: 1rem\n left: 0.25rem\n top: 0.25rem\n z-index: 40\n background: var(--color-background-primary)\n color: var(--color-foreground-primary)\n\n transform: translateY(-200%)\n transition: transform 300ms ease-in-out\n\n &:focus-within\n transform: translateY(0%)\n\narticle\n color: var(--color-content-foreground)\n background: var(--color-content-background)\n overflow-wrap: break-word\n\n.page\n display: flex\n // fill the viewport for pages with little content.\n min-height: 100%\n\n.mobile-header\n width: 100%\n height: var(--header-height)\n background-color: var(--color-header-background)\n color: var(--color-header-text)\n border-bottom: 1px solid var(--color-header-border)\n\n // Looks like sub-script/super-script have this, and we need this to\n // be \"on top\" of those.\n z-index: 10\n\n // We don't show the header on large screens.\n display: none\n\n // Add shadow when scrolled\n &.scrolled\n border-bottom: none\n box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2)\n\n .header-center\n a\n color: var(--color-header-text)\n text-decoration: none\n\n.main\n display: flex\n flex: 1\n\n// Sidebar (left) also covers the entire left portion of screen.\n.sidebar-drawer\n box-sizing: border-box\n\n border-right: 1px solid var(--color-sidebar-background-border)\n background: var(--color-sidebar-background)\n\n display: flex\n justify-content: flex-end\n // These next two lines took me two days to figure out.\n width: calc((100% - #{$full-width}) / 2 + #{$sidebar-width})\n min-width: $sidebar-width\n\n// Scroll-along sidebars\n.sidebar-container,\n.toc-drawer\n box-sizing: border-box\n width: $sidebar-width\n\n.toc-drawer\n background: var(--color-toc-background)\n // See HACK described on top of this document\n padding-right: 1rem\n\n.sidebar-sticky,\n.toc-sticky\n position: sticky\n top: 0\n height: min(100%, 100vh)\n height: 100vh\n\n display: flex\n flex-direction: column\n\n.sidebar-scroll,\n.toc-scroll\n flex-grow: 1\n flex-shrink: 1\n\n overflow: auto\n scroll-behavior: smooth\n\n// Central items.\n.content\n padding: 0 $content-padding\n width: $content-width\n\n display: flex\n flex-direction: column\n justify-content: space-between\n\n.icon\n display: inline-block\n height: 1rem\n width: 1rem\n svg\n width: 100%\n height: 100%\n\n//\n// Accommodate announcement banner\n//\n.announcement\n background-color: var(--color-announcement-background)\n color: var(--color-announcement-text)\n\n height: var(--header-height)\n display: flex\n align-items: center\n overflow-x: auto\n & + .page\n min-height: calc(100% - var(--header-height))\n\n.announcement-content\n box-sizing: border-box\n padding: 0.5rem\n min-width: 100%\n white-space: nowrap\n text-align: center\n\n a\n color: var(--color-announcement-text)\n text-decoration-color: var(--color-announcement-text)\n\n &:hover\n color: var(--color-announcement-text)\n text-decoration-color: var(--color-link--hover)\n\n////////////////////////////////////////////////////////////////////////////////\n// Toggles for theme\n////////////////////////////////////////////////////////////////////////////////\n.no-js .theme-toggle-container // don't show theme toggle if there's no JS\n display: none\n\n.theme-toggle-container\n display: flex\n\n.theme-toggle\n display: flex\n cursor: pointer\n border: none\n padding: 0\n background: transparent\n\n.theme-toggle svg\n height: 1.25rem\n width: 1.25rem\n color: var(--color-foreground-primary)\n display: none\n\n.theme-toggle-header\n display: flex\n align-items: center\n justify-content: center\n\n////////////////////////////////////////////////////////////////////////////////\n// Toggles for elements\n////////////////////////////////////////////////////////////////////////////////\n.toc-overlay-icon, .nav-overlay-icon\n display: none\n cursor: pointer\n\n .icon\n color: var(--color-foreground-secondary)\n height: 1.5rem\n width: 1.5rem\n\n.toc-header-icon, .nav-overlay-icon\n // for when we set display: flex\n justify-content: center\n align-items: center\n\n.toc-content-icon\n height: 1.5rem\n width: 1.5rem\n\n.content-icon-container\n float: right\n display: flex\n margin-top: 1.5rem\n margin-left: 1rem\n margin-bottom: 1rem\n gap: 0.5rem\n\n .edit-this-page, .view-this-page\n svg\n color: inherit\n height: 1.25rem\n width: 1.25rem\n\n.sidebar-toggle\n position: absolute\n display: none\n// \n.sidebar-toggle[name=\"__toc\"]\n left: 20px\n.sidebar-toggle:checked\n left: 40px\n// \n\n.overlay\n position: fixed\n top: 0\n width: 0\n height: 0\n\n transition: width 0ms, height 0ms, opacity 250ms ease-out\n\n opacity: 0\n background-color: rgba(0, 0, 0, 0.54)\n.sidebar-overlay\n z-index: 20\n.toc-overlay\n z-index: 40\n\n// Keep things on top and smooth.\n.sidebar-drawer\n z-index: 30\n transition: left 250ms ease-in-out\n.toc-drawer\n z-index: 50\n transition: right 250ms ease-in-out\n\n// Show the Sidebar\n#__navigation:checked\n & ~ .sidebar-overlay\n width: 100%\n height: 100%\n opacity: 1\n & ~ .page\n .sidebar-drawer\n top: 0\n left: 0\n // Show the toc sidebar\n#__toc:checked\n & ~ .toc-overlay\n width: 100%\n height: 100%\n opacity: 1\n & ~ .page\n .toc-drawer\n top: 0\n right: 0\n\n////////////////////////////////////////////////////////////////////////////////\n// Back to top\n////////////////////////////////////////////////////////////////////////////////\n.back-to-top\n text-decoration: none\n\n display: none\n position: fixed\n left: 0\n top: 1rem\n padding: 0.5rem\n padding-right: 0.75rem\n border-radius: 1rem\n font-size: 0.8125rem\n\n background: var(--color-background-primary)\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), #6b728080 0px 0px 1px 0px\n\n z-index: 10\n\n margin-left: 50%\n transform: translateX(-50%)\n svg\n height: 1rem\n width: 1rem\n fill: currentColor\n display: inline-block\n\n span\n margin-left: 0.25rem\n\n .show-back-to-top &\n display: flex\n align-items: center\n\n////////////////////////////////////////////////////////////////////////////////\n// Responsive layouting\n////////////////////////////////////////////////////////////////////////////////\n// Make things a bit bigger on bigger screens.\n@media (min-width: $full-width + $sidebar-width)\n html\n font-size: 110%\n\n@media (max-width: $full-width)\n // Collapse \"toc\" into the icon.\n .toc-content-icon\n display: flex\n .toc-drawer\n position: fixed\n height: 100vh\n top: 0\n right: -$sidebar-width\n border-left: 1px solid var(--color-background-muted)\n .toc-tree\n border-left: none\n font-size: var(--toc-font-size--mobile)\n\n // Accomodate for a changed content width.\n .sidebar-drawer\n width: calc((100% - #{$full-width - $sidebar-width}) / 2 + #{$sidebar-width})\n\n@media (max-width: $content-padded-width + $sidebar-width)\n // Center the page\n .content\n margin-left: auto\n margin-right: auto\n padding: 0 $content-padding--small\n\n@media (max-width: $content-padded-width--small + $sidebar-width)\n // Collapse \"navigation\".\n .nav-overlay-icon\n display: flex\n .sidebar-drawer\n position: fixed\n height: 100vh\n width: $sidebar-width\n\n top: 0\n left: -$sidebar-width\n\n // Swap which icon is visible.\n .toc-header-icon, .theme-toggle-header\n display: flex\n .toc-content-icon, .theme-toggle-content\n display: none\n\n // Show the header.\n .mobile-header\n position: sticky\n top: 0\n display: flex\n justify-content: space-between\n align-items: center\n\n .header-left,\n .header-right\n display: flex\n height: var(--header-height)\n padding: 0 var(--header-padding)\n label\n height: 100%\n width: 100%\n user-select: none\n\n .nav-overlay-icon .icon,\n .theme-toggle svg\n height: 1.5rem\n width: 1.5rem\n\n // Add a scroll margin for the content\n :target\n scroll-margin-top: calc(var(--header-height) + 2.5rem)\n\n // Show back-to-top below the header\n .back-to-top\n top: calc(var(--header-height) + 0.5rem)\n\n // Accommodate for the header.\n .page\n flex-direction: column\n justify-content: center\n\n@media (max-width: $content-width + 2* $content-padding--small)\n // Content should respect window limits.\n .content\n width: 100%\n overflow-x: auto\n\n@media (max-width: $content-width)\n article[role=main] aside.sidebar\n float: none\n width: 100%\n margin: 1rem 0\n","// Overall Layout Variables\n//\n// Because CSS variables can't be used in media queries. The fact that this\n// makes the layout non-user-configurable is a good thing.\n$content-padding: 3em;\n$content-padding--small: 1em;\n$content-width: 46em;\n$sidebar-width: 15em;\n$content-padded-width: $content-width + 2 * $content-padding;\n$content-padded-width--small: $content-width + 2 * $content-padding--small;\n$full-width: $content-padded-width + 2 * $sidebar-width;\n","//\n// The design here is strongly inspired by mkdocs-material.\n.admonition, .topic\n margin: 1rem auto\n padding: 0 0.5rem 0.5rem 0.5rem\n\n background: var(--color-admonition-background)\n\n border-radius: 0.2rem\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n font-size: var(--admonition-font-size)\n\n overflow: hidden\n page-break-inside: avoid\n\n // First element should have no margin, since the title has it.\n > :nth-child(2)\n margin-top: 0\n\n // Last item should have no margin, since we'll control that w/ padding\n > :last-child\n margin-bottom: 0\n\n.admonition p.admonition-title,\np.topic-title\n position: relative\n margin: 0 -0.5rem 0.5rem\n padding-left: 2rem\n padding-right: .5rem\n padding-top: .4rem\n padding-bottom: .4rem\n\n font-weight: 500\n font-size: var(--admonition-title-font-size)\n line-height: 1.3\n\n // Our fancy icon\n &::before\n content: \"\"\n position: absolute\n left: 0.5rem\n width: 1rem\n height: 1rem\n\n// Default styles\np.admonition-title\n background-color: var(--color-admonition-title-background)\n &::before\n background-color: var(--color-admonition-title)\n mask-image: var(--icon-admonition-default)\n mask-repeat: no-repeat\n\np.topic-title\n background-color: var(--color-topic-title-background)\n &::before\n background-color: var(--color-topic-title)\n mask-image: var(--icon-topic-default)\n mask-repeat: no-repeat\n\n//\n// Variants\n//\n.admonition\n border-left: 0.2rem solid var(--color-admonition-title)\n\n @each $type, $value in $admonitions\n &.#{$type}\n border-left-color: var(--color-admonition-title--#{$type})\n > .admonition-title\n background-color: var(--color-admonition-title-background--#{$type})\n &::before\n background-color: var(--color-admonition-title--#{$type})\n mask-image: var(--icon-#{nth($value, 2)})\n\n.admonition-todo > .admonition-title\n text-transform: uppercase\n","// This file stylizes the API documentation (stuff generated by autodoc). It's\n// deeply nested due to how autodoc structures the HTML without enough classes\n// to select the relevant items.\n\n// API docs!\ndl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)\n // Tweak the spacing of all the things!\n dd\n margin-left: 2rem\n > :first-child\n margin-top: 0.125rem\n > :last-child\n margin-bottom: 0.75rem\n\n // This is used for the arguments\n .field-list\n margin-bottom: 0.75rem\n\n // \"Headings\" (like \"Parameters\" and \"Return\")\n > dt\n text-transform: uppercase\n font-size: var(--font-size--small)\n\n dd:empty\n margin-bottom: 0.5rem\n dd > ul\n margin-left: -1.2rem\n > li\n > p:nth-child(2)\n margin-top: 0\n // When the last-empty-paragraph follows a paragraph, it doesn't need\n // to augument the existing spacing.\n > p + p:last-child:empty\n margin-top: 0\n margin-bottom: 0\n\n // Colorize the elements\n > dt\n color: var(--color-api-overall)\n\n.sig:not(.sig-inline)\n font-weight: bold\n\n font-size: var(--api-font-size)\n font-family: var(--font-stack--monospace)\n\n margin-left: -0.25rem\n margin-right: -0.25rem\n padding-top: 0.25rem\n padding-bottom: 0.25rem\n padding-right: 0.5rem\n\n // These are intentionally em, to properly match the font size.\n padding-left: 3em\n text-indent: -2.5em\n\n border-radius: 0.25rem\n\n background: var(--color-api-background)\n transition: background 100ms ease-out\n\n &:hover\n background: var(--color-api-background-hover)\n\n // adjust the size of the [source] link on the right.\n a.reference\n .viewcode-link\n font-weight: normal\n width: 4.25rem\n\nem.property\n font-style: normal\n &:first-child\n color: var(--color-api-keyword)\n.sig-name\n color: var(--color-api-name)\n.sig-prename\n font-weight: normal\n color: var(--color-api-pre-name)\n.sig-paren\n color: var(--color-api-paren)\n.sig-param\n font-style: normal\n\ndiv.versionadded,\ndiv.versionchanged,\ndiv.deprecated,\ndiv.versionremoved\n border-left: 0.1875rem solid\n border-radius: 0.125rem\n\n padding-left: 0.75rem\n\n p\n margin-top: 0.125rem\n margin-bottom: 0.125rem\n\ndiv.versionadded\n border-color: var(--color-api-added-border)\n .versionmodified\n color: var(--color-api-added)\n\ndiv.versionchanged\n border-color: var(--color-api-changed-border)\n .versionmodified\n color: var(--color-api-changed)\n\ndiv.deprecated\n border-color: var(--color-api-deprecated-border)\n .versionmodified\n color: var(--color-api-deprecated)\n\ndiv.versionremoved\n border-color: var(--color-api-removed-border)\n .versionmodified\n color: var(--color-api-removed)\n\n// Align the [docs] and [source] to the right.\n.viewcode-link, .viewcode-back\n float: right\n text-align: right\n",".line-block\n margin-top: 0.5rem\n margin-bottom: 0.75rem\n .line-block\n margin-top: 0rem\n margin-bottom: 0rem\n padding-left: 1rem\n","// Captions\narticle p.caption,\ntable > caption,\n.code-block-caption\n font-size: var(--font-size--small)\n text-align: center\n\n// Caption above a TOCTree\n.toctree-wrapper.compound\n .caption, :not(.caption) > .caption-text\n font-size: var(--font-size--small)\n text-transform: uppercase\n\n text-align: initial\n margin-bottom: 0\n\n > ul\n margin-top: 0\n margin-bottom: 0\n","// Inline code\ncode.literal, .sig-inline\n background: var(--color-inline-code-background)\n border-radius: 0.2em\n // Make the font smaller, and use padding to recover.\n font-size: var(--font-size--small--2)\n padding: 0.1em 0.2em\n\n pre.literal-block &\n font-size: inherit\n padding: 0\n\n p &\n border: 1px solid var(--color-background-border)\n\n.sig-inline\n font-family: var(--font-stack--monospace)\n\n// Code and Literal Blocks\n$code-spacing-vertical: 0.625rem\n$code-spacing-horizontal: 0.875rem\n\n// Wraps every literal block + line numbers.\ndiv[class*=\" highlight-\"],\ndiv[class^=\"highlight-\"]\n margin: 1em 0\n display: flex\n\n .table-wrapper\n margin: 0\n padding: 0\n\npre\n margin: 0\n padding: 0\n overflow: auto\n\n // Needed to have more specificity than pygments' \"pre\" selector. :(\n article[role=\"main\"] .highlight &\n line-height: 1.5\n\n &.literal-block,\n .highlight &\n font-size: var(--code-font-size)\n padding: $code-spacing-vertical $code-spacing-horizontal\n\n // Make it look like all the other blocks.\n &.literal-block\n margin-top: 1rem\n margin-bottom: 1rem\n\n border-radius: 0.2rem\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n\n// All code is always contained in this.\n.highlight\n width: 100%\n border-radius: 0.2rem\n\n // Make line numbers and prompts un-selectable.\n .gp, span.linenos\n user-select: none\n pointer-events: none\n\n // Expand the line-highlighting.\n .hll\n display: block\n margin-left: -$code-spacing-horizontal\n margin-right: -$code-spacing-horizontal\n padding-left: $code-spacing-horizontal\n padding-right: $code-spacing-horizontal\n\n/* Make code block captions be nicely integrated */\n.code-block-caption\n display: flex\n padding: $code-spacing-vertical $code-spacing-horizontal\n\n border-radius: 0.25rem\n border-bottom-left-radius: 0\n border-bottom-right-radius: 0\n font-weight: 300\n border-bottom: 1px solid\n\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n border-color: var(--color-background-border)\n\n + div[class]\n margin-top: 0\n pre\n border-top-left-radius: 0\n border-top-right-radius: 0\n\n// When `html_codeblock_linenos_style` is table.\n.highlighttable\n width: 100%\n display: block\n tbody\n display: block\n\n tr\n display: flex\n\n // Line numbers\n td.linenos\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n padding: $code-spacing-vertical $code-spacing-horizontal\n padding-right: 0\n border-top-left-radius: 0.2rem\n border-bottom-left-radius: 0.2rem\n\n .linenodiv\n padding-right: $code-spacing-horizontal\n font-size: var(--code-font-size)\n box-shadow: -0.0625rem 0 var(--color-foreground-border) inset\n\n // Actual code\n td.code\n padding: 0\n display: block\n flex: 1\n overflow: hidden\n\n .highlight\n border-top-left-radius: 0\n border-bottom-left-radius: 0\n\n// When `html_codeblock_linenos_style` is inline.\n.highlight\n span.linenos\n display: inline-block\n padding-left: 0\n padding-right: $code-spacing-horizontal\n margin-right: $code-spacing-horizontal\n box-shadow: -0.0625rem 0 var(--color-foreground-border) inset\n","// Inline Footnote Reference\n.footnote-reference\n font-size: var(--font-size--small--4)\n vertical-align: super\n\n// Definition list, listing the content of each note.\n// docutils <= 0.17\ndl.footnote.brackets\n font-size: var(--font-size--small)\n color: var(--color-foreground-secondary)\n\n display: grid\n grid-template-columns: max-content auto\n dt\n margin: 0\n > .fn-backref\n margin-left: 0.25rem\n\n &:after\n content: \":\"\n\n .brackets\n &:before\n content: \"[\"\n &:after\n content: \"]\"\n\n dd\n margin: 0\n padding: 0 1rem\n\n// docutils >= 0.18\naside.footnote\n font-size: var(--font-size--small)\n color: var(--color-foreground-secondary)\n\naside.footnote > span,\ndiv.citation > span\n float: left\n font-weight: 500\n padding-right: 0.25rem\n\naside.footnote > *:not(span),\ndiv.citation > p\n margin-left: 2rem\n","//\n// Figures\n//\nimg\n box-sizing: border-box\n max-width: 100%\n height: auto\n\narticle\n figure, .figure\n border-radius: 0.2rem\n\n margin: 0\n :last-child\n margin-bottom: 0\n\n .align-left\n float: left\n clear: left\n margin: 0 1rem 1rem\n\n .align-right\n float: right\n clear: right\n margin: 0 1rem 1rem\n\n .align-default,\n .align-center\n display: block\n text-align: center\n margin-left: auto\n margin-right: auto\n\n // WELL, table needs to be stylised like a table.\n table.align-default\n display: table\n text-align: initial\n",".genindex-jumpbox, .domainindex-jumpbox\n border-top: 1px solid var(--color-background-border)\n border-bottom: 1px solid var(--color-background-border)\n padding: 0.25rem\n\n.genindex-section, .domainindex-section\n h2\n margin-top: 0.75rem\n margin-bottom: 0.5rem\n ul\n margin-top: 0\n margin-bottom: 0\n","ul,\nol\n padding-left: 1.2rem\n\n // Space lists out like paragraphs\n margin-top: 1rem\n margin-bottom: 1rem\n // reduce margins within li.\n li\n > p:first-child\n margin-top: 0.25rem\n margin-bottom: 0.25rem\n\n > p:last-child\n margin-top: 0.25rem\n\n > ul,\n > ol\n margin-top: 0.5rem\n margin-bottom: 0.5rem\n\nol\n &.arabic\n list-style: decimal\n &.loweralpha\n list-style: lower-alpha\n &.upperalpha\n list-style: upper-alpha\n &.lowerroman\n list-style: lower-roman\n &.upperroman\n list-style: upper-roman\n\n// Don't space lists out when they're \"simple\" or in a `.. toctree::`\n.simple,\n.toctree-wrapper\n li\n > ul,\n > ol\n margin-top: 0\n margin-bottom: 0\n\n// Definition Lists\n.field-list,\n.option-list,\ndl:not([class]),\ndl.simple,\ndl.footnote,\ndl.glossary\n dt\n font-weight: 500\n margin-top: 0.25rem\n + dt\n margin-top: 0\n\n .classifier::before\n content: \":\"\n margin-left: 0.2rem\n margin-right: 0.2rem\n\n dd\n > p:first-child,\n ul\n margin-top: 0.125rem\n\n ul\n margin-bottom: 0.125rem\n",".math-wrapper\n width: 100%\n overflow-x: auto\n\ndiv.math\n position: relative\n text-align: center\n\n .headerlink,\n &:focus .headerlink\n display: none\n\n &:hover .headerlink\n display: inline-block\n\n span.eqno\n position: absolute\n right: 0.5rem\n top: 50%\n transform: translate(0, -50%)\n z-index: 1\n","// Abbreviations\nabbr[title]\n cursor: help\n\n// \"Problematic\" content, as identified by Sphinx\n.problematic\n color: var(--color-problematic)\n\n// Keyboard / Mouse \"instructions\"\nkbd:not(.compound)\n margin: 0 0.2rem\n padding: 0 0.2rem\n border-radius: 0.2rem\n border: 1px solid var(--color-foreground-border)\n color: var(--color-foreground-primary)\n vertical-align: text-bottom\n\n font-size: var(--font-size--small--3)\n display: inline-block\n\n box-shadow: 0 0.0625rem 0 rgba(0, 0, 0, 0.2), inset 0 0 0 0.125rem var(--color-background-primary)\n\n background-color: var(--color-background-secondary)\n\n// Blockquote\nblockquote\n border-left: 4px solid var(--color-background-border)\n background: var(--color-background-secondary)\n\n margin-left: 0\n margin-right: 0\n padding: 0.5rem 1rem\n\n .attribution\n font-weight: 600\n text-align: right\n\n &.pull-quote,\n &.highlights\n font-size: 1.25em\n\n &.epigraph,\n &.pull-quote\n border-left-width: 0\n border-radius: 0.5rem\n\n &.highlights\n border-left-width: 0\n background: transparent\n\n// Center align embedded-in-text images\np .reference img\n vertical-align: middle\n","p.rubric\n line-height: 1.25\n font-weight: bold\n font-size: 1.125em\n\n // For Numpy-style documentation that's got rubrics within it.\n // https://github.com/pradyunsg/furo/discussions/505\n dd &\n line-height: inherit\n font-weight: inherit\n\n font-size: var(--font-size--small)\n text-transform: uppercase\n","article .sidebar\n float: right\n clear: right\n width: 30%\n\n margin-left: 1rem\n margin-right: 0\n\n border-radius: 0.2rem\n background-color: var(--color-background-secondary)\n border: var(--color-background-border) 1px solid\n\n > *\n padding-left: 1rem\n padding-right: 1rem\n\n > ul, > ol // lists need additional padding, because bullets.\n padding-left: 2.2rem\n\n .sidebar-title\n margin: 0\n padding: 0.5rem 1rem\n border-bottom: var(--color-background-border) 1px solid\n\n font-weight: 500\n\n// TODO: subtitle\n// TODO: dedicated variables?\n","[role=main] .table-wrapper.container\n width: 100%\n overflow-x: auto\n margin-top: 1rem\n margin-bottom: 0.5rem\n padding: 0.2rem 0.2rem 0.75rem\n\ntable.docutils\n border-radius: 0.2rem\n border-spacing: 0\n border-collapse: collapse\n\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n th\n background: var(--color-table-header-background)\n\n td,\n th\n // Space things out properly\n padding: 0 0.25rem\n\n // Get the borders looking just-right.\n border-left: 1px solid var(--color-table-border)\n border-right: 1px solid var(--color-table-border)\n border-bottom: 1px solid var(--color-table-border)\n\n p\n margin: 0.25rem\n\n &:first-child\n border-left: none\n &:last-child\n border-right: none\n\n // MyST-parser tables set these classes for control of column alignment\n &.text-left\n text-align: left\n &.text-right\n text-align: right\n &.text-center\n text-align: center\n",":target\n scroll-margin-top: 2.5rem\n\n@media (max-width: $full-width - $sidebar-width)\n :target\n scroll-margin-top: calc(2.5rem + var(--header-height))\n\n // When a heading is selected\n section > span:target\n scroll-margin-top: calc(2.8rem + var(--header-height))\n\n// Permalinks\n.headerlink\n font-weight: 100\n user-select: none\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\ndl dt,\np.caption,\nfigcaption p,\ntable > caption,\n.code-block-caption\n > .headerlink\n margin-left: 0.5rem\n visibility: hidden\n &:hover > .headerlink\n visibility: visible\n\n // Don't change to link-like, if someone adds the contents directive.\n > .toc-backref\n color: inherit\n text-decoration-line: none\n\n// Figure and table captions are special.\nfigure:hover > figcaption > p > .headerlink,\ntable:hover > caption > .headerlink\n visibility: visible\n\n:target >, // Regular section[id] style anchors\nspan:target ~ // Non-regular span[id] style \"extra\" anchors\n h1,\n h2,\n h3,\n h4,\n h5,\n h6\n &:nth-of-type(1)\n background-color: var(--color-highlight-on-target)\n // .headerlink\n // visibility: visible\n code.literal\n background-color: transparent\n\ntable:target > caption,\nfigure:target\n background-color: var(--color-highlight-on-target)\n\n// Inline page contents\n.this-will-duplicate-information-and-it-is-still-useful-here li :target\n background-color: var(--color-highlight-on-target)\n\n// Code block permalinks\n.literal-block-wrapper:target .code-block-caption\n background-color: var(--color-highlight-on-target)\n\n// When a definition list item is selected\n//\n// There isn't really an alternative to !important here, due to the\n// high-specificity of API documentation's selector.\ndt:target\n background-color: var(--color-highlight-on-target) !important\n\n// When a footnote reference is selected\n.footnote > dt:target + dd,\n.footnote-reference:target\n background-color: var(--color-highlight-on-target)\n",".guilabel\n background-color: var(--color-guilabel-background)\n border: 1px solid var(--color-guilabel-border)\n color: var(--color-guilabel-text)\n\n padding: 0 0.3em\n border-radius: 0.5em\n font-size: 0.9em\n","// This file contains the styles used for stylizing the footer that's shown\n// below the content.\n\nfooter\n font-size: var(--font-size--small)\n display: flex\n flex-direction: column\n\n margin-top: 2rem\n\n// Bottom of page information\n.bottom-of-page\n display: flex\n align-items: center\n justify-content: space-between\n\n margin-top: 1rem\n padding-top: 1rem\n padding-bottom: 1rem\n\n color: var(--color-foreground-secondary)\n border-top: 1px solid var(--color-background-border)\n\n line-height: 1.5\n\n @media (max-width: $content-width)\n text-align: center\n flex-direction: column-reverse\n gap: 0.25rem\n\n .left-details\n font-size: var(--font-size--small)\n\n .right-details\n display: flex\n flex-direction: column\n gap: 0.25rem\n text-align: right\n\n .icons\n display: flex\n justify-content: flex-end\n gap: 0.25rem\n font-size: 1rem\n\n a\n text-decoration: none\n\n svg,\n img\n font-size: 1.125rem\n height: 1em\n width: 1em\n\n// Next/Prev page information\n.related-pages\n a\n display: flex\n align-items: center\n\n text-decoration: none\n &:hover .page-info .title\n text-decoration: underline\n color: var(--color-link)\n text-decoration-color: var(--color-link-underline)\n\n svg.furo-related-icon,\n svg.furo-related-icon > use\n flex-shrink: 0\n\n color: var(--color-foreground-border)\n\n width: 0.75rem\n height: 0.75rem\n margin: 0 0.5rem\n\n &.next-page\n max-width: 50%\n\n float: right\n clear: right\n text-align: right\n\n &.prev-page\n max-width: 50%\n\n float: left\n clear: left\n\n svg\n transform: rotate(180deg)\n\n.page-info\n display: flex\n flex-direction: column\n overflow-wrap: anywhere\n\n .next-page &\n align-items: flex-end\n\n .context\n display: flex\n align-items: center\n\n padding-bottom: 0.1rem\n\n color: var(--color-foreground-muted)\n font-size: var(--font-size--small)\n text-decoration: none\n","// This file contains the styles for the contents of the left sidebar, which\n// contains the navigation tree, logo, search etc.\n\n////////////////////////////////////////////////////////////////////////////////\n// Brand on top of the scrollable tree.\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-brand\n display: flex\n flex-direction: column\n flex-shrink: 0\n\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n text-decoration: none\n\n.sidebar-brand-text\n color: var(--color-sidebar-brand-text)\n overflow-wrap: break-word\n margin: var(--sidebar-item-spacing-vertical) 0\n font-size: 1.5rem\n\n.sidebar-logo-container\n margin: var(--sidebar-item-spacing-vertical) 0\n\n.sidebar-logo\n margin: 0 auto\n display: block\n max-width: 100%\n\n////////////////////////////////////////////////////////////////////////////////\n// Search\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-search-container\n display: flex\n align-items: center\n margin-top: var(--sidebar-search-space-above)\n\n position: relative\n\n background: var(--color-sidebar-search-background)\n &:hover,\n &:focus-within\n background: var(--color-sidebar-search-background--focus)\n\n &::before\n content: \"\"\n position: absolute\n left: var(--sidebar-item-spacing-horizontal)\n width: var(--sidebar-search-icon-size)\n height: var(--sidebar-search-icon-size)\n\n background-color: var(--color-sidebar-search-icon)\n mask-image: var(--icon-search)\n\n.sidebar-search\n box-sizing: border-box\n\n border: none\n border-top: 1px solid var(--color-sidebar-search-border)\n border-bottom: 1px solid var(--color-sidebar-search-border)\n\n padding-top: var(--sidebar-search-input-spacing-vertical)\n padding-bottom: var(--sidebar-search-input-spacing-vertical)\n padding-right: var(--sidebar-search-input-spacing-horizontal)\n padding-left: calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size))\n\n width: 100%\n\n color: var(--color-sidebar-search-foreground)\n background: transparent\n z-index: 10\n\n &:focus\n outline: none\n\n &::placeholder\n font-size: var(--sidebar-search-input-font-size)\n\n//\n// Hide Search Matches link\n//\n#searchbox .highlight-link\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0\n margin: 0\n text-align: center\n\n a\n color: var(--color-sidebar-search-icon)\n font-size: var(--font-size--small--2)\n\n////////////////////////////////////////////////////////////////////////////////\n// Structure/Skeleton of the navigation tree (left)\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-tree\n font-size: var(--sidebar-item-font-size)\n margin-top: var(--sidebar-tree-space-above)\n margin-bottom: var(--sidebar-item-spacing-vertical)\n\n ul\n padding: 0\n margin-top: 0\n margin-bottom: 0\n\n display: flex\n flex-direction: column\n\n list-style: none\n\n li\n position: relative\n margin: 0\n\n > ul\n margin-left: var(--sidebar-item-spacing-horizontal)\n\n .icon\n color: var(--color-sidebar-link-text)\n\n .reference\n box-sizing: border-box\n color: var(--color-sidebar-link-text)\n\n // Fill the parent.\n display: inline-block\n line-height: var(--sidebar-item-line-height)\n text-decoration: none\n\n // Don't allow long words to cause wrapping.\n overflow-wrap: anywhere\n\n height: 100%\n width: 100%\n\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n\n &:hover\n color: var(--color-sidebar-link-text)\n background: var(--color-sidebar-item-background--hover)\n\n // Add a nice little \"external-link\" arrow here.\n &.external::after\n content: url('data:image/svg+xml,')\n margin: 0 0.25rem\n vertical-align: middle\n color: var(--color-sidebar-link-text)\n\n // Make the current page reference bold.\n .current-page > .reference\n font-weight: bold\n\n label\n position: absolute\n top: 0\n right: 0\n height: var(--sidebar-item-height)\n width: var(--sidebar-expander-width)\n\n cursor: pointer\n user-select: none\n\n display: flex\n justify-content: center\n align-items: center\n\n .caption, :not(.caption) > .caption-text\n font-size: var(--sidebar-caption-font-size)\n color: var(--color-sidebar-caption-text)\n\n font-weight: bold\n text-transform: uppercase\n\n margin: var(--sidebar-caption-space-above) 0 0 0\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n\n // If it has children, add a bit more padding to wrap the content to avoid\n // overlapping with the