diff --git a/tutorial/transport/0_transport-tutorial_platform-setup.ipynb b/tutorial/transport/0_transport-tutorial_platform-setup.ipynb index eebbd1d0..098459a2 100644 --- a/tutorial/transport/0_transport-tutorial_platform-setup.ipynb +++ b/tutorial/transport/0_transport-tutorial_platform-setup.ipynb @@ -26,7 +26,7 @@ "\n", "This tutorial consists of three Jupyter notebooks:\n", "\n", - "0. Set up an **ixmp4.Platform** to store the scenario input data and solution\n", + "0. Set up an `ixmp4.Platform` to store the scenario input data and solution\n", "1. Implement the **baseline version of the transport problem** and solve it\n", "2. Create an **alternative scenario** and solve it " ] @@ -83,7 +83,7 @@ "id": "b6209e20-9979-4f3b-b6b7-fe37a1c09366", "metadata": {}, "source": [ - "## Defining units in the platform and database\n", + "## Defining units in the `Platform` and database\n", "\n", "The **ixmp4** package requires that units are defined explicitly before adding data using them.\n", "\n", @@ -92,7 +92,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "9be63ed0-aca6-44e3-a6c6-d8fdf8ec2549", "metadata": {}, "outputs": [], @@ -154,7 +154,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "6c1a395d-6c8a-489c-9aa8-39d24895ca51", "metadata": {}, "outputs": [], @@ -195,19 +195,11 @@ "The prompt will ask whether you also want to delete the SQLite database file from your computer, \n", "answer `y` for a complete deletion." ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9395354e-2a0a-45f1-b819-34af7d925805", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -221,7 +213,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/tutorial/transport/1_transport-tutorial.ipynb b/tutorial/transport/1_transport-tutorial.ipynb index 72c64cdd..73dbb0c3 100644 --- a/tutorial/transport/1_transport-tutorial.ipynb +++ b/tutorial/transport/1_transport-tutorial.ipynb @@ -25,7 +25,7 @@ "\n", "This tutorial consists of three Jupyter notebooks:\n", "\n", - "0. Set up an **ixmp4.Platform** to store the scenario input data and solution\n", + "0. Set up an `ixmp4.Platform` to store the scenario input data and solution\n", "1. Implement the **baseline version of the transport problem** and solve it\n", "2. Create an **alternative scenario** and solve it " ] @@ -45,7 +45,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## The platform as a connection to the database\n", + "## The `Platform` as a connection to the database\n", "\n", "An [**ixmp4.Platform**](https://docs.ece.iiasa.ac.at/projects/ixmp4/en/latest/devs/ixmp4.core/platform.html#ixmp4.core.platform.Platform)\n", "is the connection to a database instance that can hold scenario data and relevant additional information." @@ -57,15 +57,8 @@ "metadata": {}, "outputs": [], "source": [ - "import ixmp4" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ + "import ixmp4\n", + "\n", "platform = ixmp4.Platform(\"transport-tutorial\")" ] }, @@ -75,14 +68,14 @@ "source": [ "An [**ixmp4.Run**](https://docs.ece.iiasa.ac.at/projects/ixmp4/en/latest/devs/ixmp4.core/run.html#ixmp4.core.run.Run)\n", "is an object that holds all relevant information for one quantification of a \"scenario\". \n", - "A run is identified by a *model name*, a *scenario name* and an automatically assigned *version number*.\n", + "A `Run` is identified by a *model name*, a *scenario name* and an automatically assigned *version number*.\n", "\n", - "As a first step to solve the **transport problem**, we create a new run." + "As a first step to solve the **transport problem**, we create a new `Run`." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -95,13 +88,13 @@ "source": [ "## Defining the structure of the optimization problem\n", "\n", - "### The IndexSets\n", + "### The `IndexSet`s\n", "\n", - "An **IndexSet** defines a list of elements with a name. These sets can be used for \"indexed assignment\" of parameters, variables and equations. \n", - "The entries of these parameters, etc. are then validated against the elements of the linked set. \n", - "In database terms, a column of a parameter, etc. can be \"foreign-keyed\" onto an set.\n", + "An `IndexSet` defines a list of elements with a name. These sets can be used for \"indexed assignment\" of `Parameter`s, `Variable`s and `Equation`s. \n", + "The entries of these `Parameter`s, etc. are then validated against the elements of the linked set. \n", + "In database terms, a column of a `Parameter`, etc. is \"foreign-keyed\" onto a `IndexSet`.\n", "\n", - "Below, we first show the data as they would be written in the linopy tutorial." + "Below, we first show the data as they would be written in the **GAMS** tutorial." ] }, { @@ -122,7 +115,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -134,7 +127,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can display the elements of any **IndexSet** as a Python list:" + "We can display the data (a.k.a elements) of any `IndexSet` as a Python list:" ] }, { @@ -150,12 +143,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "An **IndexSet** can have a docstring as a means for documentation." + "An `IndexSet` can have a docstring as a means for documentation." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -166,14 +159,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For simplicity, the steps of creating an **IndexSet** and assigning elements can be done in one line.\n", + "For simplicity, the steps of creating an `IndexSet` and assigning elements can be done in one line.\n", "\n", - "We illustrate this for the second index-set of the transport problem." + "We illustrate this for the second `IndexSet` of the transport problem." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -184,12 +177,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To add the docstring, we now have to explicitly get the index-set *j* and add the documentation." + "To add the docstring, we now have to explicitly get the `IndexSet` *j* and add the documentation." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -202,10 +195,10 @@ "source": [ "### Parameters of the optimization problem\n", "\n", - "A **Parameter** is a table with a number of index columns (each constrained to an **IndexSet**) as well as *units* and *values* columns.\n", + "A `Parameter` is a table with a number of index columns (each constrained to an `IndexSet`) as well as *units* and *values* columns.\n", "\n", "As a next step to solving the transport problem, we define the parameters *capacity* and *demand*.\n", - "The parameters are assigned on the indexsets *i* and *j*, respectively." + "The parameters are assigned on the `IndexSet`s *i* and *j*, respectively." ] }, { @@ -226,12 +219,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The parameter data can be assigned as a dictionary." + "The data of the `Parameter` can be assigned as a dictionary." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -250,18 +243,17 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Alternatively, the parameter data can be passed as a **pandas.DataFrame**." + "Alternatively, the data can be passed as a `pandas.DataFrame`." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", - "\n", "b = run.optimization.parameters.create(\"b\", constrained_to_indexsets=[\"j\"])\n", "b.docs = \"Demand at market j\"\n", "\n", @@ -280,7 +272,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Notice how the data has three columns but has only been linked to one **IndexSet**? That's on purpose: Every **Parameter** needs to have (the columns) *values* and *units*. The value(s) can be any number(s), but the units have to be defined a-priori in the **ixmp4.Platform**.\n", + "Notice how the data has three columns but has only been linked to one `IndexSet`? That's on purpose: Every `Parameter` needs to have (the columns) *values* and *units*. The value(s) can be any number(s), but the units have to be defined a-priori in the `ixmp4.Platform`.\n", "\n", "Here's how to access `parameter.data` to e.g. quickly confirm that *b* is set correctly:" ] @@ -315,14 +307,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It is possible to add data to a parameter in several steps...\n", + "It is possible to add data to a `Parameter` in several steps...\n", "\n", "Here, we first define the parameter *d* and add four datapoints." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -347,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -361,7 +353,7 @@ "source": [ "
\n", "\n", - "Every time you add data, **all** columns of the parameter must be present!\n", + "Every time you add data, **all** columns of the `Parameter` must be present!\n", "\n", "
" ] @@ -372,7 +364,7 @@ "source": [ "### Scalars\n", "\n", - "Another type of input data for optimization problems is a **Scalar**. These are not linked to an **IndexSet**, but consist of only a value and a unit (and a docstring)." + "Another type of input data for optimization problems is a `Scalar`. These are not linked to an `IndexSet`, but consist of only a *value* and a *unit* (and a docstring)." ] }, { @@ -384,11 +376,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ - "f = run.optimization.scalars.create(name=\"f\", value=90, unit=\"dollars per case per 1000 miles\")\n", + "f = run.optimization.scalars.create(\n", + " name=\"f\", value=90, unit=\"dollars per case per 1000 miles\"\n", + ")\n", "f.docs = \"Freight\"" ] }, @@ -398,9 +392,9 @@ "source": [ "### Defining the solution structure\n", "\n", - "The solution of an optimization problem are a list of **Variable** and **Equation** objects.\n", + "The solution of an optimization problem is a list of `Variable` and `Equation` objects.\n", "\n", - "We first define the variables and equations in the **ixmp4.Run**. The values of the solution (level and marginal, mathematically speaking) are read from the **linopy** output after solving the problem." + "We first define the `Variable`s and `Equation`s in the `ixmp4.Run`. The values of the solution (level and marginal, mathematically speaking) are read from the **linopy** output after solving the problem." ] }, { @@ -421,14 +415,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here, *supply* can only come from the canning plant in **IndexSet** *i*, while *demand* needs to be met at the markets in **IndexSet** *j*.\n", + "Here, *supply* can only come from the canning plant in `IndexSet` *i*, while *demand* needs to be met at the markets in `IndexSet` *j*.\n", "\n", "Shipment is defined from a canning plant to a market, so *x* needs to be assigned to both *i* and *j*." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -445,13 +439,13 @@ "source": [ "## Solve the scenario\n", "\n", - "In this tutorial, we solve the tutorial using the open-source solver *highs* in **linopy**. \n", + "In this tutorial, we solve the tutorial using the open-source solver [*highs*](https://github.com/ERGO-Code/HiGHS) in **linopy**. \n", "\n", - "The ``create_dantzig_model()`` function is a convenience shortcut to retrieve the data from the **ixmp4.Run**\n", - "and set up a linopy model correctly to solve the transport problem. Please see ``linopy_model.py`` for details.\n", + "The ``create_dantzig_model()`` function is a convenience shortcut to retrieve the data from the `ixmp4.Run`\n", + "and set up a **linopy** model correctly to solve the transport problem. Please see ``linopy_model.py`` for details.\n", "\n", "The solution of the transport problem is stored with the model object automatically.\n", - "The function ``store_dantzig_solution()`` reads the solution and stores it in the respective **Variable** and **Equation** objects of the **ixmp4.Run**." + "The function ``store_dantzig_solution()`` reads the solution and stores it in the respective `Variable` and `Equation` objects of the `ixmp4.Run`." ] }, { @@ -465,7 +459,6 @@ " read_dantzig_solution,\n", ")\n", "\n", - "\n", "linopy_model = create_dantzig_model(run=run)\n", "linopy_model.solve(\"highs\")\n", "read_dantzig_solution(model=linopy_model, run=run)" @@ -511,7 +504,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The levels and marginals of the **Equation** show the shipped quantities and shadow prices (\"dual variables\") of the least-cost solution." + "The levels and marginals of the `Equation`s show the shipped quantities and shadow prices (\"dual variables\") of the least-cost solution." ] }, { @@ -536,36 +529,29 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Setting a default version of a run\n", + "## Setting a default version of a `Run`\n", "\n", - "The key benefit of **ixmp4** is handling a large number of scenarios - aka **ixmp4.Run** objects - in a version-controlled database.\n", - "Each run is identified by a *model name*, a *scenario name* and an automatically assigned *version number*.\n", + "The key benefit of **ixmp4** is handling a large number of scenarios - aka `ixmp4.Run` objects - in a version-controlled database.\n", + "Each `Run` is identified by a *model name*, a *scenario name* and an automatically assigned *version number*.\n", "\n", - "For every model-scenario combination, we can assign one run as the *default version*.\n", + "For every model-scenario combination, we can assign one `Run` as the *default version*.\n", "This allows to keep previous versions in the database (for easy reference and comparison) but have a well-defined approach to get the \"right\" version (e.g., the latest version of a scenario)." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "run.set_as_default()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -579,7 +565,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/tutorial/transport/2_transport-tutorial_variant.ipynb b/tutorial/transport/2_transport-tutorial_variant.ipynb index e8b1b2d7..a1b32d46 100644 --- a/tutorial/transport/2_transport-tutorial_variant.ipynb +++ b/tutorial/transport/2_transport-tutorial_variant.ipynb @@ -25,7 +25,7 @@ "\n", "This tutorial consists of three Jupyter notebooks:\n", "\n", - "0. Set up an **ixmp4.Platform** to store the scenario input data and solution\n", + "0. Set up an `ixmp4.Platform` to store the scenario input data and solution\n", "1. Implement the **baseline version of the transport problem** and solve it\n", "2. Create an **alternative scenario** and solve it " ] @@ -45,7 +45,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## The platform as a connection to the database\n", + "## The `Platform` as a connection to the database\n", "\n", "An [**ixmp4.Platform**](https://docs.ece.iiasa.ac.at/projects/ixmp4/en/latest/devs/ixmp4.core/platform.html#ixmp4.core.platform.Platform)\n", "is the connection to a database instance that can hold scenario data and relevant additional information." @@ -58,15 +58,7 @@ "outputs": [], "source": [ "import ixmp4\n", - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ + "\n", "platform = ixmp4.Platform(\"transport-tutorial\")" ] }, @@ -75,7 +67,7 @@ "metadata": {}, "source": [ "As a first step, we list all scenarios (aka [**ixmp4.Run**](https://docs.ece.iiasa.ac.at/projects/ixmp4/en/latest/devs/ixmp4.core/run.html#ixmp4.core.run.Run) instances)\n", - "available in the platform connected to the database instance." + "available in the `Platform` connected to the database instance." ] }, { @@ -103,14 +95,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Retrieve a run from the platform\n", + "## Retrieve a `Run` from the `Platform`\n", "\n", - "As a first step in this notebook, you can load the **ixmp4.Run** that you created in Notebook 1." + "As a first step in this notebook, you can load the `ixmp4.Run` that you created in [Notebook 1](1_transport-tutorial.ipynb)." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -137,7 +129,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can check that the solution from Notebook 1 was indeed saved by inspecting an **ixmp4.Variable**. \n", + "You can check that the solution from [Notebook 1](1_transport-tutorial.ipynb) was indeed saved by inspecting an `ixmp4.Variable`. \n", "The `data` of any optimization object is stored internally as a dictionary." ] }, @@ -154,22 +146,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For instances of a **Variable** or a **Parameter** that have multiple dimensions,\n", + "For instances of a `Variable` or a `Parameter` that have multiple dimensions,\n", "you can use **pandas** for filtering and inspecting the data.\n", "\n", - "The following cell shows the data for a using the distance parameter *d* and cast the `Parameter.data` to a **pandas.DataFrame**." + "The following cell shows how to retrieve the distance parameter *d* and cast the `Parameter.data` to a `pandas.DataFrame`." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ + "import pandas as pd\n", + "\n", "d = run.optimization.parameters.get(\"d\")\n", "distance = pd.DataFrame(d.data)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**pandas** enables easy and powerful filtering, e.g. showing only the distances for connections from Seattle:" + ] + }, { "cell_type": "code", "execution_count": null, @@ -218,7 +219,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 46, "metadata": {}, "outputs": [], "source": [ @@ -246,12 +247,12 @@ "metadata": {}, "source": [ "We now modify the structure and parameters of the *detroit* scenario.\n", - "First, we reduce the demand in *chicago* to ensure a feasible problem.." + "First, we reduce the demand in *chicago* to ensure a feasible problem." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 48, "metadata": {}, "outputs": [], "source": [ @@ -269,7 +270,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 49, "metadata": {}, "outputs": [], "source": [ @@ -282,7 +283,7 @@ " \"i\": [\"seattle\", \"san-diego\"],\n", " \"j\": [\"detroit\", \"detroit\"],\n", " \"values\": [1.7, 1.9],\n", - " \"units\": [\"km\", \"km\"],\n", + " \"units\": [\"1000 miles\", \"1000 miles\"],\n", " }\n", ")" ] @@ -294,7 +295,7 @@ "### Solve the new scenario\n", "\n", "Now, we create a **linopy** model, solve it, and read the solution to store it\n", - "in the respective **Variable** and **Equation** objects of the **ixmp4.Run**." + "in the respective `Variable` and `Equation` objects of the `ixmp4.Run`." ] }, { @@ -308,7 +309,6 @@ " read_dantzig_solution,\n", ")\n", "\n", - "\n", "linopy_model_detroit = create_dantzig_model(run=run_detroit)\n", "linopy_model_detroit.solve(\"highs\")\n", "read_dantzig_solution(model=linopy_model_detroit, run=run_detroit)" @@ -320,12 +320,12 @@ "source": [ "## Setting a default version of the scenario\n", "\n", - "As in Notebook 1, we set the *detroit* scenario as the default version so that we can easily retrieve it later." + "As in [Notebook 1](1_transport-tutorial.ipynb), we set the *detroit* scenario as the default version so that we can easily retrieve it later." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 51, "metadata": {}, "outputs": [], "source": [ @@ -338,9 +338,9 @@ "source": [ "## Display and analyze the results\n", "\n", - "We can now compare the *standard* instance of the *transport problem* and the *detroit* scenario.\n", + "We can now compare the *standard* instance of the **transport problem** and the *detroit* scenario.\n", "\n", - "The next two cells show the objective values of the two runs." + "The next two cells show the objective values of the two `Run`s." ] }, { @@ -375,7 +375,7 @@ "source": [ "You see that the *detroit* scenario has higher total cost.\n", "\n", - "The next two cells compare the optimal assignment of shipments from plants to markets in the two runs." + "The next two cells compare the optimal assignment of shipments from plants to markets in the two `Run`s." ] }, { @@ -402,7 +402,7 @@ "metadata": {}, "outputs": [], "source": [ - "run_detroit.optimization.equations.get(\"demand\").data" + "pd.DataFrame(run_detroit.optimization.equations.get(\"demand\").data)" ] }, { @@ -411,7 +411,7 @@ "source": [ "## Scenario version management\n", "\n", - "As above, you can now list all scenarios available in the database instance to which the **ixmp4.Platform** is connected.\n", + "As above, you can now list all scenarios available in the database instance to which the `ixmp4.Platform` is connected.\n", "\n", "You should now see (at least) two scenarios." ] @@ -431,15 +431,15 @@ "source": [ "When developing scenarios, we often make minor variations to numerous scenarios, and changing the *scenario name* every time is not practical.\n", "\n", - "The **ixmp4** package provides a more efficient approach to scenario version management: Every time that you *create* a new run or *clone* an existing run and the *model-scenario-name* combination already exists, a new *version* will be created.\n", + "The **ixmp4** package provides a more efficient approach to scenario version management: Every time that you `create()` a new `Run` or `clone()` an existing `Run` and the *model-scenario-name* combination already exists, a new *version* will be created.\n", "\n", - "You can get a specific *version* by including the version number in the call to get an **ixmp4.Run** like\n", + "You can get a specific *version* by including the version number in the call to get an `ixmp4.Run` like\n", "\n", "```python\n", "platforms.runs.get(model, scenario, version)\n", "```\n", "\n", - "You also can assign one of the versions as *default*, so that this run will be retrieved when you call\n", + "You also can assign one of the versions as *default*, so that this `Run` will be retrieved when you call\n", "\n", "```python\n", "platforms.runs.get(model, scenario)\n", @@ -447,9 +447,9 @@ "\n", "without a version number.\n", "\n", - "All versions will be kept in the database and are accessible via the **ixmp4.Platform**.\n", + "All versions will be kept in the database and are accessible via the `ixmp4.Platform`.\n", "\n", - "If you have run this tutorial multiple times, you can tabulate all runs - including the non-default versions - as illustrated below." + "If you have run this tutorial multiple times, you can tabulate all `Run`s - including the non-default versions - as illustrated below." ] }, { @@ -474,21 +474,14 @@ "ixmp4 platforms remove transport-tutorial\n", "```\n", "\n", - "The prompt will ask whether you also want to delete the SQLite database file from your computer." + "The prompt will ask whether you also want to delete the SQLite database file from your computer, answer `y` for a complete deletion." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -502,7 +495,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.12.3" } }, "nbformat": 4,