{ "cells": [ { "cell_type": "markdown", "id": "08f22ef2", "metadata": {}, "source": [ "# A Large and Complex Wind Farm" ] }, { "cell_type": "markdown", "id": "3da71e0a-3861-4a7c-8eaa-de84bce11338", "metadata": {}, "source": [ "This notebook demonstrates how to apply *OptiWindNet* to optimize electrical network (cable routing) for a large-scale wind farm with 122 turbines and 2 substations, set in a concave area containing an internal obstacle. We will use `EWRouter` for warm-starting, then run `MILPRouter` with the COIN-OR CBC solver to find a high-quality branched cable network. The process covers loading real-world location data, generating and visualizing warm-start solutions, configuring solver and model options, and plotting the final optimized network.\n", "\n", "\n", "> Note: Any additional geometric complexity will have negligible impact on solving time, as they do not increase the number of variables in the MILP optimization model.\n", "\n", "> This notebook uses *COIN-OR CBC* as solver, but examples for other solvers (*gurobi, cplex, ortools, scip, highs*) can be found in the *MILPRouter* notebooks. Gurobi and IBM ILOG CPLEX are comercial solvers (academic license available). Google's OR-Tools, COIN-OR CBC, SCIP and HiGHS are open-source software." ] }, { "cell_type": "code", "execution_count": 1, "id": "a1ae5f35-904c-48de-a19a-4be303e46327", "metadata": {}, "outputs": [], "source": [ "from optiwindnet.api import WindFarmNetwork, EWRouter, MILPRouter, ModelOptions, load_repository" ] }, { "cell_type": "code", "execution_count": 2, "id": "d9337d43", "metadata": {}, "outputs": [], "source": [ "# Display figures as SVG in Jupyter notebooks\n", "%config InlineBackend.figure_formats = ['svg']" ] }, { "cell_type": "markdown", "id": "c37992fa-ecd0-4eec-9485-01c721213b72", "metadata": {}, "source": [ "## Load input data\n", "\n", "> Note: the `load_repository()` functionality of *OptiWindNet* is used to load a prebuilt *Networkx.Graph* of the avaible locations. For more details on this functionality look into the notebook about [Load repositories containing location data](a03_load_repositories.ipynb)." ] }, { "cell_type": "code", "execution_count": 3, "id": "9e47e5ac-661d-4084-ba6b-157aebe72cd8", "metadata": {}, "outputs": [], "source": [ "locations = load_repository()" ] }, { "cell_type": "markdown", "id": "13a15e55-d94a-4af3-a2a2-a400367f926f", "metadata": {}, "source": [ "The example location from (Taylor_2023) provides suitable specifications for our purpose; a large wind farm with multiple substations and complex geometry." ] }, { "cell_type": "code", "execution_count": 4, "id": "e8f1b4f3", "metadata": {}, "outputs": [], "source": [ "wfn = WindFarmNetwork(L=locations.taylor_2023, cables=[(5, 1800.0), (8, 2200.0)])" ] }, { "cell_type": "markdown", "id": "d3eaf120", "metadata": {}, "source": [ "> Note: To view output plots, trust this notebook first." ] }, { "cell_type": "code", "execution_count": 5, "id": "c3576c4e", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn" ] }, { "cell_type": "markdown", "id": "cbe51c02", "metadata": {}, "source": [ "There are two options for generating an initial solution to warm-start the `MILPRouter`:\n", "- heuristic: `EWRouter()`\n", "- meta-heuristic: `HGSRouter()`\n", "\n", "`EWRouter` has the downside of not limiting the number of feeders, thus the model to be warm-started must also have an unlimited number of feeders. This is usually not a problem, as most cable route sets produced by the `MILPRouter` uses the minimum number of feeders or only one more than the minimum." ] }, { "cell_type": "markdown", "id": "9bb10ad9", "metadata": {}, "source": [ "## Generate the warm-start solution" ] }, { "cell_type": "markdown", "id": "59c0d61e-4daf-44f3-af91-f096952f07ab", "metadata": {}, "source": [ "To generate a warm-start solution, simply run `wfn.optimize()` with the desired solver e.g.:\n", "\n", "```python\n", "wfn.optimize(router=warmstart_router)\n", "```\n", "\n", "In this example, we use `EWRouter` to warm-start the optimization model. Alternatively, `HGSRouter()` can be used for the same purpose.\n", "\n", "The resulting solution is stored within the `wfn` object. The next time `wfn.optimize()` is called, the stored solution (`S` which contains *selected-links*) will be reused as a warm start; provided that the solver in `MILPRouter` supports warm-starting.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "afb280cc", "metadata": {}, "outputs": [], "source": [ "warmstart_router = EWRouter()\n", "res_warmstart= wfn.optimize(router=warmstart_router)" ] }, { "cell_type": "code", "execution_count": 7, "id": "b0048736", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "113112.15297348917" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn.length()" ] }, { "cell_type": "markdown", "id": "cff4d530-ff92-483f-9a65-43bc6c89f08c", "metadata": {}, "source": [ "### Visualizing the warm-start solution" ] }, { "cell_type": "markdown", "id": "81de3bb0", "metadata": {}, "source": [ "`S` can be visualized using `wfn.plot_selected_links()`.\n", "\n", "> Check [the plotting tutorial](a04_Plotting.ipynb) for details on the available plots." ] }, { "cell_type": "code", "execution_count": 8, "id": "29fc5b50", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "216613758 €Σλ = 113112.0 m(+3) [-1]: 9, [-2]: 10κ = 8, T = 122" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn" ] }, { "cell_type": "markdown", "id": "5f88cdac", "metadata": {}, "source": [ "## MILP Router" ] }, { "cell_type": "markdown", "id": "966e3317", "metadata": {}, "source": [ "Create an instance of `MILPRouter` and choose COIN-OR Branch and Cut (CBC) as solver." ] }, { "cell_type": "markdown", "id": "708a88ce", "metadata": {}, "source": [ "When initializing a `MILPRouter` three arguments are required to be given:\n", "- solver name\n", "- time limit\n", "- gap" ] }, { "cell_type": "markdown", "id": "79b7673d", "metadata": {}, "source": [ "Solver options, including those set by OptiWindNet as well as additional configurable parameters, can be modified by creating a **dictionary** and passing it to the router. The same approach applies to **model options**. For a detailed explanation about model_options and solver_options of MILP routers see the notebook about [ModelOptions vs SolverOptions](a08_ModelOptions.ipynb)." ] }, { "cell_type": "code", "execution_count": 9, "id": "5c277e03", "metadata": {}, "outputs": [], "source": [ "solver_options=dict( # these are in addition to the default solver.options\n", " # if repeatable results are desired, set the seed\n", " RandomCbcSeed=1234567,\n", " # CBC works better if the number threads is set to the number of physical cores\n", " threads=6,\n", " )\n", "\n", "model_options = ModelOptions(\n", " topology='branched',\n", " feeder_limit='unlimited',\n", " feeder_route='segmented',\n", " )\n", "\n", "milp_router = MILPRouter(solver_name='cbc', time_limit=30, mip_gap=0.005, solver_options=solver_options, model_options=model_options)" ] }, { "cell_type": "markdown", "id": "eda1ab2b", "metadata": {}, "source": [ "### Run optimizatin with MILP router" ] }, { "cell_type": "code", "execution_count": 10, "id": "8783cc8f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using warm start: the model is initialized with the provided solution S.\n", "\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:optiwindnet.MILP.pyomo:>>> cbc solver options <<<\n", "Dins: 'on'\n", "RandomCbcSeed: 1234567\n", "Rens: 'on'\n", "Rins: 'on'\n", "VndVariableNeighborhoodSearch: 'on'\n", "cliqueCuts: 'off'\n", "flowCoverCuts: 'on'\n", "gomoryCuts: 'on'\n", "knapsackCuts: 'off'\n", "liftAndProjectCuts: 'off'\n", "mixedIntegerRoundingCuts: 'on'\n", "nodeStrategy: 'downFewest'\n", "pivotAndComplement: 'off'\n", "probingCuts: 'off'\n", "proximitySearch: 'off'\n", "ratioGap: 0.005\n", "residualCapacityCuts: 'off'\n", "seconds: 30\n", "threads: 6\n", "timeMode: 'elapsed'\n", "twoMirCuts: 'off'\n", "zeroHalfCuts: 'off'\n", "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Welcome to the CBC MILP Solver \n", "Version: 2.10.8 \n", "Build Date: Jan 1 1970 \n", "\n", "command line - C:\\my_program_files\\cbc\\bin\\cbc.exe -threads 6 -timeMode elapsed -nodeStrategy downFewest -Dins on -VndVariableNeighborhoodSearch on -Rens on -Rins on -pivotAndComplement off -proximitySearch off -gomoryCuts on -mixedIntegerRoundingCuts on -flowCoverCuts on -cliqueCuts off -twoMirCuts off -knapsackCuts off -probingCuts off -zeroHalfCuts off -liftAndProjectCuts off -residualCapacityCuts off -RandomCbcSeed 1234567 -seconds 30 -ratioGap 0.005 -printingOptions all -import C:\\Users\\amia\\AppData\\Local\\Temp\\tmpallszsg2.pyomo.lp -mipstart \\Users\\amia\\AppData\\Local\\Temp\\tmpkwuu819e.cbc.soln -stat=1 -solve -solu C:\\Users\\amia\\AppData\\Local\\Temp\\tmpallszsg2.pyomo.soln (default strategy 1)\n", "threads was changed from 0 to 6\n", "Option for timeMode changed from cpu to elapsed\n", "Option for nodeStrategy changed from fewest to downfewest\n", "Option for Dins changed from off to on\n", "Option for VndVariableNeighborhoodSearch changed from off to on\n", "Option for Rens changed from off to on\n", "Option for gomoryCuts changed from ifmove to on\n", "Option for mixedIntegerRoundingCuts changed from ifmove to on\n", "Option for flowCoverCuts changed from ifmove to on\n", "Option for cliqueCuts changed from ifmove to off\n", "Option for twoMirCuts changed from root to off\n", "Option for knapsackCuts changed from ifmove to off\n", "Option for probingCuts changed from on to off\n", "Option for zeroHalfCuts changed from ifmove to off\n", "randomCbcSeed was changed from -1 to 1234567\n", "seconds was changed from 1e+100 to 30\n", "ratioGap was changed from 0 to 0.005\n", "Option for printingOptions changed from normal to all\n", "opening mipstart file \\Users\\amia\\AppData\\Local\\Temp\\tmpkwuu819e.cbc.soln.\n", "MIPStart values read for 244 variables.\n", "Presolve 4314 (-2) rows, 2891 (-1) columns and 14858 (-1442) elements\n", "Statistics for presolved model\n", "Original problem has 2892 integers (1446 of which binary)\n", "Presolved problem has 2891 integers (1445 of which binary)\n", "==== 1446 zero objective 840 different\n", "==== absolute objective values 840 different\n", "==== for integers 1446 zero objective 840 different\n", "==== for integers absolute objective values 840 different\n", "===== end objective counts\n", "\n", "\n", "Problem has 4314 rows, 2891 columns (1445 with objective) and 14858 elements\n", "Column breakdown:\n", "0 of type 0.0->inf, 1446 of type 0.0->up, 0 of type lo->inf, \n", "0 of type lo->up, 0 of type free, 0 of type fixed, \n", "0 of type -inf->0.0, 0 of type -inf->up, 1445 of type 0.0->1.0 \n", "Row breakdown:\n", "0 of type E 0.0, 243 of type E 1.0, 0 of type E -1.0, \n", "1 of type E other, 0 of type G 0.0, 0 of type G 1.0, \n", "1 of type G other, 2891 of type L 0.0, 1054 of type L 1.0, \n", "124 of type L other, 0 of type Range 0.0->1.0, 0 of type Range other, \n", "0 of type Free \n", "Continuous objective value is 104281 - 0.06 seconds\n", "Cgl0003I 0 fixed, 0 tightened bounds, 528 strengthened rows, 0 substitutions\n", "Cgl0003I 0 fixed, 0 tightened bounds, 528 strengthened rows, 0 substitutions\n", "Cgl0003I 0 fixed, 0 tightened bounds, 202 strengthened rows, 0 substitutions\n", "Cgl0003I 0 fixed, 0 tightened bounds, 121 strengthened rows, 0 substitutions\n", "Cgl0004I processed model has 3881 rows, 2892 columns (2892 integer (1446 of which binary)) and 15680 elements\n", "Cbc0045I MIPStart provided solution with cost 113017\n", "Cbc0012I Integer solution of 113017.26 found by Reduced search after 0 iterations and 0 nodes (0.28 seconds)\n", "Cbc0038I Full problem 3882 rows 2892 columns, reduced to 255 rows 151 columns\n", "Cbc0038I Full problem 3882 rows 2892 columns, reduced to 255 rows 151 columns\n", "Cbc0038I Full problem 3882 rows 2892 columns, reduced to 255 rows 151 columns\n", "Cbc0038I Full problem 3881 rows 2892 columns, reduced to 405 rows 226 columns\n", "Cbc0038I Full problem 3882 rows 2892 columns, reduced to 292 rows 181 columns\n", "Cbc0038I Full problem 3882 rows 2892 columns, reduced to 292 rows 181 columns\n", "Cbc0038I Full problem 3882 rows 2892 columns, reduced to 292 rows 181 columns\n", "Cbc0031I 88 added rows had average density of 88.568182\n", "Cbc0013I At root node, 88 cuts changed objective from 104281.43 to 104895.91 in 19 passes\n", "Cbc0014I Cut generator 0 (Gomory) - 230 row cuts average 346.8 elements, 0 column cuts (0 active) in 0.486 seconds - new frequency is 1\n", "Cbc0014I Cut generator 1 (MixedIntegerRounding2) - 2 row cuts average 356.0 elements, 0 column cuts (0 active) in 0.070 seconds - new frequency is 3\n", "Cbc0014I Cut generator 2 (FlowCover) - 163 row cuts average 16.7 elements, 0 column cuts (0 active) in 0.030 seconds - new frequency is 1\n", "Cbc0010I After 0 nodes, 1 on tree, 113017.26 best solution, best possible 104895.91 (1.99 seconds)\n", "Cbc0010I After 100 nodes, 60 on tree, 113017.26 best solution, best possible 104952.14 (7.87 seconds)\n", "Cbc0010I After 200 nodes, 109 on tree, 113017.26 best solution, best possible 104952.14 (10.44 seconds)\n", "Cbc0010I After 300 nodes, 162 on tree, 113017.26 best solution, best possible 104952.14 (12.46 seconds)\n", "Cbc0010I After 400 nodes, 213 on tree, 113017.26 best solution, best possible 104952.14 (14.34 seconds)\n", "Cbc0010I After 500 nodes, 264 on tree, 113017.26 best solution, best possible 104952.14 (15.93 seconds)\n", "Cbc0010I After 600 nodes, 313 on tree, 113017.26 best solution, best possible 104952.14 (17.23 seconds)\n", "Cbc0010I After 700 nodes, 360 on tree, 113017.26 best solution, best possible 104952.14 (18.35 seconds)\n", "Cbc0010I After 800 nodes, 375 on tree, 113017.26 best solution, best possible 104952.14 (19.10 seconds)\n", "Cbc0010I After 900 nodes, 400 on tree, 113017.26 best solution, best possible 104952.14 (19.70 seconds)\n", "Cbc0010I After 1000 nodes, 423 on tree, 113017.26 best solution, best possible 104952.14 (20.19 seconds)\n", "Cbc0010I After 1100 nodes, 416 on tree, 113017.26 best solution, best possible 104952.14 (20.50 seconds)\n", "Cbc0010I After 1200 nodes, 410 on tree, 113017.26 best solution, best possible 104952.14 (20.87 seconds)\n", "Cbc0010I After 1300 nodes, 406 on tree, 113017.26 best solution, best possible 104952.14 (21.23 seconds)\n", "Cbc0010I After 1400 nodes, 407 on tree, 113017.26 best solution, best possible 104952.14 (21.71 seconds)\n", "Cbc0010I After 1500 nodes, 391 on tree, 113017.26 best solution, best possible 104952.14 (22.33 seconds)\n", "Cbc0010I After 1600 nodes, 413 on tree, 113017.26 best solution, best possible 104952.14 (22.98 seconds)\n", "Cbc0010I After 1700 nodes, 417 on tree, 113017.26 best solution, best possible 104952.14 (23.43 seconds)\n", "Cbc0010I After 1800 nodes, 428 on tree, 113017.26 best solution, best possible 104952.14 (23.91 seconds)\n", "Cbc0010I After 1900 nodes, 461 on tree, 113017.26 best solution, best possible 104952.14 (24.36 seconds)\n", "Cbc0010I After 2000 nodes, 481 on tree, 113017.26 best solution, best possible 104952.14 (24.80 seconds)\n", "Cbc0010I After 2100 nodes, 484 on tree, 113017.26 best solution, best possible 104952.14 (25.23 seconds)\n", "Cbc0010I After 2200 nodes, 480 on tree, 113017.26 best solution, best possible 104952.14 (25.71 seconds)\n", "Cbc0010I After 2300 nodes, 482 on tree, 113017.26 best solution, best possible 104952.14 (26.12 seconds)\n", "Cbc0010I After 2400 nodes, 476 on tree, 113017.26 best solution, best possible 104952.14 (26.48 seconds)\n", "Cbc0010I After 2500 nodes, 496 on tree, 113017.26 best solution, best possible 104952.14 (26.95 seconds)\n", "Cbc0010I After 2600 nodes, 498 on tree, 113017.26 best solution, best possible 104952.14 (27.27 seconds)\n", "Cbc0010I After 2700 nodes, 517 on tree, 113017.26 best solution, best possible 104952.14 (27.72 seconds)\n", "Cbc0010I After 2800 nodes, 537 on tree, 113017.26 best solution, best possible 104952.14 (28.22 seconds)\n", "Cbc0010I After 2900 nodes, 571 on tree, 113017.26 best solution, best possible 104952.14 (28.77 seconds)\n", "Cbc0010I After 3000 nodes, 590 on tree, 113017.26 best solution, best possible 104952.14 (29.30 seconds)\n", "Cbc0030I Thread 0 used 515 times, waiting to start 0.6256721, 2947 locks, 0.21976614 locked, 0.018067122 waiting for locks\n", "Cbc0030I Thread 1 used 517 times, waiting to start 0.42010474, 2975 locks, 0.20623255 locked, 0.027341366 waiting for locks\n", "Cbc0030I Thread 2 used 495 times, waiting to start 1.1103261, 2838 locks, 0.20609736 locked, 0.03138876 waiting for locks\n", "Cbc0030I Thread 3 used 511 times, waiting to start 1.4357114, 2921 locks, 0.20849109 locked, 0.020550966 waiting for locks\n", "Cbc0030I Thread 4 used 523 times, waiting to start 1.6045356, 2968 locks, 0.22195411 locked, 0.031655073 waiting for locks\n", "Cbc0030I Thread 5 used 536 times, waiting to start 1.752903, 3048 locks, 0.20871186 locked, 0.032920122 waiting for locks\n", "Cbc0030I Main thread 26.558228 waiting for threads, 6230 locks, 0.003030777 locked, 0.025787354 waiting for locks\n", "Cbc0020I Exiting on maximum time\n", "Cbc0005I Partial search - best objective 113017.26 (best possible 104952.14), took 188757 iterations and 3092 nodes (30.16 seconds)\n", "Cbc0032I Strong branching done 17988 times (489750 iterations), fathomed 292 nodes and fixed 891 variables\n", "Cbc0035I Maximum depth 99, 88349 variables fixed on reduced cost\n", "Cuts at root node changed objective from 104281 to 104896\n", "Gomory was tried 1161 times and created 2367 cuts of which 0 were active after adding rounds of cuts (6.767 seconds)\n", "MixedIntegerRounding2 was tried 1581 times and created 922 cuts of which 0 were active after adding rounds of cuts (3.641 seconds)\n", "FlowCover was tried 1161 times and created 5082 cuts of which 0 were active after adding rounds of cuts (1.684 seconds)\n", "\n", "Result - Stopped on time limit\n", "\n", "Objective value: 113017.25676634\n", "Lower bound: 104952.135\n", "Gap: 0.08\n", "Enumerated nodes: 3092\n", "Total iterations: 188757\n", "Time (CPU seconds): 30.25\n", "Time (Wallclock seconds): 30.25\n", "\n", "Total time (CPU seconds): 30.69 (Wallclock seconds): 30.69\n", "\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:optiwindnet.MILP.pyomo:>>> Solution <<<\n", "SolutionInfo(runtime=30.69, bound=104952.14, objective=113017.25676634, relgap=0.07136181674462683, termination='maxTimeLimit')\n", "\n" ] } ], "source": [ "# Set logging level to INFO to display messages about solver options\n", "import logging\n", "logging.basicConfig(level=logging.INFO)\n", "\n", "# run optimization with the milp router (Set verbose=True to display messages about solver progress)\n", "res = wfn.optimize(router=milp_router, verbose=True)" ] }, { "cell_type": "code", "execution_count": 11, "id": "39ec2a2c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'runtime': 30.69,\n", " 'bound': 104952.14,\n", " 'objective': 113017.25676634,\n", " 'relgap': 0.07136181674462683,\n", " 'termination': 'maxTimeLimit'}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn.solution_info()" ] }, { "cell_type": "code", "execution_count": 12, "id": "a9ccd926", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "113112.15297348917" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn.G.size(weight='length')" ] }, { "cell_type": "code", "execution_count": 13, "id": "197f3308", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "113112.15297348917" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn.length()" ] }, { "cell_type": "markdown", "id": "9a449527", "metadata": {}, "source": [ "Terse link of the optimized network is returned as output of `wfn.optimize()`. If not assigned to a variable (e.g. res), it can be easily accessed via `wfn.terse_links()`." ] }, { "cell_type": "code", "execution_count": 14, "id": "00effc9d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 6, 0, 1, 10, 3, -2, 5, 14, 7, 10, 11, 20, -2,\n", " 12, 13, 16, 17, 18, 19, 29, -2, 14, 31, 15, 25, 26,\n", " 27, 28, -2, -2, -2, 41, 42, 34, 35, 36, 37, 38, -2,\n", " -2, -2, 42, 53, 44, 45, 55, 47, 48, 49, 39, 40, 50,\n", " 64, 54, 66, 67, 68, 69, 59, 49, -2, 60, 51, 62, 65,\n", " 77, -1, 68, 81, 70, 83, 59, 60, 61, 73, 63, 75, 78,\n", " 79, 80, -1, -1, -1, 82, 83, 84, 85, 74, 87, 90, 91,\n", " -1, -1, -1, 93, 94, 95, 96, 87, 100, 101, 92, -1, 94,\n", " 103, 104, 114, 106, 100, -1, 102, 110, 111, 112, 113, 109, 115,\n", " 121, 117, 118, 116, 120])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res" ] }, { "cell_type": "markdown", "id": "44d06e34-832a-49fd-9a49-6ecb0ed431ee", "metadata": {}, "source": [ "### Plot the optimized network graph" ] }, { "cell_type": "code", "execution_count": 15, "id": "42b84cd0-d148-4e5d-907c-7de61dc17450", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "216613758 €Σλ = 113112.0 m(+3) [-1]: 9, [-2]: 10κ = 8, T = 122" ], "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn" ] } ], "metadata": { "kernelspec": { "display_name": "OptiWindNet", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.12.9" } }, "nbformat": 4, "nbformat_minor": 5 }