{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Interfacing PowerModels.jl with pandapower" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "pandapower now has an interface to PowerModels.jl that can be used for efficient power system optimization.\n", "\n", "### What is PowerModels.jl and why should I use it?\n", "\n", "- [PowerModels.jl](https://lanl-ansi.github.io/PowerModels.jl/stable/) is package for steady-state power network optimization\n", "- It is based on the relatively new language [Julia](https://julialang.org/) which is gaining popularity in scientific applications\n", "- PowerModels uses Julia/JuMP for the optimization, which [clearly outperforms the Python alternative Pyomo](http://yetanothermathprogrammingconsultant.blogspot.com/2015/05/model-generation-in-julia.html)\n", "- PowerModels has a great modular design that allows you to define [different formulations for optimization problems](https://lanl-ansi.github.io/PowerModels.jl/stable/specifications/) based on different [network formulations](https://lanl-ansi.github.io/PowerModels.jl/stable/formulations/) as well as use several [relaxation schemes](https://lanl-ansi.github.io/PowerModels.jl/stable/relaxations/). You can then solve the problem using many open source as well as commercial solvers through [JuMP](http://www.juliaopt.org/JuMP.jl/0.18/installation.html#getting-solvers)\n", "\n", "### Well then why do I still need pandapower?\n", "\n", "Because pandapower:\n", "\n", "- allows you to easily define power systems with nameplate parameters and standard types\n", "- comes with thouroughly validated element models of transformers with tap changers, three-winding transformers, switches/breakers, extended ward equivalents and many more \n", "- keeps all data in tables (pandas DataFrames), which makes data management and analysis very comfortable\n", "- provides different power system analysis functions, such as a (very fast) power flow, short-circuit calculation, state estimation, graph searches and a plotting library that can be used on the same grid models\n", "- allows you to do all pre- and postprocessing in Python, which still has a much richer environment of free libraries than Julia (currently 157,755 packages on PyPI vs. 1,906 libraries on Pkg)\n", "\n", "So using pandapower to define the grid models and then using PowerModels for the optimization really gives you the best of all worlds - you can use the rich environment of Python libraries, the sophisticated element models of pandapower, the modular optimization framework of PowerModels and the efficient mathematical modeling of JuMP." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Let's get started\n", "\n", "So here is an example of how it works. First, we create a grid in pandapower. Here, we create a meshed 110kV grid with four buses that is fed from an 220kV network through a 3-Winding transformer." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "This pandapower network includes the following parameter tables:\n", " - bus (5 elements)\n", " - load (3 elements)\n", " - gen (3 elements)\n", " - line (4 elements)\n", " - trafo3w (1 element)\n", " - poly_cost (3 elements)\n", " - bus_geodata (5 elements)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandapower as pp\n", "import numpy as np\n", "net = pp.create_empty_network()\n", "\n", "min_vm_pu = 0.95\n", "max_vm_pu = 1.05\n", "\n", "#create buses\n", "bus1 = pp.create_bus(net, vn_kv=220., geodata=(5,9), min_vm_pu=min_vm_pu, max_vm_pu=max_vm_pu)\n", "bus2 = pp.create_bus(net, vn_kv=110., geodata=(6,10), min_vm_pu=min_vm_pu, max_vm_pu=max_vm_pu)\n", "bus3 = pp.create_bus(net, vn_kv=110., geodata=(10,9), min_vm_pu=min_vm_pu, max_vm_pu=max_vm_pu)\n", "bus4 = pp.create_bus(net, vn_kv=110., geodata=(8,8), min_vm_pu=min_vm_pu, max_vm_pu=max_vm_pu)\n", "bus5 = pp.create_bus(net, vn_kv=110., geodata=(6,8), min_vm_pu=min_vm_pu, max_vm_pu=max_vm_pu)\n", "\n", "#create 220/110/110 kV 3W-transformer\n", "pp.create_transformer3w_from_parameters(net, bus1, bus2, bus5, vn_hv_kv=220, vn_mv_kv=110,\n", " vn_lv_kv=110, vk_hv_percent=10., vk_mv_percent=10.,\n", " vk_lv_percent=10., vkr_hv_percent=0.5,\n", " vkr_mv_percent=0.5, vkr_lv_percent=0.5, pfe_kw=10,\n", " i0_percent=0.1, shift_mv_degree=0, shift_lv_degree=0,\n", " sn_hv_mva=100, sn_mv_mva=50, sn_lv_mva=50)\n", "\n", "#create 110 kV lines\n", "l1 = pp.create_line(net, bus2, bus3, length_km=70., std_type='149-AL1/24-ST1A 110.0')\n", "l2 = pp.create_line(net, bus3, bus4, length_km=50., std_type='149-AL1/24-ST1A 110.0')\n", "l3 = pp.create_line(net, bus4, bus2, length_km=40., std_type='149-AL1/24-ST1A 110.0')\n", "l4 = pp.create_line(net, bus4, bus5, length_km=30., std_type='149-AL1/24-ST1A 110.0')\n", "\n", "#create loads\n", "pp.create_load(net, bus2, p_mw=60)\n", "pp.create_load(net, bus3, p_mw=70)\n", "pp.create_load(net, bus4, p_mw=10)\n", "\n", "#create generators\n", "g1 = pp.create_gen(net, bus1, p_mw=40, min_p_mw=0, max_p_mw=200, vm_pu=1.01, slack=True)\n", "pp.create_poly_cost(net, g1, 'gen', cp1_eur_per_mw=1)\n", "\n", "g2 = pp.create_gen(net, bus3, p_mw=40, min_p_mw=0, max_p_mw=200, vm_pu=1.01)\n", "pp.create_poly_cost(net, g2, 'gen', cp1_eur_per_mw=3)\n", "\n", "g3 = pp.create_gen(net, bus4, p_mw=50, min_p_mw=0, max_p_mw=200, vm_pu=1.01)\n", "pp.create_poly_cost(net, g3, 'gen', cp1_eur_per_mw=3)\n", "net" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that PowerModels does not have a 3W-transformer model, but since pandapower includes the equations to calculates the equivalent branches for the 3W-transformers, it is possible to optimize grids with 3W-transformers in PowerModels through the pandapower interface. The same is true for other complex transformer models, switches/breaker, extended ward equivalents etc.\n", "\n", "Let's have a look at the grid we created with pandapowers plotting module:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "image/png": "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mDEXnTo/99xzio4u/4NwateurXbt2snpdAbslzQRXohoAAgfoRjOLgS0pEGDBqlKlSpavXq1duzY4ZdrXjipMNkzu06j16xZowcffFA2m00LFixQly5d1Lx5c73yyitasmSJDh8+XPIzH3/8sc6fP6/77rtP1157bZmvm52dra+//lrjx4/XLbfcopSUFE2fPl3nzp1T165dtXDhQo9OnUsL9FNOEH6IaAAIbaEczi7uP0A8QtSoUUMDBw7U1KlTNXnyZL333ns+v2ZFHilns9nUsWNHdezYUYcPH9a0adOUnp6un3/+WePGjSv5vgYNGqht27Zat26dJKlx48aaNWuW8vPz5XA4dPLkSWVmZsput18SIjVq1NCQIUM0YsSIkk9r9Ibr/RHQsJIrovnFQgAIHaV/OXDQoEEhFc0XivincLhkZmaqTZs2ql69ug4dOqSEhASfXq9r1676+uuvtWDBAt1///0Vfr2CggItX75c69atk91ul91uV25ursc/Hx8fr9atWys5OVnt27dXnz59VOVyn4ZjYPv27WrRooUaNWqkffv2Vfj1gAvxdA4ACH7B8lQNU+46mIC+wO23364NGzYoLS2t3F/sqwin06natWsrOztb+/btU6NGjSy/RlFRkX755Rc98MAD2rZtm26//XY1a9ZMeXl5io2NVVxcnBISEtSiRQslJyerefPmqlTJ+n8hUVhYqOrVq+vMmTMlm2/ASkQ0AASnUA1nF3cdzITjAs8884w2bNig//qv/1KfPn3UoEEDn1wnJydH2dnZuuKKK3TNNdf45BpRUVHKyMjQtm3bVK1aNS1evFhXXnmlT67lTnR0tJo3b66MjAzt2rWLgIblmHMAQHAJp6nG5fBLhBfo37+/7r33XuXk5OiJJ5647FMtKurcuXOSpKpVq/rsg1uOHDmip556SlLxx2wHIp5dqlatKun39w1YjV8sBIDAC4dfDvQUAX0Bm82mKVOmqHr16lqyZEnJc5qt5nA4JBXvjn3B6XRq5MiRys7O1j333KMhQ4b45Dqecr1P1/sGfIGIBoDAiKRwdiGgS7n66qv17rvvSpKefvppZWVlWX6NgoICSfLoWcremDNnjhYtWqRq1app6tSpfv148rK4ttWu9w34ChENAP4TieHsQkCXYfDgwerevbtOnjypQYMGKS8vz9LXj4uLkyTLX1eSfv75Zz355JOSiqcbvtpYm3CdPLveN+BLRDQA+FYkh7MLAV0G15SjTp06Wr16tR599FEVFhZa9vqukLR6E7x//37dfffdysnJUc+ePQM+3XBxvU8CGv5CRAOA9Qjn3xHQl9GwYUOtXLlS1apV0/z58zVkyBDLJgjVq1dXVFSUsrOzLYvoAwcOqEuXLjpw4IBuv/12zZkzJ+DTDZdDhw5JkmrVqhXgO0EkIaIBwBqE86UIaDfatGmjZcuWqUqVKpo5c6YeeughS2YXcXFxSkpKUmFhoTZv3lzh19u1a5c6dOigXbt2qXXr1lq6dKnPPwjGU9nZ2dqzZ4/i4+PVrFmzQN8OIgwRDQDeI5wvj4AuR4cOHbRq1SpVr15dCxYsULdu3bR///4Kv25ycrKkin/E9cqVK9WhQwft379ft956q/7973+rRo0aFb4/q2zcuFGS1KpVK598UAtQHiIaAMwQzuUjoD1w++23a82aNapbt67WrFmjli1baurUqRV6TnRFA/rUqVMaNmyYunXrpmPHjunuu+/W119/rZo1a3p9T77gen+u9wsEAhENAOUjnD1HQHuodevW2rJli+6//36dPn1aw4cPr9BpdEpKiiTvAnrlypVq2bKlpk2bptjYWI0fP17Lly8v+cCSYOJ6f673CwQKEQ0AZSOczRHQBurVq6cvvvhCn332ma688kqtWrVKLVu21MSJE5WdnW30Wq1atVJUVJS2bdum06dPe/Qz27Zt0+DBg9WtWzcdPHhQ7dq106ZNm/SXv/wlKOcRTqdTP/74oyROoBEciGgA+B3h7D0C2pDNZtOAAQO0ffv2ktPo559/Xg0bNtSQIUM8PlGuUqWKbr/9dhUWFuof//jHZb8vPz9fc+fOVadOndSyZUt9+umnJafOGzZsUFJSklVvzXJr1qzR/v371aBBAzVv3jzQtwNIIqIBgHCuOALaS67T6KVLl6pr165yOByaMWOGUlJSdMsttyg9PV1btmxx++i7kSNHSpLS0tIu2lPn5uZq7dq1eumll3TttdfqoYce0tq1a1W1alWNGDFCW7duDdpT5wulpaVJkoYPHx7094rIQkQDiESEs3VsTje/CZeSkqKMjAx/3k/I2rVrlyZPnqwZM2YoJyen5K/HxcWpVatWSk5OVnJysho1aqT4+HjFxcXJ4XCoZ8+eysnJ0ejRo5WTk6OMjAzt2rXroqBOSkrS6NGj9eijj6patWqBeHvGsrKy1KhRI0nSvn371LBhwwDfEXCpvXv3at68eerXr58aN24c6NsBAJ/Iz8/XTz/9pO+//16JiYlKTU0lmj3groMJaIudPXtWn3/+uVasWKGMjAzt3r3b+DViYmJ00003KSUlRY888ojuuOOOoPlQFE+9+uqr+utf/6q+fftq/vz5gb4d4LKIaADhinCuGAI6gHJycrRx40bZ7XZt3LhRx48fl8Ph0Llz5xQdHS1JysjIUFRUlN566y3dddddatmypWJjYwN8597Lz89X48aNlZWVpX//+9+68847A31LgFtENIBwQjhbw10HM0z1sZo1a6pz587q3LnzZb/ngQce0MKFC2W32/Xss8/68e58480331RWVpaSkpLUqVOnQN8OUC7XJpqIBhDKSofzoEGDCGcf4QQ6COzevVs33XSTzpw5oy+++EIPPPBAoG/Ja5mZmWrXrp0KCgq0Zs0adezYMdC3BHiMk2gAoYgTZ99w18E8hSMINGnSRG+99Zak4idznDhxIsB35J38/HwNHjxYBQUFevLJJ4lnhByezgEglPBUjcAhoIPEyJEj1alTJx07dkxPPfVUoG/HK2+++aY2b96sJk2aaPz48YG+HcArRDSAYEc4Bx4TjiBy4ZRj9uzZGjhwYKBvyWPff/+9UlNTmW4gbDDnABBsmGr4FxOOENGkSRNNnDhRkvTYY49pxYoVAb4jz2zdulU9evRQQUGBnn76aeIZYYGTaADBghPn4ENAB5kRI0Zo7NixOn/+vB544AGtXbs20Lfk1s6dO9W1a1fl5OSoT58+evvttwN9S4BliGgAgUQ4By8COsjYbDZNnDhRQ4cO1blz59StWzctX7480LdVpk2bNqlDhw46cuSI7rrrLs2ZM4eP7EbYIaIB+BvhHPwI6CBks9mUnp6u4cOHy+FwqHfv3poyZYrczNX97quvvlKnTp10/Phxde3aVYsXL1ZcXFygbwvwCSIagD8QzqGDgA5S0dHRmjx5sp577jkVFBToiSeeUPfu3XXw4MGA3tepU6c0dOhQde/eXbm5uerbt68WL16shISEgN4X4GtENABfIZxDDwEdxGw2m9566y3NmjVLNWvW1IoVK9SyZUvNmDEjIKfRrutPnz5dlStX1ltvvaXPP/9clStX9vu9AIFARAOw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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandapower.plotting as plot\n", "%matplotlib inline\n", "plot.simple_plot(net)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now lets run an OPF through PowerModels and look at the results (Note that the first time the runpm function is called, Julia is started in the background, which may take some time):" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "pp.runpm_ac_opf(net)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since Generator 1 has the lowest cost, all required power is supplied through this generator:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
p_mwq_mvarva_degreevm_pu
0144.85101711.0372177.117490e-271.050000
10.0000006.055925-1.680643e+011.013179
20.00000013.126070-1.298596e+011.040498
\n", "
" ], "text/plain": [ " p_mw q_mvar va_degree vm_pu\n", "0 144.851017 11.037217 7.117490e-27 1.050000\n", "1 0.000000 6.055925 -1.680643e+01 1.013179\n", "2 0.000000 13.126070 -1.298596e+01 1.040498" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.res_gen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This however leeds to an overload in the three-winding transformer, through which g1 is connected:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 190.835372\n", "Name: loading_percent, dtype: float64" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.res_trafo3w.loading_percent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's set some constraints for the 3W-transformer and the lines and rerun the OPF:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "net.trafo3w[\"max_loading_percent\"] = 50\n", "net.line[\"max_loading_percent\"] = 20\n", "pp.runpm_ac_opf(net)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The constraints are complied with for all lines and the 3W transformer:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 49.97477\n", "Name: loading_percent, dtype: float64" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.res_trafo3w.loading_percent" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 19.047619\n", "1 12.295418\n", "2 19.207577\n", "3 7.088595\n", "Name: loading_percent, dtype: float64" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.res_line.loading_percent" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The power is now generated by a mixture of the generators:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
p_mwq_mvarva_degreevm_pu
024.531626-6.2670182.961294e-281.031058
198.101643-7.928705-3.576364e-011.050000
218.0548989.928328-1.446870e+001.048757
\n", "
" ], "text/plain": [ " p_mw q_mvar va_degree vm_pu\n", "0 24.531626 -6.267018 2.961294e-28 1.031058\n", "1 98.101643 -7.928705 -3.576364e-01 1.050000\n", "2 18.054898 9.928328 -1.446870e+00 1.048757" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.res_gen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing the full functionality of PowerModels.jl" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Apart from the AC OPF used in the example above, pandapower also has an interface to run the DC OPF:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
vm_puva_degreep_mwq_mvarlam_plam_q
01.00.000000-23.521837NaN0.00.0
11.0-2.78361260.0000000.00.00.0
21.00.195199-29.310098NaN0.00.0
31.0-1.159257-7.168065NaN0.00.0
41.0-1.2646250.0000000.00.00.0
\n", "
" ], "text/plain": [ " vm_pu va_degree p_mw q_mvar lam_p lam_q\n", "0 1.0 0.000000 -23.521837 NaN 0.0 0.0\n", "1 1.0 -2.783612 60.000000 0.0 0.0 0.0\n", "2 1.0 0.195199 -29.310098 NaN 0.0 0.0\n", "3 1.0 -1.159257 -7.168065 NaN 0.0 0.0\n", "4 1.0 -1.264625 0.000000 0.0 0.0 0.0" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pp.runpm_dc_opf(net)\n", "net.res_bus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The julia file that is used to do that can be found in pandapower/pandapower/opf/run_powermodels_dc.jl and looks like this:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "using PowerModels\n", "using Ipopt\n", "using PP2PM\n", "\n", "function run_powermodels(json_path)\n", " pm = PP2PM.load_pm_from_json(json_path)\n", " result = PowerModels.run_dc_opf(pm, Ipopt.IpoptSolver(),\n", " setting = Dict(\"output\" => Dict(\"branch_flows\" => true)))\n", " return result\n", "end\n", "\"\"\";" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course PowerModels is a great modular tool that allows you to do much more than that. You might want to use a different OPF formulation, a relaxation method or a different solver. You might even want to use one of the variants of PowerModels that are being developed, such as [PowerModelsACDC.jl](https://github.com/hakanergun/PowerModelsACDC.jl) or [PowerModelsReliability.jl](https://github.com/frederikgeth/PowerModelsReliability.jl).\n", "\n", "To do that, you can switch out the standard file with your own custom .jl file. Lets say we want to run a power flow instead of an OPF. There is a custom julia file for that in pandapower/tutorials/run_powermodels_custom.jl that looks like this:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "using PowerModels\n", "using Ipopt\n", "import JSON\n", "\n", "function run_powermodels(json_path)\n", " pm = PP2PM.load_pm_from_json(json_path)\n", " result = PowerModels.run_pf(pm, ACPPowerModel, Ipopt.IpoptSolver(),\n", " setting = Dict(\"output\" => Dict(\"branch_flows\" => true)))\n", " return result\n", "end\n", "\"\"\";" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We point the runpm function to this file, and as we can see by the flat voltage values, the OPF is now run with a DC network formulation:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
vm_puva_degreep_mwq_mvarlam_plam_q
01.010000-6.034381e-36-51.203353-1.2450260.00.0
11.004756-5.426418e+0060.0000000.0000000.00.0
21.010000-6.495320e+0030.000000-15.2240750.00.0
31.010000-3.621699e+00-40.00000016.1423830.00.0
41.010243-3.216964e+000.0000000.0000000.00.0
\n", "
" ], "text/plain": [ " vm_pu va_degree p_mw q_mvar lam_p lam_q\n", "0 1.010000 -6.034381e-36 -51.203353 -1.245026 0.0 0.0\n", "1 1.004756 -5.426418e+00 60.000000 0.000000 0.0 0.0\n", "2 1.010000 -6.495320e+00 30.000000 -15.224075 0.0 0.0\n", "3 1.010000 -3.621699e+00 -40.000000 16.142383 0.0 0.0\n", "4 1.010243 -3.216964e+00 0.000000 0.000000 0.0 0.0" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pp.runpm(net, julia_file=\"run_powermodels_custom.jl\")\n", "net.res_bus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The PowerModels data structure that was passed to Julia can be accessed like this:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'1': {'index': 1,\n", " 'bus_i': 1,\n", " 'zone': 1,\n", " 'bus_type': 3,\n", " 'vmax': 1.05,\n", " 'vmin': 0.95,\n", " 'va': 0.0,\n", " 'vm': 1.01,\n", " 'base_kv': 220.0},\n", " '2': {'index': 2,\n", " 'bus_i': 2,\n", " 'zone': 1,\n", " 'bus_type': 1,\n", " 'vmax': 1.05,\n", " 'vmin': 0.95,\n", " 'va': 0.0,\n", " 'vm': 1.0,\n", " 'base_kv': 110.0},\n", " '3': {'index': 3,\n", " 'bus_i': 3,\n", " 'zone': 1,\n", " 'bus_type': 2,\n", " 'vmax': 1.05,\n", " 'vmin': 0.95,\n", " 'va': 0.0,\n", " 'vm': 1.01,\n", " 'base_kv': 110.0},\n", " '4': {'index': 4,\n", " 'bus_i': 4,\n", " 'zone': 1,\n", " 'bus_type': 2,\n", " 'vmax': 1.05,\n", " 'vmin': 0.95,\n", " 'va': 0.0,\n", " 'vm': 1.01,\n", " 'base_kv': 110.0},\n", " '5': {'index': 5,\n", " 'bus_i': 5,\n", " 'zone': 1,\n", " 'bus_type': 1,\n", " 'vmax': 1.05,\n", " 'vmin': 0.95,\n", " 'va': 0.0,\n", " 'vm': 1.0,\n", " 'base_kv': 110.0},\n", " '6': {'index': 6,\n", " 'bus_i': 6,\n", " 'zone': 1,\n", " 'bus_type': 1,\n", " 'vmax': 1.05,\n", " 'vmin': 0.95,\n", " 'va': 0.0,\n", " 'vm': 1.0,\n", " 'base_kv': 220.0}}" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net._pm[\"bus\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is also a callback that allows you to add additional data to the PowerModels data structure in case it is not already added by the pandapower/PowerModels interface. In the callback you can add any data from the net, ppc or any source:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo\n", "50.0\n" ] } ], "source": [ "def add_data(net, ppc, pm):\n", " pm[\"gen\"][\"1\"][\"bar\"] = \"foo\"\n", " pm[\"f_hz\"] = net.f_hz \n", "\n", "pp.runpm(net, julia_file=\"run_powermodels_custom.jl\", pp_to_pm_callback=add_data)\n", "print(net._pm[\"gen\"][\"1\"][\"bar\"])\n", "print(net._pm[\"f_hz\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These variables can now also be accessed on the Julia side, in case you need some more variables for custom optimizations.\n", "\n", "Keep in mind that indices in PowerModels are 1-based so that the indices are shifted by one between ppc and pm. Furthermore, the net might contain some elements that are not in the ppc, as they are out of service or disconnected, which is why the indices of all elements have to be identified through the lookup tables in `net._pd2ppc_lookups`.\n", "\n", "Some notes on the internal data structure can be found in [internal_datastructure.ipynb](internal_datastructure.ipynb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Timings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Comparing the runopp function (that runs an OPF through PYPOWER) and the runpm function shows that PowerModels is much more performant:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.22 s ± 14.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%timeit pp.runopp(net)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "221 ms ± 7.18 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], "source": [ "%timeit pp.runpm_ac_opf(net)" ] } ], "metadata": { "anaconda-cloud": {}, "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.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }