Skip to content

Commit e4529bc

Browse files
committed
Add Security-Constrained LOPF
Example included in examples/scigrid-de. Renamed folder examples/opf-scigrid-de to examples/scigrid-de, since it includes examples other than OPF.
1 parent ab6d03e commit e4529bc

30 files changed

+145
-3
lines changed

examples/scigrid-de/scigrid-sclopf.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
3+
from __future__ import print_function, division, absolute_import
4+
5+
import pypsa, os
6+
7+
csv_folder_name = os.path.dirname(pypsa.__file__) + "/../examples/scigrid-de/scigrid-with-load-gen/"
8+
9+
network = pypsa.Network(csv_folder_name=csv_folder_name)
10+
11+
12+
#There are some infeasibilities at the edge of the network where loads
13+
#are supplied by foreign lines - just add some extra capacity in Germany
14+
for line_name in ["350","583"]:
15+
network.lines.loc[line_name,"s_nom"] += 500
16+
17+
18+
network.now = network.snapshots[0]
19+
20+
branch_outages = network.lines.index[:15]
21+
22+
print("Performing security-constrained linear OPF:")
23+
24+
network.sclopf(branch_outages=branch_outages)
25+
26+
27+
#For the PF, set the P to the optimised P
28+
network.generators_t.p_set.loc[network.now] = network.generators_t.p.loc[network.now]
29+
network.storage_units_t.p_set.loc[network.now] = network.storage_units_t.p.loc[network.now]
30+
31+
32+
p0_test = network.lpf_contingency(branch_outages=branch_outages)
33+
34+
35+
max_loading = abs(p0_test.divide(network.passive_branches().s_nom,axis=0)).describe().loc["max"]
36+
37+
print(max_loading)

pypsa/components.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444

4545
from .pf import network_lpf, sub_network_lpf, network_pf, sub_network_pf, find_bus_controls, find_slack_bus, calculate_Y, calculate_PTDF, calculate_B_H, calculate_dependent_values
4646

47-
from .contingency import calculate_BODF, network_lpf_contingency
47+
from .contingency import calculate_BODF, network_lpf_contingency, network_sclopf
48+
4849

4950
from .opf import network_lopf, network_opf
5051

@@ -456,6 +457,8 @@ class Network(Basic):
456457

457458
lpf_contingency = network_lpf_contingency
458459

460+
sclopf = network_sclopf
461+
459462

460463
def __init__(self, csv_folder_name=None, **kwargs):
461464

@@ -814,7 +817,6 @@ class SubNetwork(Common):
814817

815818
calculate_BODF = calculate_BODF
816819

817-
818820
def buses(self):
819821
return self.network.buses[self.network.buses.sub_network == self.name]
820822

pypsa/contingency.py

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737

3838
from .pf import calculate_PTDF
3939

