Minimal 3-node example of PyPSA linear optimal power flow

Available as a Jupyter notebook at http://www.pypsa.org/examples/minimal_example_lopf.ipynb.

In [1]:
# make the code as Python 3 compatible as possible
from __future__ import print_function, division
import pypsa
import numpy as np
In [2]:
network = pypsa.Network()
In [3]:
#add three buses
for i in range(3):
    network.add("Bus","My bus {}".format(i))
In [4]:
print(network.buses)
attribute  v_nom type    x    y carrier  v_mag_pu_set  v_mag_pu_min  \
My bus 0     1.0       0.0  0.0      AC           1.0           0.0   
My bus 1     1.0       0.0  0.0      AC           1.0           0.0   
My bus 2     1.0       0.0  0.0      AC           1.0           0.0   

attribute  v_mag_pu_max control sub_network  
My bus 0            inf      PQ              
My bus 1            inf      PQ              
My bus 2            inf      PQ              
In [5]:
#add three lines in a ring
for i in range(3):
    network.add("Line","My line {}".format(i),
                bus0="My bus {}".format(i),
                bus1="My bus {}".format((i+1)%3),
                x=0.0001,
                s_nom=60)
In [6]:
print(network.lines)
attribute      bus0      bus1 type       x    r    g    b  s_nom  \
My line 0  My bus 0  My bus 1       0.0001  0.0  0.0  0.0   60.0   
My line 1  My bus 1  My bus 2       0.0001  0.0  0.0  0.0   60.0   
My line 2  My bus 2  My bus 0       0.0001  0.0  0.0  0.0   60.0   

attribute  s_nom_extendable  s_nom_min    ...      terrain_factor  \
My line 0             False        0.0    ...                 1.0   
My line 1             False        0.0    ...                 1.0   
My line 2             False        0.0    ...                 1.0   

attribute  num_parallel  v_ang_min  v_ang_max  sub_network  x_pu  r_pu g_pu  \
My line 0           1.0       -inf        inf                0.0   0.0  0.0   
My line 1           1.0       -inf        inf                0.0   0.0  0.0   
My line 2           1.0       -inf        inf                0.0   0.0  0.0   

attribute  b_pu  s_nom_opt  
My line 0   0.0        0.0  
My line 1   0.0        0.0  
My line 2   0.0        0.0  

[3 rows x 23 columns]
In [7]:
#add a generator at bus 0
network.add("Generator","My gen 0",
            bus="My bus 0",
            p_nom=100,
            marginal_cost=50)

#add a generator at bus 1
network.add("Generator","My gen 1",
            bus="My bus 1",
            p_nom=100,
            marginal_cost=25)
In [8]:
print(network.generators)
attribute       bus control type  p_nom  p_nom_extendable  p_nom_min  \
My gen 0   My bus 0      PQ       100.0             False        0.0   
My gen 1   My bus 1      PQ       100.0             False        0.0   

attribute  p_nom_max  p_min_pu  p_max_pu  p_set    ...      start_up_cost  \
My gen 0         inf       0.0       1.0    0.0    ...                0.0   
My gen 1         inf       0.0       1.0    0.0    ...                0.0   

attribute  shut_down_cost min_up_time  min_down_time  initial_status  \
My gen 0              0.0           0              0               1   
My gen 1              0.0           0              0               1   

attribute  ramp_limit_up  ramp_limit_down  ramp_limit_start_up  \
My gen 0             NaN              NaN                  1.0   
My gen 1             NaN              NaN                  1.0   

attribute  ramp_limit_shut_down  p_nom_opt  
My gen 0                    1.0        0.0  
My gen 1                    1.0        0.0  

[2 rows x 27 columns]
In [9]:
print(network.generators.p_set)
My gen 0    0.0
My gen 1    0.0
Name: p_set, dtype: float64
In [10]:
#add a load at bus 2
network.add("Load","My load",
            bus="My bus 2",
            p_set=100)
In [11]:
print(network.loads)
attribute       bus type  p_set  q_set  sign
My load    My bus 2       100.0    0.0  -1.0
In [12]:
print(network.loads.p_set)
My load    100.0
Name: p_set, dtype: float64
In [13]:
#Do a linear OPF

def my_f(network,snapshots):
    print(snapshots)


network.lopf(extra_functionality=my_f)
INFO:pypsa.pf:Slack bus for sub-network 0 is My bus 0
INFO:pypsa.opf:Performed preliminary steps
INFO:pypsa.opf:Building pyomo model using `angles` formulation
INFO:pypsa.opf:Solving model using glpk
Index(['now'], dtype='object')
INFO:pypsa.opf:Optimization successful
Out[13]:
('ok', 'optimal')
In [14]:
#Cheap generator 1 cannot be fully dispatched because of network constraints,
#so expensive generator 0 also has to dispatch
print(network.generators_t.p)
     My gen 0  My gen 1
now      20.0      80.0
In [15]:
#network flows
print(network.lines_t.p0)
     My line 0  My line 1  My line 2
now      -20.0       60.0      -40.0
In [16]:
#Line 1 is congested
print(abs(network.lines_t.p0)/network.lines.s_nom)
     My line 0  My line 1  My line 2
now   0.333333        1.0   0.666667
In [17]:
#Power flows towards lower voltage angles
print(network.buses_t.v_ang*180/np.pi)
     My bus 0  My bus 1  My bus 2
now       0.0  0.114592 -0.229183
In [18]:
#In linear approximation, all voltage magnitudes are nominal, i.e. 1 per unit
print(network.buses_t.v_mag_pu)
     My bus 0  My bus 1  My bus 2
now       1.0       1.0       1.0
In [19]:
#At bus 2 the price is set above any marginal generation costs in the model, because to dispatch to
#it from expensive generator 0, also some dispatch from cheap generator 1 has to be substituted from generator0
#to avoid overloading line 1.
print(network.buses_t.marginal_price)
     My bus 0  My bus 1  My bus 2
now      50.0      25.0      75.0