|
37 | 37 |
|
38 | 38 | from .pf import calculate_PTDF
|
39 | 39 |
|
| 40 | +from .opt import l_constraint |
| 41 | + |
| 42 | + |
40 | 43 | def calculate_BODF(sub_network,verbose=True,skip_pre=False):
|
41 | 44 | """
|
42 | 45 | Calculate the Branch Outage Distribution Factor (BODF) for
|
@@ -116,13 +119,16 @@ def network_lpf_contingency(network,snapshots=None,branch_outages=None,verbose=T
|
116 | 119 | else:
|
117 | 120 | snapshot = snapshots
|
118 | 121 |
|
119 |
| - |
120 | 122 | network.lpf(snapshot)
|
121 | 123 |
|
122 | 124 | # Store the flows from the base case
|
123 | 125 |
|
124 | 126 | passive_branches = network.passive_branches()
|
125 | 127 |
|
| 128 | + if branch_outages is None: |
| 129 | + branch_outages = passive_branches.index |
| 130 | + |
| 131 | + |
126 | 132 | p0_base = pd.Series(index=passive_branches.index)
|
127 | 133 |
|
128 | 134 | for typ in passive_branch_types:
|
@@ -151,3 +157,100 @@ def network_lpf_contingency(network,snapshots=None,branch_outages=None,verbose=T
|
151 | 157 | p0[branch] = p0_new
|
152 | 158 |
|
153 | 159 | 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