40+
from .opt import l_constraint
41+
42+
4043
def calculate_BODF(sub_network,verbose=True,skip_pre=False):
4144
"""
4245
Calculate the Branch Outage Distribution Factor (BODF) for
@@ -116,13 +119,16 @@ def network_lpf_contingency(network,snapshots=None,branch_outages=None,verbose=T
116119
else:
117120
snapshot = snapshots
118121

119-
120122
network.lpf(snapshot)
121123

122124
# Store the flows from the base case
123125

124126
passive_branches = network.passive_branches()
125127

128+
if branch_outages is None:
129+
branch_outages = passive_branches.index
130+
131+
126132
p0_base = pd.Series(index=passive_branches.index)
127133

128134
for typ in passive_branch_types:
@@ -151,3 +157,100 @@ def network_lpf_contingency(network,snapshots=None,branch_outages=None,verbose=T
151157
p0[branch] = p0_new
152158

153159
return p0
160+
161+
162+
163+
164+
def network_sclopf(network,snapshots=None,branch_outages=None,solver_name="glpk",verbose=True,skip_pre=False,solver_options={},keep_files=False,formulation="angles",ptdf_tolerance=0.):
165+
"""
166+
Computes Security-Constrained Linear Optimal Power Flow (SCLOPF).
167+
168+
This ensures that no branch is overloaded even given the branch outages.
169+
170+
Parameters
171+
----------
172+
snapshots : list or index slice
173+
A list of snapshots to optimise, must be a subset of network.snapshots, defaults to network.now
174+
branch_outages : list-like
175+
A list of passive branches which are to be tested for outages.
176+
If None, it's take as all network.passive_branches_i()
177+
solver_name : string
178+
Must be a solver name that pyomo recognises and that is installed, e.g. "glpk", "gurobi"
179+
verbose: bool, default True
180+
skip_pre: bool, default False
181+
Skip the preliminary steps of computing topology, calculating dependent values and finding bus controls.
182+
solver_options : dictionary
183+
A dictionary with additional options that get passed to the solver.
184+
(e.g. {'threads':2} tells gurobi to use only 2 cpus)
185+
keep_files : bool, default False
186+
Keep the files that pyomo constructs from OPF problem construction, e.g. .lp file - useful for debugging
187+
formulation : string
188+
Formulation of the linear power flow equations to use; must be one of ["angles","cycles","kirchoff","ptdf"]
189+
ptdf_tolerance : float
190+
Value below which PTDF entries are ignored
191+
192+
Returns
193+
-------
194+
None
195+
"""
196+
197+
if not skip_pre:
198+
network.determine_network_topology()
199+
200+
if snapshots is None:
201+
snapshots = [network.now]
202+
203+
passive_branches = network.passive_branches()
204+
205+
if branch_outages is None:
206+
branch_outages = passive_branches.index
207+
208+
#prepare the sub networks by calculating BODF and preparing helper DataFrames
209+
210+
for sn in network.sub_networks.obj:
211+
212+
sn.calculate_BODF(verbose)
213+
214+
sn._branches = sn.branches()
215+
sn._branches["_i"] = range(sn._branches.shape[0])
216+
sn._extendable_branches = sn._branches[sn._branches.s_nom_extendable]
217+
sn._fixed_branches = sn._branches[~ sn._branches.s_nom_extendable]
218+
219+
220+
def add_contingency_constraints(network,snapshots):
221+
222+
#a list of tuples with branch_outage and passive branches in same sub_network
223+
branch_outage_keys = []
224+
flow_upper = {}
225+
flow_lower = {}
226+
227+
for branch in branch_outages:
228+
if type(branch) is not tuple and verbose:
229+
print("No type given for {}, assuming it is a line".format(branch))
230+
branch = ("Line",branch)
231+
232+
sub = network.sub_networks.obj[passive_branches.sub_network[branch]]
233+
234+
branch_i = sub._branches.at[branch,"_i"]
235+
236+
branch_outage_keys.extend([(branch[0],branch[1],b[0],b[1]) for b in sub._branches.index])
237+
238+
flow_upper.update({(branch[0],branch[1],b[0],b[1],sn) : [[(1,network.model.passive_branch_p[b[0],b[1],sn]),(sub.BODF[sub._branches.at[b,"_i"],branch_i],network.model.passive_branch_p[branch[0],branch[1],sn])],"<=",sub._fixed_branches.s_nom[b]] for b in sub._fixed_branches.index for sn in snapshots})
239+
240+
flow_upper.update({(branch[0],branch[1],b[0],b[1],sn) : [[(1,network.model.passive_branch_p[b[0],b[1],sn]),(sub.BODF[sub._branches.at[b,"_i"],branch_i],network.model.passive_branch_p[branch[0],branch[1],sn]),(-1,network.model.branch_s_nom[b[0],b[1]])],"<=",0] for b in sub._extendable_branches.index for sn in snapshots})
241+
242+
243+
flow_lower.update({(branch[0],branch[1],b[0],b[1],sn) : [[(1,network.model.passive_branch_p[b[0],b[1],sn]),(sub.BODF[sub._branches.at[b,"_i"],branch_i],network.model.passive_branch_p[branch[0],branch[1],sn])],">=",-sub._fixed_branches.s_nom[b]] for b in sub._fixed_branches.index for sn in snapshots})
244+
245+
flow_upper.update({(branch[0],branch[1],b[0],b[1],sn) : [[(1,network.model.passive_branch_p[b[0],b[1],sn]),(sub.BODF[sub._branches.at[b,"_i"],branch_i],network.model.passive_branch_p[branch[0],branch[1],sn]),(1,network.model.branch_s_nom[b[0],b[1]])],">=",0] for b in sub._extendable_branches.index for sn in snapshots})
246+
247+
248+
l_constraint(network.model,"contingency_flow_upper",flow_upper,branch_outage_keys,snapshots)
249+
250+
251+
l_constraint(network.model,"contingency_flow_lower",flow_lower,branch_outage_keys,snapshots)
252+
253+
254+
#need to skip preparation otherwise it recalculates the sub-networks
255+
256+
network.lopf(snapshots=snapshots,solver_name=solver_name,verbose=verbose,skip_pre=True,extra_functionality=add_contingency_constraints,solver_options=solver_options,keep_files=keep_files,formulation=formulation,ptdf_tolerance=ptdf_tolerance)

0 commit comments

Comments
 (0)