{ "cells": [ { "cell_type": "markdown", "id": "f33632f5", "metadata": {}, "source": [ "(tutorial_advanced_debugging_label)=\n", "\n", "# Debugging the solution process\n", "\n", "The {ref}`debugging tutorial ` covers specification\n", "errors on a small model. This tutorial goes one step further: it walks through\n", "the solution process of a larger model and shows how to use the solver's\n", "inspection tools when the solve fails.\n", "\n", "1. how the problem reduction, the block decomposition and the starting values\n", " of a successful solve can be inspected,\n", "2. how structural over- and underdetermination is reported and what a\n", " structurally singular but count-balanced problem looks like,\n", "3. how the specifications shape the block decomposition,\n", "4. how to run the solution process in stages with :code:`presolve` and\n", " :code:`solve_continue` and\n", "5. how to pause the solver at a failed block, inspect the failure and decide\n", " whether the starting values or the specification are the problem.\n", "\n", "Background information on all steps of the solution process is available in\n", "the {ref}`solver section `. The outputs shown here are\n", "based on the following version of tespy:" ] }, { "cell_type": "code", "execution_count": null, "id": "274b2f07", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:35.933157Z", "iopub.status.busy": "2026-07-26T13:14:35.932627Z", "iopub.status.idle": "2026-07-26T13:14:37.941126Z", "shell.execute_reply": "2026-07-26T13:14:37.940764Z" } }, "outputs": [], "source": [ "from tespy import __version__\n", "__version__" ] }, { "cell_type": "markdown", "id": "8e4914d1", "metadata": {}, "source": [ "## The example model\n", "\n", "The model is an Organic Rankine Cycle with an internal recuperator, an\n", "air cooled condenser and a geothermal heat source, the same plant as in the\n", "{ref}`workflow integration section `.\n", "\n", "```{figure} /_static/images/tutorials/orc/flowsheet.svg\n", ":align: center\n", ":class: only-light\n", ":width: 80%\n", "\n", "Flowsheet of the recuperated ORC system\n", "```\n", "\n", "```{figure} /_static/images/tutorials/orc/flowsheet_darkmode.svg\n", ":align: center\n", ":class: only-dark\n", ":width: 80%\n", "\n", "Flowsheet of the recuperated ORC system\n", "```\n", "\n", "The working fluid is isopentane. The evaporation level is fixed through\n", "saturated turbine inlet at 150 °C, the condensation level through the air\n", "temperatures and the pinch specifications of the condenser.\n", "\n", "```{note}\n", "Since the following\n", "sections modify the model repeatedly, the setup lives in a function returning a\n", "freshly parametrized network.\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "fdb69d80", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:37.942580Z", "iopub.status.busy": "2026-07-26T13:14:37.942428Z", "iopub.status.idle": "2026-07-26T13:14:37.947765Z", "shell.execute_reply": "2026-07-26T13:14:37.947270Z" } }, "outputs": [], "source": [ "from tespy.components import (\n", " CycleCloser, Generator, Motor, MovingBoundaryHeatExchanger,\n", " PowerBus, PowerSink, Pump, Sink, Source, Turbine,\n", ")\n", "from tespy.connections import Connection, PowerConnection\n", "from tespy.networks import Network\n", "\n", "\n", "def build_orc():\n", " nw = Network(iterinfo=False)\n", " nw.units.set_defaults(\n", " temperature=\"degC\", pressure=\"bar\", pressure_difference=\"bar\",\n", " power=\"kW\", heat=\"kW\"\n", " )\n", "\n", " turbine = Turbine(\"turbine\")\n", " recuperator = MovingBoundaryHeatExchanger(\"recuperator\")\n", " condenser = MovingBoundaryHeatExchanger(\"condenser\")\n", " pump = Pump(\"pump\")\n", " preheater = MovingBoundaryHeatExchanger(\"preheater\")\n", " evaporator = MovingBoundaryHeatExchanger(\"evaporator\")\n", " cc = CycleCloser(\"cc\")\n", "\n", " heat_source = Source(\"heat source\")\n", " heat_outflow = Sink(\"heat outflow\")\n", " air_source = Source(\"air source\")\n", " air_sink = Sink(\"air sink\")\n", "\n", " a1 = Connection(heat_source, \"out1\", evaporator, \"in1\", label=\"a1\")\n", " a2 = Connection(evaporator, \"out1\", preheater, \"in1\", label=\"a2\")\n", " a3 = Connection(preheater, \"out1\", heat_outflow, \"in1\", label=\"a3\")\n", "\n", " b1 = Connection(cc, \"out1\", turbine, \"in1\", label=\"b1\")\n", " b2 = Connection(turbine, \"out1\", recuperator, \"in1\", label=\"b2\")\n", " b3 = Connection(recuperator, \"out1\", condenser, \"in1\", label=\"b3\")\n", " b4 = Connection(condenser, \"out1\", pump, \"in1\", label=\"b4\")\n", " b5 = Connection(pump, \"out1\", recuperator, \"in2\", label=\"b5\")\n", " b6 = Connection(recuperator, \"out2\", preheater, \"in2\", label=\"b6\")\n", " b7 = Connection(preheater, \"out2\", evaporator, \"in2\", label=\"b7\")\n", " b8 = Connection(evaporator, \"out2\", cc, \"in1\", label=\"b8\")\n", "\n", " c1 = Connection(air_source, \"out1\", condenser, \"in2\", label=\"c1\")\n", " c2 = Connection(condenser, \"out2\", air_sink, \"in1\", label=\"c2\")\n", "\n", " nw.add_conns(a1, a2, a3, b1, b2, b3, b4, b5, b6, b7, b8, c1, c2)\n", "\n", " generator = Generator(\"generator\")\n", " motor = Motor(\"motor\")\n", " power_bus = PowerBus(\"bus\", num_in=1, num_out=2)\n", " grid = PowerSink(\"grid\")\n", "\n", " e1 = PowerConnection(turbine, \"power\", generator, \"power_in\", label=\"e1\")\n", " e2 = PowerConnection(generator, \"power_out\", power_bus, \"power_in1\", label=\"e2\")\n", " e3 = PowerConnection(power_bus, \"power_out1\", motor, \"power_in\", label=\"e3\")\n", " e4 = PowerConnection(motor, \"power_out\", pump, \"power\", label=\"e4\")\n", " e5 = PowerConnection(power_bus, \"power_out2\", grid, \"power\", label=\"e5\")\n", "\n", " nw.add_conns(e1, e2, e3, e4, e5)\n", "\n", " generator.set_attr(eta=0.98)\n", " motor.set_attr(eta=0.98)\n", "\n", " a1.set_attr(fluid={\"air\": 1}, T=200, p=1, m=10)\n", " b1.set_attr(fluid={\"Isopentane\": 1}, x=1, T=150)\n", " b3.set_attr(td_dew=10)\n", " b4.set_attr(td_bubble=5)\n", " b7.set_attr(td_bubble=5)\n", " c1.set_attr(fluid={\"air\": 1}, T=10, p=1)\n", " c2.set_attr(T=20)\n", "\n", " recuperator.set_attr(dp1=0, dp2=0)\n", " condenser.set_attr(dp1=0, dp2=0)\n", " preheater.set_attr(dp1=0, dp2=0)\n", " evaporator.set_attr(dp1=0, dp2=0)\n", "\n", " turbine.set_attr(eta_s=0.8)\n", " pump.set_attr(eta_s=0.7)\n", "\n", " condenser.set_attr(td_pinch=5)\n", " evaporator.set_attr(td_pinch=10)\n", " return nw" ] }, { "cell_type": "markdown", "id": "f7567dce", "metadata": {}, "source": [ "The model is well specified and solves without any issues:" ] }, { "cell_type": "code", "execution_count": null, "id": "d4aad625", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:37.949101Z", "iopub.status.busy": "2026-07-26T13:14:37.948989Z", "iopub.status.idle": "2026-07-26T13:14:38.067755Z", "shell.execute_reply": "2026-07-26T13:14:38.067290Z" } }, "outputs": [], "source": [ "nw = build_orc()\n", "nw.solve(\"design\")\n", "nw.assert_convergence()\n", "print(f\"net power: {nw.get_conn('e5').E.val:.1f} kW\")" ] }, { "cell_type": "markdown", "id": "8e4e046c", "metadata": {}, "source": [ "## Inspecting a successful solve\n", "\n", "Before breaking the model, we look at what the solver does with it when\n", "everything works. All inspection methods are available after\n", "{py:meth}`~tespy.networks.network.Network.presolve`, which prepares and\n", "presolves the problem but stops before solving.\n", "\n", "The original problem holds 57 variables. mass flow, pressure and enthalpy\n", "per :code:`Connection` and an energy flow per :code:`PowerConnection`. The\n", "reduction merges linearly coupled variables (e.g. all mass flows of the\n", "cycle, the pressures connected through the :code:`dp` specifications) and\n", "presolves everything the specifications determine directly. 13 variables\n", "remain:" ] }, { "cell_type": "code", "execution_count": null, "id": "ff33aab3", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.069320Z", "iopub.status.busy": "2026-07-26T13:14:38.069204Z", "iopub.status.idle": "2026-07-26T13:14:38.152281Z", "shell.execute_reply": "2026-07-26T13:14:38.151782Z" } }, "outputs": [], "source": [ "nw = build_orc()\n", "nw.presolve(\"design\")\n", "nw.print_variables()" ] }, { "cell_type": "markdown", "id": "07fb6279", "metadata": {}, "source": [ "Each remaining variable has a short label (e.g. :code:`h4`) and lists the\n", "original variables it represents. The same reduction applies to the equations.\n", "\n", "- :code:`nw.print_equations()`,\n", "- :code:`nw.print_presolved_variables()` and\n", "- :code:`nw.print_presolved_equations()` show the tables.\n", "\n", "Next, the solver analyses which equation determines which variable and in\n", "what order the equations can be processed, see\n", "{ref}`the decomposition section ` for how this\n", "works:" ] }, { "cell_type": "code", "execution_count": null, "id": "152b7dbd", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.153566Z", "iopub.status.busy": "2026-07-26T13:14:38.153476Z", "iopub.status.idle": "2026-07-26T13:14:38.156436Z", "shell.execute_reply": "2026-07-26T13:14:38.156006Z" } }, "outputs": [], "source": [ "nw.print_blocks()" ] }, { "cell_type": "markdown", "id": "f802a7bd", "metadata": {}, "source": [ "The decomposition allows to analyze the problem on the mathematical level. You\n", "will find that only the three equations fix the condensation state, i.e. the\n", "condenser pinch together with the two saturation offsets. Everything else\n", "follows sequentially: The isentropic efficiencies determine the machine outlet\n", "enthalpies, the energy balances determine the mass flows and the power\n", "connector balances determine the energy flows. The :code:`Needs` column lists\n", "which blocks must be solved before solving a block.\n", "\n", "The block lower triangular structure can also be displayed as an incidence\n", "matrix, with :code:`X` marking the entries of an equation within its own\n", "block and :code:`x` marking dependencies on variables of preceding blocks:" ] }, { "cell_type": "code", "execution_count": null, "id": "6cc5dc2b", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.157409Z", "iopub.status.busy": "2026-07-26T13:14:38.157335Z", "iopub.status.idle": "2026-07-26T13:14:38.160310Z", "shell.execute_reply": "2026-07-26T13:14:38.159924Z" } }, "outputs": [], "source": [ "nw.print_incidence_matrix(block_order=True)" ] }, { "cell_type": "markdown", "id": "b5d77000", "metadata": {}, "source": [ "The starting values generated for the remaining variables are inspected\n", "with :code:`print_variable_values`. All values are SI values:" ] }, { "cell_type": "code", "execution_count": null, "id": "85b9269f", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.161377Z", "iopub.status.busy": "2026-07-26T13:14:38.161279Z", "iopub.status.idle": "2026-07-26T13:14:38.164205Z", "shell.execute_reply": "2026-07-26T13:14:38.163772Z" } }, "outputs": [], "source": [ "nw.print_variable_values()" ] }, { "cell_type": "markdown", "id": "87806a6c", "metadata": {}, "source": [ "`solve_continue` runs the solution process on the prepared problem\n", "and finishes the calculation:" ] }, { "cell_type": "code", "execution_count": null, "id": "ef68fdb3", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.165308Z", "iopub.status.busy": "2026-07-26T13:14:38.165223Z", "iopub.status.idle": "2026-07-26T13:14:38.207956Z", "shell.execute_reply": "2026-07-26T13:14:38.207455Z" } }, "outputs": [], "source": [ "nw.solve_continue()\n", "print(nw.status)" ] }, { "cell_type": "markdown", "id": "d4c7eee6", "metadata": {}, "source": [ "## Structural errors\n", "\n", "The determination check compares the number of unknowns with the number of\n", "equations. With the maximum matching of the\n", "{ref}`decomposition ` the solver additionally\n", "locates the part of the problem in which a defect sits. Specifying the\n", "evaporator heat transfer on top of the well determined model will show what\n", "equations overdetermine which variable(s)." ] }, { "cell_type": "code", "execution_count": null, "id": "7a8013a1", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.209183Z", "iopub.status.busy": "2026-07-26T13:14:38.209102Z", "iopub.status.idle": "2026-07-26T13:14:38.414040Z", "shell.execute_reply": "2026-07-26T13:14:38.413594Z" }, "tags": [ "raises-exception" ] }, "outputs": [], "source": [ "nw = build_orc()\n", "nw.get_comp(\"evaporator\").set_attr(Q=-1e6)\n", "nw.solve(\"design\")" ] }, { "cell_type": "markdown", "id": "4887c13f", "metadata": {}, "source": [ "The structural analysis remains available after the error is raised and you can\n", "print the affected equations and variables:" ] }, { "cell_type": "code", "execution_count": null, "id": "0c060c2d", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.415899Z", "iopub.status.busy": "2026-07-26T13:14:38.415802Z", "iopub.status.idle": "2026-07-26T13:14:38.417972Z", "shell.execute_reply": "2026-07-26T13:14:38.417451Z" } }, "outputs": [], "source": [ "nw.print_structural_analysis()" ] }, { "cell_type": "markdown", "id": "0b6922bf", "metadata": {}, "source": [ "The evaporator heat transfer and its pinch specification both determine the\n", "enthalpy at the evaporator air outlet (:code:`h1`).\n", "\n", "Similarly, if we remove a specification from a fresh network instance, we\n", "produce an under-determined part:" ] }, { "cell_type": "code", "execution_count": null, "id": "466dc460", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.419196Z", "iopub.status.busy": "2026-07-26T13:14:38.419120Z", "iopub.status.idle": "2026-07-26T13:14:38.508135Z", "shell.execute_reply": "2026-07-26T13:14:38.507686Z" }, "tags": [ "raises-exception" ] }, "outputs": [], "source": [ "nw = build_orc()\n", "nw.get_conn(\"c2\").set_attr(T=None)\n", "nw.solve(\"design\")" ] }, { "cell_type": "code", "execution_count": null, "id": "f2e771f4", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.509936Z", "iopub.status.busy": "2026-07-26T13:14:38.509852Z", "iopub.status.idle": "2026-07-26T13:14:38.511799Z", "shell.execute_reply": "2026-07-26T13:14:38.511285Z" } }, "outputs": [], "source": [ "nw.print_structural_analysis()" ] }, { "cell_type": "markdown", "id": "ed6b77c2", "metadata": {}, "source": [ "The list is longer here because the missing equation propagates through\n", "multiple parts. without the air outlet temperature the condenser energy balance\n", "cannot determine the air mass flow, without the air mass flow the condensation\n", "level is open, and so on.\n", "\n", "A more subtle case is a **count-balanced but structurally singular**\n", "problem. If we remove the air outlet temperature and specify the evaporator\n", "heat transfer, we will see 14 equations for 14 variables. However, now the\n", "evaporator side is over-determined and the condenser side is under-determined\n", "at the same time. Block-wise solving is impossible in this case and the solver\n", "reports the structural analysis and exits with status 3. " ] }, { "cell_type": "code", "execution_count": null, "id": "8f14c81c", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.513382Z", "iopub.status.busy": "2026-07-26T13:14:38.513262Z", "iopub.status.idle": "2026-07-26T13:14:38.578744Z", "shell.execute_reply": "2026-07-26T13:14:38.578285Z" } }, "outputs": [], "source": [ "nw = build_orc()\n", "nw.get_conn(\"c2\").set_attr(T=None)\n", "nw.get_comp(\"evaporator\").set_attr(Q=-1e6)\n", "nw.solve(\"design\")\n", "print(nw.status)" ] }, { "cell_type": "markdown", "id": "06458b7d", "metadata": {}, "source": [ "Both defective parts are visible at once here:" ] }, { "cell_type": "code", "execution_count": null, "id": "26839e97", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.580361Z", "iopub.status.busy": "2026-07-26T13:14:38.580258Z", "iopub.status.idle": "2026-07-26T13:14:38.582069Z", "shell.execute_reply": "2026-07-26T13:14:38.581719Z" } }, "outputs": [], "source": [ "nw.print_structural_analysis()" ] }, { "cell_type": "markdown", "id": "00b19ff8", "metadata": {}, "source": [ "```{tip}\n", "The message also points to :code:`block_solve=False` to attempt the\n", "simultaneous solution anyway, for the case that the structural defect stems\n", "from an incomplete dependency declaration of a custom component rather than\n", "from the specifications.\n", "```\n", "\n", "The structural analysis works on the incidence only (which equation depends on\n", "which variable). A linear dependency can also arise from the values even when\n", "the structure is sound, e.g. redundant specifications connected through\n", "nonlinear relations or starting values on a saturation plateau. The solver then\n", "reports status 3 and the singularity diagnosis names the suspect equations in\n", "the log, see {ref}`the debugging section `.\n", "\n", "## Specifications and decomposition\n", "\n", "Changing a specification changes the block structure. In the base model the\n", "mass flow of the heat source is given and every block is determined in\n", "sequence. If we instead prescribe the net power of the plant, the mass flows\n", "can only be determined together with the energy flows through the bus balance:" ] }, { "cell_type": "code", "execution_count": null, "id": "139f5eee", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.583444Z", "iopub.status.busy": "2026-07-26T13:14:38.583367Z", "iopub.status.idle": "2026-07-26T13:14:38.648218Z", "shell.execute_reply": "2026-07-26T13:14:38.647622Z" } }, "outputs": [], "source": [ "nw = build_orc()\n", "nw.get_conn(\"a1\").set_attr(m=None)\n", "nw.get_conn(\"e5\").set_attr(E=180)\n", "nw.presolve(\"design\")\n", "nw.print_blocks()" ] }, { "cell_type": "markdown", "id": "e3d3267e", "metadata": {}, "source": [ "Block 4 now couples the cycle mass flow with the turbine and pump energy flows.\n", "The bus balance connects generator output and motor input, which are tied to\n", "the same mass flow through the connector balances. The larger the blocks of\n", "your model the harder will debugging be. However, especially in offdesign\n", "simulations tightly coupled problems that cannot easily be reduced to multiple\n", "subproblems are expected." ] }, { "cell_type": "markdown", "id": "3537f1ca", "metadata": {}, "source": [ "## Staged solving\n", "\n", "`solve` first executes `presolve` and subsequently `solve_continue`. You can\n", "pause the model execution between the two stages and inspect the problem, the\n", "variables and their values and even modify the initial guesses yourself (values\n", "are SI values). On this model the generated starting values are good enough\n", "that staging is not needed, therefore the cell only intends to show how the\n", "mechanism works." ] }, { "cell_type": "code", "execution_count": null, "id": "01c0654c", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.649503Z", "iopub.status.busy": "2026-07-26T13:14:38.649421Z", "iopub.status.idle": "2026-07-26T13:14:38.754100Z", "shell.execute_reply": "2026-07-26T13:14:38.753637Z" } }, "outputs": [], "source": [ "nw = build_orc()\n", "nw.presolve(\"design\")\n", "nw.set_variable_value(\"b4\", \"p\", 1.2e5)\n", "nw.solve_continue()\n", "print(nw.status)" ] }, { "cell_type": "markdown", "id": "f58f634b", "metadata": {}, "source": [ "## Pausing and inspecting a failed block\n", "\n", "This section will show how you can set starting values selectively for a\n", "failing block. To construct the example, we first run the simulation with a\n", "tiny pinch point value for the condenser, retrieve the resulting UA and impose\n", "it to a freshly constructed model. Then we see that the new solve will fail\n", "with a non-convergence warning." ] }, { "cell_type": "code", "execution_count": null, "id": "74268e78", "metadata": {}, "outputs": [], "source": [ "nw = build_orc()\n", "nw.get_comp(\"condenser\").set_attr(td_pinch=0.001)\n", "nw.solve(\"design\")\n", "nw.assert_convergence()\n", "UA_value = nw.get_comp(\"condenser\").UA.val" ] }, { "cell_type": "code", "execution_count": null, "id": "f781baf0", "metadata": {}, "outputs": [], "source": [ "nw.results[\"Connection\"].loc[[\"c1\", \"b3\", \"b4\"], [\"m\", \"p\", \"h\"]]" ] }, { "cell_type": "code", "execution_count": null, "id": "5c55955f", "metadata": { "execution": { "iopub.execute_input": "2026-07-26T13:14:38.756029Z", "iopub.status.busy": "2026-07-26T13:14:38.755936Z", "iopub.status.idle": "2026-07-26T13:14:38.932659Z", "shell.execute_reply": "2026-07-26T13:14:38.932037Z" } }, "outputs": [], "source": [ "nw = build_orc()\n", "nw.get_comp(\"condenser\").set_attr(td_pinch=None, UA=UA_value)\n", "nw.solve(\"design\", pause_on_block_failure=True)\n", "nw.status" ] }, { "cell_type": "markdown", "id": "f59d366e", "metadata": {}, "source": [ "We can inspect the fluid state at failure, the variables relevant to the block\n", "are marked with a star." ] }, { "cell_type": "code", "execution_count": null, "id": "16a97aaf", "metadata": {}, "outputs": [], "source": [ "nw.print_block_states(block=2, at=\"failure\")" ] }, { "cell_type": "markdown", "id": "dec47c95", "metadata": {}, "source": [ "And we can inspect the same before starting the block solve. Simultaneously, we\n", "can also inspect the variables directly when starting the block solve." ] }, { "cell_type": "code", "execution_count": null, "id": "bd99746f", "metadata": {}, "outputs": [], "source": [ "nw.print_block_states(block=2)\n", "nw.print_variable_values(block=2)" ] }, { "cell_type": "markdown", "id": "eed8a95d", "metadata": {}, "source": [ "We can transfer the variable values from the tiny pinch solution and continue\n", "solving. In this case we apply `osciallation_damping` to help the convergence,\n", "since a tiny change in any of the variables will trigger a sign change in the\n", "`UA` residual." ] }, { "cell_type": "code", "execution_count": null, "id": "631a686b", "metadata": {}, "outputs": [], "source": [ "nw.set_variable_value(\"b3\", \"h\", 347635)\n", "nw.set_variable_value(\"c1\", \"m\", 78)\n", "nw.set_variable_value(\"b4\", \"h\", -29941)\n", "nw.set_variable_value(\"b3\", \"p\", 754.166)\n", "nw.solve_continue()\n", "nw.status" ] }, { "cell_type": "markdown", "id": "208757cb", "metadata": {}, "source": [ "In case you want to continue solving without pausing on a failure, you can run\n", "`nw.solve_continue(pause_on_block_failure=False)`." ] } ], "metadata": { "kernelspec": { "display_name": "tespy (3.14.0.candidate.2)", "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.14.0rc2" } }, "nbformat": 4, "nbformat_minor": 5 }