ltbams 0.9.9__py3-none-any.whl → 1.0.2a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. ams/__init__.py +4 -11
  2. ams/_version.py +3 -3
  3. ams/cases/5bus/pjm5bus_demo.xlsx +0 -0
  4. ams/cases/5bus/pjm5bus_jumper.xlsx +0 -0
  5. ams/cases/5bus/pjm5bus_uced.json +1062 -0
  6. ams/cases/5bus/pjm5bus_uced.xlsx +0 -0
  7. ams/cases/5bus/pjm5bus_uced_esd1.xlsx +0 -0
  8. ams/cases/5bus/pjm5bus_uced_ev.xlsx +0 -0
  9. ams/cases/ieee123/ieee123.xlsx +0 -0
  10. ams/cases/ieee123/ieee123_regcv1.xlsx +0 -0
  11. ams/cases/ieee14/ieee14.json +1166 -0
  12. ams/cases/ieee14/ieee14.raw +92 -0
  13. ams/cases/ieee14/ieee14_conn.xlsx +0 -0
  14. ams/cases/ieee14/ieee14_uced.xlsx +0 -0
  15. ams/cases/ieee39/ieee39.xlsx +0 -0
  16. ams/cases/ieee39/ieee39_uced.xlsx +0 -0
  17. ams/cases/ieee39/ieee39_uced_esd1.xlsx +0 -0
  18. ams/cases/ieee39/ieee39_uced_pvd1.xlsx +0 -0
  19. ams/cases/ieee39/ieee39_uced_vis.xlsx +0 -0
  20. ams/cases/matpower/benchmark.json +1594 -0
  21. ams/cases/matpower/case118.m +787 -0
  22. ams/cases/matpower/case14.m +129 -0
  23. ams/cases/matpower/case300.m +1315 -0
  24. ams/cases/matpower/case39.m +205 -0
  25. ams/cases/matpower/case5.m +62 -0
  26. ams/cases/matpower/case_ACTIVSg2000.m +9460 -0
  27. ams/cases/npcc/npcc.m +644 -0
  28. ams/cases/npcc/npcc_uced.xlsx +0 -0
  29. ams/cases/pglib/pglib_opf_case39_epri__api.m +243 -0
  30. ams/cases/wecc/wecc.m +714 -0
  31. ams/cases/wecc/wecc_uced.xlsx +0 -0
  32. ams/cli.py +6 -0
  33. ams/core/__init__.py +2 -0
  34. ams/core/documenter.py +652 -0
  35. ams/core/matprocessor.py +782 -0
  36. ams/core/model.py +330 -0
  37. ams/core/param.py +322 -0
  38. ams/core/service.py +918 -0
  39. ams/core/symprocessor.py +224 -0
  40. ams/core/var.py +59 -0
  41. ams/extension/__init__.py +5 -0
  42. ams/extension/eva.py +401 -0
  43. ams/interface.py +1085 -0
  44. ams/io/__init__.py +133 -0
  45. ams/io/json.py +82 -0
  46. ams/io/matpower.py +406 -0
  47. ams/io/psse.py +6 -0
  48. ams/io/pypower.py +103 -0
  49. ams/io/xlsx.py +80 -0
  50. ams/main.py +81 -4
  51. ams/models/__init__.py +24 -0
  52. ams/models/area.py +40 -0
  53. ams/models/bus.py +52 -0
  54. ams/models/cost.py +169 -0
  55. ams/models/distributed/__init__.py +3 -0
  56. ams/models/distributed/esd1.py +71 -0
  57. ams/models/distributed/ev.py +60 -0
  58. ams/models/distributed/pvd1.py +67 -0
  59. ams/models/group.py +231 -0
  60. ams/models/info.py +26 -0
  61. ams/models/line.py +238 -0
  62. ams/models/renewable/__init__.py +5 -0
  63. ams/models/renewable/regc.py +119 -0
  64. ams/models/reserve.py +94 -0
  65. ams/models/shunt.py +14 -0
  66. ams/models/static/__init__.py +2 -0
  67. ams/models/static/gen.py +165 -0
  68. ams/models/static/pq.py +61 -0
  69. ams/models/timeslot.py +69 -0
  70. ams/models/zone.py +49 -0
  71. ams/opt/__init__.py +12 -0
  72. ams/opt/constraint.py +175 -0
  73. ams/opt/exprcalc.py +127 -0
  74. ams/opt/expression.py +188 -0
  75. ams/opt/objective.py +174 -0
  76. ams/opt/omodel.py +432 -0
  77. ams/opt/optzbase.py +192 -0
  78. ams/opt/param.py +156 -0
  79. ams/opt/var.py +233 -0
  80. ams/pypower/__init__.py +8 -0
  81. ams/pypower/_compat.py +9 -0
  82. ams/pypower/core/__init__.py +8 -0
  83. ams/pypower/core/pips.py +894 -0
  84. ams/pypower/core/ppoption.py +244 -0
  85. ams/pypower/core/ppver.py +18 -0
  86. ams/pypower/core/solver.py +2451 -0
  87. ams/pypower/eps.py +6 -0
  88. ams/pypower/idx.py +174 -0
  89. ams/pypower/io.py +604 -0
  90. ams/pypower/make/__init__.py +11 -0
  91. ams/pypower/make/matrices.py +665 -0
  92. ams/pypower/make/pdv.py +506 -0
  93. ams/pypower/routines/__init__.py +7 -0
  94. ams/pypower/routines/cpf.py +513 -0
  95. ams/pypower/routines/cpf_callbacks.py +114 -0
  96. ams/pypower/routines/opf.py +1803 -0
  97. ams/pypower/routines/opffcns.py +1946 -0
  98. ams/pypower/routines/pflow.py +852 -0
  99. ams/pypower/toggle.py +1098 -0
  100. ams/pypower/utils.py +293 -0
  101. ams/report.py +212 -50
  102. ams/routines/__init__.py +23 -0
  103. ams/routines/acopf.py +117 -0
  104. ams/routines/cpf.py +65 -0
  105. ams/routines/dcopf.py +241 -0
  106. ams/routines/dcpf.py +209 -0
  107. ams/routines/dcpf0.py +196 -0
  108. ams/routines/dopf.py +150 -0
  109. ams/routines/ed.py +312 -0
  110. ams/routines/pflow.py +255 -0
  111. ams/routines/pflow0.py +113 -0
  112. ams/routines/routine.py +1033 -0
  113. ams/routines/rted.py +519 -0
  114. ams/routines/type.py +160 -0
  115. ams/routines/uc.py +376 -0
  116. ams/shared.py +63 -9
  117. ams/system.py +61 -22
  118. ams/utils/__init__.py +3 -0
  119. ams/utils/misc.py +77 -0
  120. ams/utils/paths.py +257 -0
  121. docs/Makefile +21 -0
  122. docs/make.bat +35 -0
  123. docs/source/_templates/autosummary/base.rst +5 -0
  124. docs/source/_templates/autosummary/class.rst +35 -0
  125. docs/source/_templates/autosummary/module.rst +65 -0
  126. docs/source/_templates/autosummary/module_toctree.rst +66 -0
  127. docs/source/api.rst +102 -0
  128. docs/source/conf.py +203 -0
  129. docs/source/examples/index.rst +34 -0
  130. docs/source/genmodelref.py +61 -0
  131. docs/source/genroutineref.py +47 -0
  132. docs/source/getting_started/copyright.rst +20 -0
  133. docs/source/getting_started/formats/index.rst +20 -0
  134. docs/source/getting_started/formats/matpower.rst +183 -0
  135. docs/source/getting_started/formats/psse.rst +46 -0
  136. docs/source/getting_started/formats/pypower.rst +223 -0
  137. docs/source/getting_started/formats/xlsx.png +0 -0
  138. docs/source/getting_started/formats/xlsx.rst +23 -0
  139. docs/source/getting_started/index.rst +76 -0
  140. docs/source/getting_started/install.rst +234 -0
  141. docs/source/getting_started/overview.rst +26 -0
  142. docs/source/getting_started/testcase.rst +45 -0
  143. docs/source/getting_started/verification.rst +13 -0
  144. docs/source/images/curent.ico +0 -0
  145. docs/source/images/dcopf_time.png +0 -0
  146. docs/source/images/sponsors/CURENT_Logo_NameOnTrans.png +0 -0
  147. docs/source/images/sponsors/CURENT_Logo_Transparent.png +0 -0
  148. docs/source/images/sponsors/CURENT_Logo_Transparent_Name.png +0 -0
  149. docs/source/images/sponsors/doe.png +0 -0
  150. docs/source/index.rst +108 -0
  151. docs/source/modeling/example.rst +159 -0
  152. docs/source/modeling/index.rst +17 -0
  153. docs/source/modeling/model.rst +210 -0
  154. docs/source/modeling/routine.rst +122 -0
  155. docs/source/modeling/system.rst +51 -0
  156. docs/source/release-notes.rst +398 -0
  157. ltbams-1.0.2a1.dist-info/METADATA +210 -0
  158. ltbams-1.0.2a1.dist-info/RECORD +188 -0
  159. {ltbams-0.9.9.dist-info → ltbams-1.0.2a1.dist-info}/WHEEL +1 -1
  160. ltbams-1.0.2a1.dist-info/top_level.txt +3 -0
  161. tests/__init__.py +0 -0
  162. tests/test_1st_system.py +33 -0
  163. tests/test_addressing.py +40 -0
  164. tests/test_andes_mats.py +61 -0
  165. tests/test_case.py +266 -0
  166. tests/test_cli.py +34 -0
  167. tests/test_export_csv.py +89 -0
  168. tests/test_group.py +83 -0
  169. tests/test_interface.py +216 -0
  170. tests/test_io.py +32 -0
  171. tests/test_jumper.py +27 -0
  172. tests/test_known_good.py +267 -0
  173. tests/test_matp.py +437 -0
  174. tests/test_model.py +54 -0
  175. tests/test_omodel.py +119 -0
  176. tests/test_paths.py +22 -0
  177. tests/test_report.py +251 -0
  178. tests/test_repr.py +21 -0
  179. tests/test_routine.py +178 -0
  180. tests/test_rtn_dcopf.py +101 -0
  181. tests/test_rtn_dcpf.py +77 -0
  182. tests/test_rtn_ed.py +279 -0
  183. tests/test_rtn_pflow.py +219 -0
  184. tests/test_rtn_rted.py +273 -0
  185. tests/test_rtn_uc.py +248 -0
  186. tests/test_service.py +73 -0
  187. ltbams-0.9.9.dist-info/LICENSE +0 -692
  188. ltbams-0.9.9.dist-info/METADATA +0 -859
  189. ltbams-0.9.9.dist-info/RECORD +0 -14
  190. ltbams-0.9.9.dist-info/top_level.txt +0 -1
  191. {ltbams-0.9.9.dist-info → ltbams-1.0.2a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,1803 @@
1
+ """
2
+ Module to solve OPF.
3
+ """
4
+
5
+ import logging
6
+ from copy import deepcopy
7
+
8
+ import numpy as np
9
+ from numpy import flatnonzero as find
10
+
11
+ import scipy.sparse as sp
12
+ from scipy.sparse import csr_matrix as c_sparse
13
+
14
+ from andes.shared import deg2rad
15
+ from andes.utils.misc import elapsed
16
+
17
+ from ams.pypower.core import (ppoption, pipsopf_solver, ipoptopf_solver)
18
+ from ams.pypower.utils import isload, fairmax
19
+ from ams.pypower.idx import IDX
20
+ from ams.pypower.io import loadcase
21
+ from ams.pypower.make import (makeYbus, makeAvl, makeApq,
22
+ makeAang, makeAy)
23
+
24
+ import ams.pypower.routines.opffcns as opfcn
25
+
26
+ from ams.pypower.toggle import toggle_reserves
27
+
28
+ from ams.shared import inf
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ def runopf(casedata, ppopt):
34
+ """
35
+ Runs an optimal power flow.
36
+
37
+ @see: L{rundcopf}, L{runuopf}
38
+
39
+ @author: Ray Zimmerman (PSERC Cornell)
40
+ """
41
+ sstats = dict(solver_name='PYPOWER',
42
+ num_iters=1) # solver stats
43
+ ppopt = ppoption(ppopt)
44
+ r = fopf(casedata, ppopt)
45
+ sstats['solver_name'] = 'PYPOWER-PIPS'
46
+ sstats['num_iters'] = r['raw']['output']['iterations']
47
+ return r, sstats
48
+
49
+
50
+ def runuopf(casedata, ppopt):
51
+ """
52
+ Runs an optimal power flow with unit-decommitment heuristic.
53
+
54
+ @see: L{rundcopf}, L{runuopf}
55
+
56
+ @author: Ray Zimmerman (PSERC Cornell)
57
+ """
58
+ # default arguments
59
+ ppopt = ppoption(ppopt)
60
+
61
+ # ----- run the unit de-commitment / optimal power flow -----
62
+ r = uopf(casedata, ppopt)
63
+
64
+ return r
65
+
66
+
67
+ def runduopf(casedata, ppopt):
68
+ """
69
+ Runs a DC optimal power flow with unit-decommitment heuristic.
70
+
71
+ @see: L{rundcopf}, L{runuopf}
72
+
73
+ @author: Ray Zimmerman (PSERC Cornell)
74
+ """
75
+ # default arguments
76
+ ppopt = ppoption(ppopt, PF_DC=True)
77
+
78
+ return runuopf(casedata, ppopt)
79
+
80
+
81
+ def runopf_w_res(*args):
82
+ """
83
+ Runs an optimal power flow with fixed zonal reserves.
84
+
85
+ Runs an optimal power flow with the addition of reserve requirements
86
+ specified as a set of fixed zonal reserves. See L{runopf} for a
87
+ description of the input and output arguments, which are the same,
88
+ with the exception that the case file or dict C{casedata} must define
89
+ a 'reserves' field, which is a dict with the following fields:
90
+ - C{zones} C{nrz x ng}, C{zone(i, j) = 1}, if gen C{j} belongs
91
+ to zone C{i} 0, otherwise
92
+ - C{req} C{nrz x 1}, zonal reserve requirement in MW
93
+ - C{cost} (C{ng} or C{ngr}) C{x 1}, cost of reserves in $/MW
94
+ - C{qty} (C{ng} or C{ngr}) C{x 1}, max quantity of reserves
95
+ in MW (optional)
96
+ where C{nrz} is the number of reserve zones and C{ngr} is the number of
97
+ generators belonging to at least one reserve zone and C{ng} is the total
98
+ number of generators.
99
+
100
+ In addition to the normal OPF output, the C{results} dict contains a
101
+ new 'reserves' field with the following fields, in addition to those
102
+ provided in the input:
103
+ - C{R} - C{ng x 1}, reserves provided by each gen in MW
104
+ - C{Rmin} - C{ng x 1}, lower limit on reserves provided by
105
+ each gen, (MW)
106
+ - C{Rmax} - C{ng x 1}, upper limit on reserves provided by
107
+ each gen, (MW)
108
+ - C{mu.l} - C{ng x 1}, shadow price on reserve lower limit, ($/MW)
109
+ - C{mu.u} - C{ng x 1}, shadow price on reserve upper limit, ($/MW)
110
+ - C{mu.Pmax} - C{ng x 1}, shadow price on C{Pg + R <= Pmax}
111
+ constraint, ($/MW)
112
+ - C{prc} - C{ng x 1}, reserve price for each gen equal to
113
+ maximum of the shadow prices on the zonal requirement constraint
114
+ for each zone the generator belongs to
115
+
116
+ See L{t.t_case30_userfcns} for an example case file with fixed reserves,
117
+ and L{toggle_reserves} for the implementation.
118
+
119
+ Calling syntax options::
120
+ results = runopf_w_res(casedata)
121
+ results = runopf_w_res(casedata, ppopt)
122
+ results = runopf_w_res(casedata, ppopt, fname)
123
+ results = runopf_w_res(casedata, [popt, fname, solvedcase)
124
+ results, success = runopf_w_res(...)
125
+
126
+ Example::
127
+ results = runopf_w_res('t_case30_userfcns')
128
+
129
+ @see: L{runopf}, L{toggle_reserves}, L{t.t_case30_userfcns}
130
+
131
+ @author: Ray Zimmerman (PSERC Cornell)
132
+ """
133
+ ppc = loadcase(args[0])
134
+ ppc = toggle_reserves(ppc, 'on')
135
+
136
+ r = runopf(ppc, *args[1:])
137
+ r = toggle_reserves(r, 'off')
138
+
139
+ return r
140
+
141
+
142
+ def fopf(*args):
143
+ """
144
+ Solve an optimal power flow, return a `results` dict,
145
+ previously named ``opf``.
146
+
147
+ The data for the problem can be specified in one of three ways:
148
+ 1. a string (ppc) containing the file name of a PYPOWER case
149
+ which defines the data matrices baseMVA, bus, gen, branch, and
150
+ gencost (areas is not used at all, it is only included for
151
+ backward compatibility of the API).
152
+ 2. a dict (ppc) containing the data matrices as fields.
153
+ 3. the individual data matrices themselves.
154
+
155
+ The optional user parameters for user constraints (A, l, u), user costs
156
+ (N, fparm, H, Cw), user variable initializer (z0), and user variable
157
+ limits (zl, zu) can also be specified as fields in a case dict,
158
+ either passed in directly or defined in a case file referenced by name.
159
+
160
+ When specified, A, l, u represent additional linear constraints on the
161
+ optimization variables, l <= A*[x z] <= u. If the user specifies an A
162
+ matrix that has more columns than the number of "x" (OPF) variables,
163
+ then there are extra linearly constrained "z" variables. For an
164
+ explanation of the formulation used and instructions for forming the
165
+ A matrix, see the MATPOWER manual.
166
+
167
+ A generalized cost on all variables can be applied if input arguments
168
+ N, fparm, H, and Cw are specified. First, a linear transformation
169
+ of the optimization variables is defined by means of r = N * [x z].
170
+ Then, to each element of r a function is applied as encoded in the
171
+ fparm matrix (see MATPOWER manual). If the resulting vector is named
172
+ w, then H and Cw define a quadratic cost on w:
173
+ (1/2)*w'*H*w + Cw * w. H and N should be sparse matrices and H
174
+ should also be symmetric.
175
+
176
+ The optional ppopt vector specifies PYPOWER options. If the OPF
177
+ algorithm is not explicitly set in the options, PYPOWER will use the default
178
+ solver, based on a primal-dual interior point method. For the AC OPF, this
179
+ is OPF_ALG = 560. For the DC OPF, the default is OPF_ALG_DC = 200.
180
+ See L{ppoption} for more details on the available OPF solvers and other OPF
181
+ options and their default values.
182
+
183
+ The solved case is returned in a single results dict (described
184
+ below). Also returned are the final objective function value (f) and a
185
+ flag which is True if the algorithm was successful in finding a solution
186
+ (success). Additional optional return values are an algorithm specific
187
+ return status (info), elapsed time in seconds (et), the constraint
188
+ vector (g), the Jacobian matrix (jac), and the vector of variables
189
+ (xr) as well as the constraint multipliers (pimul).
190
+
191
+ The single results dict is a PYPOWER case struct (ppc) with the
192
+ usual baseMVA, bus, branch, gen, gencost fields, along with the
193
+ following additional fields:
194
+
195
+ - order see 'help ext2int' for details of this field
196
+ - et elapsed time in seconds for solving OPF
197
+ - success 1 if solver converged successfully, 0 otherwise
198
+ - om OPF model object, see 'help opf_model'
199
+ - x final value of optimization variables (internal order)
200
+ - f final objective function value
201
+ - mu shadow prices on ...
202
+ - var
203
+ - l lower bounds on variables
204
+ - u upper bounds on variables
205
+ - nln
206
+ - l lower bounds on nonlinear constraints
207
+ - u upper bounds on nonlinear constraints
208
+ - lin
209
+ - l lower bounds on linear constraints
210
+ - u upper bounds on linear constraints
211
+ - g (optional) constraint values
212
+ - dg (optional) constraint 1st derivatives
213
+ - df (optional) obj fun 1st derivatives (not yet implemented)
214
+ - d2f (optional) obj fun 2nd derivatives (not yet implemented)
215
+ - raw raw solver output in form returned by MINOS, and more
216
+ - xr final value of optimization variables
217
+ - pimul constraint multipliers
218
+ - info solver specific termination code
219
+ - output solver specific output information
220
+ - alg algorithm code of solver used
221
+ - var
222
+ - val optimization variable values, by named block
223
+ - Va voltage angles
224
+ - Vm voltage magnitudes (AC only)
225
+ - Pg real power injections
226
+ - Qg reactive power injections (AC only)
227
+ - y constrained cost variable (only if have pwl costs)
228
+ - (other) any user-defined variable blocks
229
+ - mu variable bound shadow prices, by named block
230
+ - l lower bound shadow prices
231
+ - Va, Vm, Pg, Qg, y, (other)
232
+ - u upper bound shadow prices
233
+ - Va, Vm, Pg, Qg, y, (other)
234
+ - nln (AC only)
235
+ - mu shadow prices on nonlinear constraints, by named block
236
+ - l lower bounds
237
+ - Pmis real power mismatch equations
238
+ - Qmis reactive power mismatch equations
239
+ - Sf flow limits at "from" end of branches
240
+ - St flow limits at "to" end of branches
241
+ - u upper bounds
242
+ - Pmis, Qmis, Sf, St
243
+ - lin
244
+ - mu shadow prices on linear constraints, by named block
245
+ - l lower bounds
246
+ - Pmis real power mistmatch equations (DC only)
247
+ - Pf flow limits at "from" end of branches (DC only)
248
+ - Pt flow limits at "to" end of branches (DC only)
249
+ - PQh upper portion of gen PQ-capability curve (AC only)
250
+ - PQl lower portion of gen PQ-capability curve (AC only)
251
+ - vl constant power factor constraint for loads
252
+ - ycon basin constraints for CCV for pwl costs
253
+ - (other) any user-defined constraint blocks
254
+ - u upper bounds
255
+ - Pmis, Pf, Pt, PQh, PQl, vl, ycon, (other)
256
+ - cost user-defined cost values, by named block
257
+
258
+ Author
259
+ ------
260
+ Ray Zimmerman (PSERC Cornell)
261
+
262
+ Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales)
263
+ """
264
+ # ----- initialization -----
265
+ t0, _ = elapsed() # start timer
266
+
267
+ # process input arguments
268
+ ppc, ppopt = opf_args2(*args)
269
+
270
+ # add zero columns to bus, gen, branch for multipliers, etc if needed
271
+ nb = np.shape(ppc['bus'])[0] # number of buses
272
+ nl = np.shape(ppc['branch'])[0] # number of branches
273
+ ng = np.shape(ppc['gen'])[0] # number of dispatchable injections
274
+ if np.shape(ppc['bus'])[1] < IDX.bus.MU_VMIN + 1:
275
+ ppc['bus'] = np.c_[ppc['bus'], np.zeros((nb, IDX.bus.MU_VMIN + 1 - np.shape(ppc['bus'])[1]))]
276
+
277
+ if np.shape(ppc['gen'])[1] < IDX.gen.MU_QMIN + 1:
278
+ ppc['gen'] = np.c_[ppc['gen'], np.zeros((ng, IDX.gen.MU_QMIN + 1 - np.shape(ppc['gen'])[1]))]
279
+
280
+ if np.shape(ppc['branch'])[1] < IDX.branch.MU_ANGMAX + 1:
281
+ ppc['branch'] = np.c_[ppc['branch'], np.zeros((nl, IDX.branch.MU_ANGMAX + 1 - np.shape(ppc['branch'])[1]))]
282
+
283
+ # ----- convert to internal numbering, remove out-of-service stuff -----
284
+ ppc = opfcn.ext2int(ppc)
285
+
286
+ # ----- construct OPF model object -----
287
+ om = opf_setup(ppc, ppopt)
288
+
289
+ # ----- execute the OPF -----
290
+ results, success, raw = opf_execute(om, ppopt)
291
+
292
+ # ----- revert to original ordering, including out-of-service stuff -----
293
+ results = opfcn.int2ext(results)
294
+
295
+ # zero out result fields of out-of-service gens & branches
296
+ if len(results['order']['gen']['status']['off']) > 0:
297
+ results['gen'][
298
+ np.ix_(
299
+ results['order']['gen']['status']['off'],
300
+ [IDX.gen.PG,
301
+ IDX.gen.QG,
302
+ IDX.gen.MU_PMAX,
303
+ IDX.gen.MU_PMIN])] = 0
304
+
305
+ if len(results['order']['branch']['status']['off']) > 0:
306
+ results['branch'][
307
+ np.ix_(
308
+ results['order']['branch']['status']['off'],
309
+ [IDX.branch.PF,
310
+ IDX.branch.QF,
311
+ IDX.branch.PT,
312
+ IDX.branch.QT,
313
+ IDX.branch.MU_SF,
314
+ IDX.branch.MU_ST,
315
+ IDX.branch.MU_ANGMIN,
316
+ IDX.branch.MU_ANGMAX])] = 0
317
+
318
+ # ----- finish preparing output -----
319
+ _, results['et'] = elapsed(t0)
320
+
321
+ results['success'] = success
322
+ results['raw'] = raw
323
+
324
+ return results
325
+
326
+
327
+ def opf_setup(ppc, ppopt):
328
+ """Constructs an OPF model object from a PYPOWER case dict.
329
+
330
+ Assumes that ppc is a PYPOWER case dict with internal indexing,
331
+ all equipment in-service, etc.
332
+
333
+ @see: L{opf}, L{ext2int}, L{opf_execute}
334
+
335
+ @author: Ray Zimmerman (PSERC Cornell)
336
+ @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad
337
+ Autonoma de Manizales)
338
+ """
339
+ # options
340
+ alg = ppopt['OPF_ALG']
341
+
342
+ # data dimensions
343
+ nb = ppc['bus'].shape[0] # number of buses
344
+ nl = ppc['branch'].shape[0] # number of branches
345
+ ng = ppc['gen'].shape[0] # number of dispatchable injections
346
+ if 'A' in ppc:
347
+ nusr = ppc['A'].shape[0] # number of linear user constraints
348
+ else:
349
+ nusr = 0
350
+
351
+ if 'N' in ppc:
352
+ nw = ppc['N'].shape[0] # number of general cost vars, w
353
+ else:
354
+ nw = 0
355
+
356
+ # convert single-block piecewise-linear costs into linear polynomial cost
357
+ pwl1 = find((ppc['gencost'][:, IDX.cost.MODEL] == IDX.cost.PW_LINEAR)
358
+ & (ppc['gencost'][:, IDX.cost.NCOST] == 2))
359
+ # p1 = np.array([])
360
+ if len(pwl1) > 0:
361
+ x0 = ppc['gencost'][pwl1, IDX.cost.COST]
362
+ y0 = ppc['gencost'][pwl1, IDX.cost.COST + 1]
363
+ x1 = ppc['gencost'][pwl1, IDX.cost.COST + 2]
364
+ y1 = ppc['gencost'][pwl1, IDX.cost.COST + 3]
365
+ m = (y1 - y0) / (x1 - x0)
366
+ b = y0 - m * x0
367
+ ppc['gencost'][pwl1, IDX.cost.MODEL] = IDX.cost.POLYNOMIAL
368
+ ppc['gencost'][pwl1, IDX.cost.NCOST] = 2
369
+ ppc['gencost'][pwl1, IDX.cost.COST:IDX.cost.COST + 2] = np.r_[m, b]
370
+
371
+ # create (read-only) copies of individual fields for convenience
372
+ baseMVA, bus, gen, branch, gencost, _, lbu, ubu, ppopt, \
373
+ _, fparm, H, Cw, z0, zl, zu, userfcn, _ = opf_args(ppc, ppopt)
374
+
375
+ # warn if there is more than one reference bus
376
+ refs = find(bus[:, IDX.bus.BUS_TYPE] == IDX.bus.REF)
377
+ if len(refs) > 1:
378
+ errstr = 'opf_setup: Warning: Multiple reference buses.\n' + \
379
+ ' For a system with islands, a reference bus in each island\n' + \
380
+ ' may help convergence, but in a fully connected system such\n' + \
381
+ ' a situation is probably not reasonable.\n\n'
382
+ logger.info(errstr)
383
+
384
+ # set up initial variables and bounds
385
+ gbus = gen[:, IDX.gen.GEN_BUS].astype(int)
386
+ Va = bus[:, IDX.bus.VA] * deg2rad
387
+ Vm = bus[:, IDX.bus.VM].copy()
388
+ Vm[gbus] = gen[:, IDX.gen.VG] # buses with gens, init Vm from gen data
389
+ Pg = gen[:, IDX.gen.PG] / baseMVA
390
+ Qg = gen[:, IDX.gen.QG] / baseMVA
391
+ Pmin = gen[:, IDX.gen.PMIN] / baseMVA
392
+ Pmax = gen[:, IDX.gen.PMAX] / baseMVA
393
+ Qmin = gen[:, IDX.gen.QMIN] / baseMVA
394
+ Qmax = gen[:, IDX.gen.QMAX] / baseMVA
395
+
396
+ # AC model with more problem dimensions
397
+ nv = nb # number of voltage magnitude vars
398
+ nq = ng # number of Qg vars
399
+ q1 = ng # index of 1st Qg column in Ay
400
+
401
+ # dispatchable load, constant power factor constraints
402
+ Avl, lvl, uvl, _ = makeAvl(baseMVA, gen)
403
+
404
+ # generator PQ capability curve constraints
405
+ Apqh, ubpqh, Apql, ubpql, Apqdata = makeApq(baseMVA, gen)
406
+
407
+ user_vars = ['Va', 'Vm', 'Pg', 'Qg']
408
+ ycon_vars = ['Pg', 'Qg', 'y']
409
+
410
+ # voltage angle reference constraints
411
+ Vau = inf * np.ones(nb)
412
+ Val = -Vau
413
+ Vau[refs] = Va[refs]
414
+ Val[refs] = Va[refs]
415
+
416
+ # branch voltage angle difference limits
417
+ Aang, lang, uang, iang = makeAang(baseMVA, branch, nb, ppopt)
418
+
419
+ # basin constraints for piece-wise linear gen cost variables
420
+ if alg == 545 or alg == 550: # SC-PDIPM or TRALM, no CCV cost vars
421
+ ny = 0
422
+ Ay = None
423
+ by = np.array([])
424
+ else:
425
+ ipwl = find(gencost[:, IDX.cost.MODEL] == IDX.cost.PW_LINEAR) # piece-wise linear costs
426
+ ny = ipwl.shape[0] # number of piece-wise linear cost vars
427
+ Ay, by = makeAy(baseMVA, ng, gencost, 1, q1, 1+ng+nq)
428
+
429
+ if np.any((gencost[:, IDX.cost.MODEL] != IDX.cost.POLYNOMIAL) &
430
+ (gencost[:, IDX.cost.MODEL] != IDX.cost.PW_LINEAR)):
431
+ logger.debug('opf_setup: some generator cost rows have invalid MODEL value\n')
432
+
433
+ # more problem dimensions
434
+ nx = nb+nv + ng+nq # number of standard OPF control variables
435
+ if nusr:
436
+ nz = ppc['A'].shape[1] - nx # number of user z variables
437
+ if nz < 0:
438
+ logger.debug('opf_setup: user supplied A matrix must have at least %d columns.\n' % nx)
439
+ else:
440
+ nz = 0 # number of user z variables
441
+ if nw: # still need to check number of columns of N
442
+ if ppc['N'].shape[1] != nx:
443
+ logger.debug('opf_setup: user supplied N matrix must have %d columns.\n' % nx)
444
+
445
+ # construct OPF model object
446
+ om = opf_model(ppc)
447
+ if len(pwl1) > 0:
448
+ om.userdata('pwl1', pwl1)
449
+
450
+ om.userdata('Apqdata', Apqdata)
451
+ om.userdata('iang', iang)
452
+ om.add_vars('Va', nb, Va, Val, Vau)
453
+ om.add_vars('Vm', nb, Vm, bus[:, IDX.bus.VMIN], bus[:, IDX.bus.VMAX])
454
+ om.add_vars('Pg', ng, Pg, Pmin, Pmax)
455
+ om.add_vars('Qg', ng, Qg, Qmin, Qmax)
456
+ om.add_constraints('Pmis', nb, 'nonlinear')
457
+ om.add_constraints('Qmis', nb, 'nonlinear')
458
+ om.add_constraints('Sf', nl, 'nonlinear')
459
+ om.add_constraints('St', nl, 'nonlinear')
460
+ om.add_constraints('PQh', Apqh, np.array([]), ubpqh, ['Pg', 'Qg']) # npqh
461
+ om.add_constraints('PQl', Apql, np.array([]), ubpql, ['Pg', 'Qg']) # npql
462
+ om.add_constraints('vl', Avl, lvl, uvl, ['Pg', 'Qg']) # nvl
463
+ om.add_constraints('ang', Aang, lang, uang, ['Va']) # nang
464
+
465
+ # y vars, constraints for piece-wise linear gen costs
466
+ if ny > 0:
467
+ om.add_vars('y', ny)
468
+ om.add_constraints('ycon', Ay, np.array([]), by, ycon_vars) # ncony
469
+
470
+ # add user vars, constraints and costs (as specified via A, ..., N, ...)
471
+ if nz > 0:
472
+ om.add_vars('z', nz, z0, zl, zu)
473
+ user_vars.append('z')
474
+
475
+ if nusr:
476
+ om.add_constraints('usr', ppc['A'], lbu, ubu, user_vars) # nusr
477
+
478
+ if nw:
479
+ user_cost = {}
480
+ user_cost['N'] = ppc['N']
481
+ user_cost['Cw'] = Cw
482
+ if len(fparm) > 0:
483
+ user_cost['dd'] = fparm[:, 0]
484
+ user_cost['rh'] = fparm[:, 1]
485
+ user_cost['kk'] = fparm[:, 2]
486
+ user_cost['mm'] = fparm[:, 3]
487
+
488
+ # if len(H) > 0:
489
+ user_cost['H'] = H
490
+
491
+ om.add_costs('usr', user_cost, user_vars)
492
+
493
+ # execute userfcn callbacks for 'formulation' stage
494
+ opfcn.run_userfcn(userfcn, 'formulation', om)
495
+
496
+ return om
497
+
498
+
499
+ class opf_model(object):
500
+ """This class implements the OPF model object used to encapsulate
501
+ a given OPF problem formulation. It allows for access to optimization
502
+ variables, constraints and costs in named blocks, keeping track of the
503
+ ordering and indexing of the blocks as variables, constraints and costs
504
+ are added to the problem.
505
+
506
+ @author: Ray Zimmerman (PSERC Cornell)
507
+ """
508
+
509
+ def __init__(self, ppc):
510
+ #: PYPOWER case dict used to build the object.
511
+ self.ppc = ppc
512
+
513
+ #: data for optimization variable sets that make up the
514
+ # full optimization variable x
515
+ self.var = {
516
+ 'idx': {
517
+ 'i1': {}, # starting index within x
518
+ 'iN': {}, # ending index within x
519
+ 'N': {} # number of elements in this variable set
520
+ },
521
+ 'N': 0, # total number of elements in x
522
+ 'NS': 0, # number of variable sets or named blocks
523
+ 'data': { # bounds and initial value data
524
+ 'v0': {}, # vector of initial values
525
+ 'vl': {}, # vector of lower bounds
526
+ 'vu': {}, # vector of upper bounds
527
+ },
528
+ 'order': [] # list of names for variable blocks in the order they appear in x
529
+ }
530
+
531
+ #: data for nonlinear constraints that make up the
532
+ # full set of nonlinear constraints ghn(x)
533
+ self.nln = {
534
+ 'idx': {
535
+ 'i1': {}, # starting index within ghn(x)
536
+ 'iN': {}, # ending index within ghn(x)
537
+ 'N': {} # number of elements in this constraint set
538
+ },
539
+ 'N': 0, # total number of elements in ghn(x)
540
+ 'NS': 0, # number of nonlinear constraint sets or named blocks
541
+ 'order': [] # list of names for nonlinear constraint blocks in the order they appear in ghn(x)
542
+ }
543
+
544
+ #: data for linear constraints that make up the
545
+ # full set of linear constraints ghl(x)
546
+ self.lin = {
547
+ 'idx': {
548
+ 'i1': {}, # starting index within ghl(x)
549
+ 'iN': {}, # ending index within ghl(x)
550
+ 'N': {} # number of elements in this constraint set
551
+ },
552
+ 'N': 0, # total number of elements in ghl(x)
553
+ 'NS': 0, # number of linear constraint sets or named blocks
554
+ 'data': { # data for l <= A*xx <= u linear constraints
555
+ 'A': {}, # sparse linear constraint matrix
556
+ 'l': {}, # left hand side vector, bounding A*x below
557
+ 'u': {}, # right hand side vector, bounding A*x above
558
+ 'vs': {} # cell array of variable sets that define the xx for this constraint block
559
+ },
560
+ 'order': [] # list of names for linear constraint blocks in the order they appear in ghl(x)
561
+ }
562
+
563
+ #: data for user-defined costs
564
+ self.cost = {
565
+ 'idx': {
566
+ 'i1': {}, # starting row index within full N matrix
567
+ 'iN': {}, # ending row index within full N matrix
568
+ 'N': {} # number of rows in this cost block in full N matrix
569
+ },
570
+ 'N': 0, # total number of rows in full N matrix
571
+ 'NS': 0, # number of cost blocks
572
+ 'data': { # data for each user-defined cost block
573
+ 'N': {}, # see help for add_costs() for details
574
+ 'H': {}, # "
575
+ 'Cw': {}, # "
576
+ 'dd': {}, # "
577
+ 'rh': {}, # "
578
+ 'kk': {}, # "
579
+ 'mm': {}, # "
580
+ 'vs': {} # list of variable sets that define xx for this cost block, where the N for this block multiplies xx'
581
+ },
582
+ 'order': [] # of names for cost blocks in the order they appear in the rows of the full N matrix
583
+ }
584
+
585
+ self.user_data = {}
586
+
587
+ # def __repr__(self):
588
+ # """String representation of the object.
589
+ # """
590
+ # s = ''
591
+ # if self.var['NS']:
592
+ # s += '\n%-22s %5s %8s %8s %8s\n' % ('VARIABLES', 'name', 'i1', 'iN', 'N')
593
+ # s += '%-22s %5s %8s %8s %8s\n' % ('=========', '------', '-----', '-----', '------')
594
+ # for k in range(self.var['NS']):
595
+ # name = self.var['order'][k]
596
+ # idx = self.var['idx']
597
+ # s += '%15d:%12s %8d %8d %8d\n' % (k, name, idx['i1'][name], idx['iN'][name], idx['N'][name])
598
+
599
+ # s += '%15s%31s\n' % (('var[\'NS\'] = %d' % self.var['NS']), ('var[\'N\'] = %d' % self.var['N']))
600
+ # s += '\n'
601
+ # else:
602
+ # s += '%s : <none>\n', 'VARIABLES'
603
+
604
+ # if self.nln['NS']:
605
+ # s += '\n%-22s %5s %8s %8s %8s\n' % ('NON-LINEAR CONSTRAINTS', 'name', 'i1', 'iN', 'N')
606
+ # s += '%-22s %5s %8s %8s %8s\n' % ('======================', '------', '-----', '-----', '------')
607
+ # for k in range(self.nln['NS']):
608
+ # name = self.nln['order'][k]
609
+ # idx = self.nln['idx']
610
+ # s += '%15d:%12s %8d %8d %8d\n' % (k, name, idx['i1'][name], idx['iN'][name], idx['N'][name])
611
+
612
+ # s += '%15s%31s\n' % (('nln.NS = %d' % self.nln['NS']), ('nln.N = %d' % self.nln['N']))
613
+ # s += '\n'
614
+ # else:
615
+ # s += '%s : <none>\n', 'NON-LINEAR CONSTRAINTS'
616
+
617
+ # if self.lin['NS']:
618
+ # s += '\n%-22s %5s %8s %8s %8s\n' % ('LINEAR CONSTRAINTS', 'name', 'i1', 'iN', 'N')
619
+ # s += '%-22s %5s %8s %8s %8s\n' % ('==================', '------', '-----', '-----', '------')
620
+ # for k in range(self.lin['NS']):
621
+ # name = self.lin['order'][k]
622
+ # idx = self.lin['idx']
623
+ # s += '%15d:%12s %8d %8d %8d\n' % (k, name, idx['i1'][name], idx['iN'][name], idx['N'][name])
624
+
625
+ # s += '%15s%31s\n' % (('lin.NS = %d' % self.lin['NS']), ('lin.N = %d' % self.lin['N']))
626
+ # s += '\n'
627
+ # else:
628
+ # s += '%s : <none>\n', 'LINEAR CONSTRAINTS'
629
+
630
+ # if self.cost['NS']:
631
+ # s += '\n%-22s %5s %8s %8s %8s\n' % ('COSTS', 'name', 'i1', 'iN', 'N')
632
+ # s += '%-22s %5s %8s %8s %8s\n' % ('=====', '------', '-----', '-----', '------')
633
+ # for k in range(self.cost['NS']):
634
+ # name = self.cost['order'][k]
635
+ # idx = self.cost['idx']
636
+ # s += '%15d:%12s %8d %8d %8d\n' % (k, name, idx['i1'][name], idx['iN'][name], idx['N'][name])
637
+
638
+ # s += '%15s%31s\n' % (('cost.NS = %d' % self.cost['NS']), ('cost.N = %d' % self.cost['N']))
639
+ # s += '\n'
640
+ # else:
641
+ # s += '%s : <none>\n' % 'COSTS'
642
+
643
+ # #s += ' ppc = '
644
+ # #if len(self.ppc):
645
+ # # s += '\n'
646
+ # #
647
+ # #s += str(self.ppc) + '\n'
648
+
649
+ # s += ' userdata = '
650
+ # if len(self.user_data):
651
+ # s += '\n'
652
+
653
+ # s += str(self.user_data)
654
+
655
+ # return s
656
+
657
+ def add_constraints(self, name, AorN, l, u=None, varsets=None):
658
+ """Adds a set of constraints to the model.
659
+
660
+ Linear constraints are of the form C{l <= A * x <= u}, where
661
+ C{x} is a vector made of of the vars specified in C{varsets} (in
662
+ the order given). This allows the C{A} matrix to be defined only
663
+ in terms of the relevant variables without the need to manually
664
+ create a lot of zero columns. If C{varsets} is empty, C{x} is taken
665
+ to be the full vector of all optimization variables. If C{l} or
666
+ C{u} are empty, they are assumed to be appropriately sized vectors
667
+ of C{-Inf} and C{Inf}, respectively.
668
+
669
+ For nonlinear constraints, the 3rd argument, C{N}, is the number
670
+ of constraints in the set. Currently, this is used internally
671
+ by PYPOWER, but there is no way for the user to specify
672
+ additional nonlinear constraints.
673
+ """
674
+ if u is None: # nonlinear
675
+ # prevent duplicate named constraint sets
676
+ if name in self.nln["idx"]["N"]:
677
+ logger.debug("opf_model.add_constraints: nonlinear constraint set named '%s' already exists\n" % name)
678
+
679
+ # add info about this nonlinear constraint set
680
+ self.nln["idx"]["i1"][name] = self.nln["N"] # + 1 ## starting index
681
+ self.nln["idx"]["iN"][name] = self.nln["N"] + AorN # ing index
682
+ self.nln["idx"]["N"][name] = AorN # number of constraints
683
+
684
+ # update number of nonlinear constraints and constraint sets
685
+ self.nln["N"] = self.nln["idx"]["iN"][name]
686
+ self.nln["NS"] = self.nln["NS"] + 1
687
+
688
+ # put name in ordered list of constraint sets
689
+ # self.nln["order"][self.nln["NS"]] = name
690
+ self.nln["order"].append(name)
691
+ else: # linear
692
+ # prevent duplicate named constraint sets
693
+ if name in self.lin["idx"]["N"]:
694
+ logger.debug('opf_model.add_constraints: linear constraint set named ''%s'' already exists\n' % name)
695
+
696
+ if varsets is None:
697
+ varsets = []
698
+
699
+ N, M = AorN.shape
700
+ if len(l) == 0: # default l is -Inf
701
+ l = -inf * np.ones(N)
702
+
703
+ if len(u) == 0: # default u is Inf
704
+ u = inf * np.ones(N)
705
+
706
+ if len(varsets) == 0:
707
+ varsets = self.var["order"]
708
+
709
+ # check sizes
710
+ if (l.shape[0] != N) or (u.shape[0] != N):
711
+ logger.debug('opf_model.add_constraints: sizes of A, l and u must match\n')
712
+
713
+ nv = 0
714
+ for k in range(len(varsets)):
715
+ nv = nv + self.var["idx"]["N"][varsets[k]]
716
+
717
+ if M != nv:
718
+ logger.debug(
719
+ 'opf_model.add_constraints: number of columns of A does not match\nnumber of variables, A is %d x %d, nv = %d\n' % (N, M, nv))
720
+
721
+ # add info about this linear constraint set
722
+ self.lin["idx"]["i1"][name] = self.lin["N"] # + 1 ## starting index
723
+ self.lin["idx"]["iN"][name] = self.lin["N"] + N # ing index
724
+ self.lin["idx"]["N"][name] = N # number of constraints
725
+ self.lin["data"]["A"][name] = AorN
726
+ self.lin["data"]["l"][name] = l
727
+ self.lin["data"]["u"][name] = u
728
+ self.lin["data"]["vs"][name] = varsets
729
+
730
+ # update number of vars and var sets
731
+ self.lin["N"] = self.lin["idx"]["iN"][name]
732
+ self.lin["NS"] = self.lin["NS"] + 1
733
+
734
+ # put name in ordered list of var sets
735
+ # self.lin["order"][self.lin["NS"]] = name
736
+ self.lin["order"].append(name)
737
+
738
+ def add_costs(self, name, cp, varsets):
739
+ """Adds a set of user costs to the model.
740
+
741
+ Adds a named block of user-defined costs to the model. Each set is
742
+ defined by the C{cp} dict described below. All user-defined sets of
743
+ costs are combined together into a single set of cost parameters in
744
+ a single C{cp} dict by L{build_cost_params}. This full aggregate set of
745
+ cost parameters can be retrieved from the model by L{get_cost_params}.
746
+
747
+ Let C{x} refer to the vector formed by combining the specified
748
+ C{varsets}, and C{f_u(x, cp)} be the cost at C{x} corresponding to the
749
+ cost parameters contained in C{cp}, where C{cp} is a dict with the
750
+ following fields::
751
+ N - nw x nx sparse matrix
752
+ Cw - nw x 1 vector
753
+ H - nw x nw sparse matrix (optional, all zeros by default)
754
+ dd, mm - nw x 1 vectors (optional, all ones by default)
755
+ rh, kk - nw x 1 vectors (optional, all zeros by default)
756
+
757
+ These parameters are used as follows to compute C{f_u(x, CP)}::
758
+
759
+ R = N*x - rh
760
+
761
+ / kk(i), R(i) < -kk(i)
762
+ K(i) = < 0, -kk(i) <= R(i) <= kk(i)
763
+ \ -kk(i), R(i) > kk(i)
764
+
765
+ RR = R + K
766
+
767
+ U(i) = / 0, -kk(i) <= R(i) <= kk(i)
768
+ \ 1, otherwise
769
+
770
+ DDL(i) = / 1, dd(i) = 1
771
+ \ 0, otherwise
772
+
773
+ DDQ(i) = / 1, dd(i) = 2
774
+ \ 0, otherwise
775
+
776
+ Dl = diag(mm) * diag(U) * diag(DDL)
777
+ Dq = diag(mm) * diag(U) * diag(DDQ)
778
+
779
+ w = (Dl + Dq * diag(RR)) * RR
780
+
781
+ f_u(x, CP) = 1/2 * w'*H*w + Cw'*w
782
+ """
783
+ # prevent duplicate named cost sets
784
+ if name in self.cost["idx"]["N"]:
785
+ logger.debug('opf_model.add_costs: cost set named \'%s\' already exists\n' % name)
786
+
787
+ if varsets is None:
788
+ varsets = []
789
+
790
+ if len(varsets) == 0:
791
+ varsets = self.var["order"]
792
+
793
+ nw, nx = cp["N"].shape
794
+
795
+ # check sizes
796
+ nv = 0
797
+ for k in range(len(varsets)):
798
+ nv = nv + self.var["idx"]["N"][varsets[k]]
799
+
800
+ if nx != nv:
801
+ if nw == 0:
802
+ cp["N"] = c_sparse(nw, nx)
803
+ else:
804
+ logger.debug(
805
+ 'opf_model.add_costs: number of columns in N (%d x %d) does not match\nnumber of variables (%d)\n' %
806
+ (nw, nx, nv))
807
+
808
+ if cp["Cw"].shape[0] != nw:
809
+ logger.debug('opf_model.add_costs: number of rows of Cw (%d x %d) and N (%d x %d) must match\n' %
810
+ (cp["Cw"].shape[0], nw, nx))
811
+
812
+ if 'H' in cp:
813
+ if (cp["H"].shape[0] != nw) | (cp["H"].shape[1] != nw):
814
+ logger.debug('opf_model.add_costs: both dimensions of H (%d x %d) must match the number of rows in N (%d x %d)\n' % (
815
+ cp["H"].shape, nw, nx))
816
+
817
+ if 'dd' in cp:
818
+ if cp["dd"].shape[0] != nw:
819
+ logger.debug(
820
+ 'opf_model.add_costs: number of rows of dd (%d x %d) and N (%d x %d) must match\n' %
821
+ (cp["dd"].shape, nw, nx))
822
+
823
+ if 'rh' in cp:
824
+ if cp["rh"].shape[0] != nw:
825
+ logger.debug(
826
+ 'opf_model.add_costs: number of rows of rh (%d x %d) and N (%d x %d) must match\n' %
827
+ (cp["rh"].shape, nw, nx))
828
+
829
+ if 'kk' in cp:
830
+ if cp["kk"].shape[0] != nw:
831
+ logger.debug(
832
+ 'opf_model.add_costs: number of rows of kk (%d x %d) and N (%d x %d) must match\n' %
833
+ (cp["kk"].shape, nw, nx))
834
+
835
+ if 'mm' in cp:
836
+ if cp["mm"].shape[0] != nw:
837
+ logger.debug(
838
+ 'opf_model.add_costs: number of rows of mm (%d x %d) and N (%d x %d) must match\n' %
839
+ (cp["mm"].shape, nw, nx))
840
+
841
+ # add info about this user cost set
842
+ self.cost["idx"]["i1"][name] = self.cost["N"] # + 1 ## starting index
843
+ self.cost["idx"]["iN"][name] = self.cost["N"] + nw # ing index
844
+ self.cost["idx"]["N"][name] = nw # number of costs (nw)
845
+ self.cost["data"]["N"][name] = cp["N"]
846
+ self.cost["data"]["Cw"][name] = cp["Cw"]
847
+ self.cost["data"]["vs"][name] = varsets
848
+ if 'H' in cp:
849
+ self.cost["data"]["H"][name] = cp["H"]
850
+
851
+ if 'dd' in cp:
852
+ self.cost["data"]["dd"]["name"] = cp["dd"]
853
+
854
+ if 'rh' in cp:
855
+ self.cost["data"]["rh"]["name"] = cp["rh"]
856
+
857
+ if 'kk' in cp:
858
+ self.cost["data"]["kk"]["name"] = cp["kk"]
859
+
860
+ if 'mm' in cp:
861
+ self.cost["data"]["mm"]["name"] = cp["mm"]
862
+
863
+ # update number of vars and var sets
864
+ self.cost["N"] = self.cost["idx"]["iN"][name]
865
+ self.cost["NS"] = self.cost["NS"] + 1
866
+
867
+ # put name in ordered list of var sets
868
+ self.cost["order"].append(name)
869
+
870
+ def add_vars(self, name, N, v0=None, vl=None, vu=None):
871
+ """ Adds a set of variables to the model.
872
+
873
+ Adds a set of variables to the model, where N is the number of
874
+ variables in the set, C{v0} is the initial value of those variables,
875
+ and C{vl} and C{vu} are the lower and upper bounds on the variables.
876
+ The defaults for the last three arguments, which are optional,
877
+ are for all values to be initialized to zero (C{v0 = 0}) and unbounded
878
+ (C{VL = -Inf, VU = Inf}).
879
+ """
880
+ # prevent duplicate named var sets
881
+ if name in self.var["idx"]["N"]:
882
+ logger.debug('opf_model.add_vars: variable set named ''%s'' already exists\n' % name)
883
+
884
+ if v0 is None or len(v0) == 0:
885
+ v0 = np.zeros(N) # init to zero by default
886
+
887
+ if vl is None or len(vl) == 0:
888
+ vl = -inf * np.ones(N) # unbounded below by default
889
+
890
+ if vu is None or len(vu) == 0:
891
+ vu = inf * np.ones(N) # unbounded above by default
892
+
893
+ # add info about this var set
894
+ self.var["idx"]["i1"][name] = self.var["N"] # + 1 ## starting index
895
+ self.var["idx"]["iN"][name] = self.var["N"] + N # ing index
896
+ self.var["idx"]["N"][name] = N # number of vars
897
+ self.var["data"]["v0"][name] = v0 # initial value
898
+ self.var["data"]["vl"][name] = vl # lower bound
899
+ self.var["data"]["vu"][name] = vu # upper bound
900
+
901
+ # update number of vars and var sets
902
+ self.var["N"] = self.var["idx"]["iN"][name]
903
+ self.var["NS"] = self.var["NS"] + 1
904
+
905
+ # put name in ordered list of var sets
906
+ # self.var["order"][self.var["NS"]] = name
907
+ self.var["order"].append(name)
908
+
909
+ def build_cost_params(self):
910
+ """Builds and saves the full generalized cost parameters.
911
+
912
+ Builds the full set of cost parameters from the individual named
913
+ sub-sets added via L{add_costs}. Skips the building process if it has
914
+ already been done, unless a second input argument is present.
915
+
916
+ These cost parameters can be retrieved by calling L{get_cost_params}
917
+ and the user-defined costs evaluated by calling L{compute_cost}.
918
+ """
919
+ # initialize parameters
920
+ nw = self.cost["N"]
921
+ # nnzN = 0
922
+ # nnzH = 0
923
+ # for k in range(self.cost["NS"]):
924
+ # name = self.cost["order"][k]
925
+ # nnzN = nnzN + nnz(self.cost["data"]["N"][name])
926
+ # if name in self.cost["data"]["H"]:
927
+ # nnzH = nnzH + nnz(self.cost["data"]["H"][name])
928
+
929
+ # FIXME Zero dimensional sparse matrices
930
+ N = np.zeros((nw, self.var["N"]))
931
+ H = np.zeros((nw, nw)) # default => no quadratic term
932
+
933
+ Cw = np.zeros(nw)
934
+ dd = np.ones(nw) # default => linear
935
+ rh = np.zeros(nw) # default => no shift
936
+ kk = np.zeros(nw) # default => no dead zone
937
+ mm = np.ones(nw) # default => no scaling
938
+
939
+ # fill in each piece
940
+ for k in range(self.cost["NS"]):
941
+ name = self.cost["order"][k]
942
+ Nk = self.cost["data"]["N"][name] # N for kth cost set
943
+ i1 = self.cost["idx"]["i1"][name] # starting row index
944
+ iN = self.cost["idx"]["iN"][name] # ing row index
945
+ if self.cost["idx"]["N"][name]: # non-zero number of rows to add
946
+ vsl = self.cost["data"]["vs"][name] # var set list
947
+ kN = 0 # initialize last col of Nk used
948
+ for v in vsl:
949
+ j1 = self.var["idx"]["i1"][v] # starting column in N
950
+ jN = self.var["idx"]["iN"][v] # ing column in N
951
+ k1 = kN # starting column in Nk
952
+ kN = kN + self.var["idx"]["N"][v] # ing column in Nk
953
+ N[i1:iN, j1:jN] = Nk[:, k1:kN].todense()
954
+
955
+ Cw[i1:iN] = self.cost["data"]["Cw"][name]
956
+ if name in self.cost["data"]["H"]:
957
+ H[i1:iN, i1:iN] = self.cost["data"]["H"][name].todense()
958
+
959
+ if name in self.cost["data"]["dd"]:
960
+ dd[i1:iN] = self.cost["data"]["dd"][name]
961
+
962
+ if name in self.cost["data"]["rh"]:
963
+ rh[i1:iN] = self.cost["data"]["rh"][name]
964
+
965
+ if name in self.cost["data"]["kk"]:
966
+ kk[i1:iN] = self.cost["data"]["kk"][name]
967
+
968
+ if name in self.cost["data"]["mm"]:
969
+ mm[i1:iN] = self.cost["data"]["mm"][name]
970
+
971
+ if nw:
972
+ N = c_sparse(N)
973
+ H = c_sparse(H)
974
+
975
+ # save in object
976
+ self.cost["params"] = {
977
+ 'N': N, 'Cw': Cw, 'H': H, 'dd': dd, 'rh': rh, 'kk': kk, 'mm': mm}
978
+
979
+ def compute_cost(self, x, name=None):
980
+ """ Computes a user-defined cost.
981
+
982
+ Computes the value of a user defined cost, either for all user
983
+ defined costs or for a named set of costs. Requires calling
984
+ L{build_cost_params} first to build the full set of parameters.
985
+
986
+ Let C{x} be the full set of optimization variables and C{f_u(x, cp)} be
987
+ the user-defined cost at C{x}, corresponding to the set of cost
988
+ parameters in the C{cp} dict returned by L{get_cost_params}, where
989
+ C{cp} is a dict with the following fields::
990
+ N - nw x nx sparse matrix
991
+ Cw - nw x 1 vector
992
+ H - nw x nw sparse matrix (optional, all zeros by default)
993
+ dd, mm - nw x 1 vectors (optional, all ones by default)
994
+ rh, kk - nw x 1 vectors (optional, all zeros by default)
995
+
996
+ These parameters are used as follows to compute C{f_u(x, cp)}::
997
+
998
+ R = N*x - rh
999
+
1000
+ / kk(i), R(i) < -kk(i)
1001
+ K(i) = < 0, -kk(i) <= R(i) <= kk(i)
1002
+ \ -kk(i), R(i) > kk(i)
1003
+
1004
+ RR = R + K
1005
+
1006
+ U(i) = / 0, -kk(i) <= R(i) <= kk(i)
1007
+ \ 1, otherwise
1008
+
1009
+ DDL(i) = / 1, dd(i) = 1
1010
+ \ 0, otherwise
1011
+
1012
+ DDQ(i) = / 1, dd(i) = 2
1013
+ \ 0, otherwise
1014
+
1015
+ Dl = diag(mm) * diag(U) * diag(DDL)
1016
+ Dq = diag(mm) * diag(U) * diag(DDQ)
1017
+
1018
+ w = (Dl + Dq * diag(RR)) * RR
1019
+
1020
+ F_U(X, CP) = 1/2 * w'*H*w + Cw'*w
1021
+ """
1022
+ if name is None:
1023
+ cp = self.get_cost_params()
1024
+ else:
1025
+ cp = self.get_cost_params(name)
1026
+
1027
+ N, Cw, H, dd, rh, kk, mm = \
1028
+ cp["N"], cp["Cw"], cp["H"], cp["dd"], cp["rh"], cp["kk"], cp["mm"]
1029
+ nw = N.shape[0]
1030
+ r = N * x - rh # Nx - rhat
1031
+ iLT = find(r < -kk) # below dead zone
1032
+ iEQ = find((r == 0) & (kk == 0)) # dead zone doesn't exist
1033
+ iGT = find(r > kk) # above dead zone
1034
+ iND = np.r_[iLT, iEQ, iGT] # rows that are Not in the Dead region
1035
+ iL = find(dd == 1) # rows using linear function
1036
+ iQ = find(dd == 2) # rows using quadratic function
1037
+ LL = c_sparse((np.ones(len(iL)), (iL, iL)), (nw, nw))
1038
+ QQ = c_sparse((np.ones(len(iQ)), (iQ, iQ)), (nw, nw))
1039
+ kbar = c_sparse((np.r_[np.ones(len(iLT)),
1040
+ np.zeros(len(iEQ)),
1041
+ -np.ones(len(iGT))], (iND, iND)), (nw, nw)) * kk
1042
+ rr = r + kbar # apply non-dead zone shift
1043
+ M = c_sparse((mm[iND], (iND, iND)), (nw, nw)) # dead zone or scale
1044
+ diagrr = c_sparse((rr, (np.arange(nw), np.arange(nw))), (nw, nw))
1045
+
1046
+ # linear rows multiplied by rr(i), quadratic rows by rr(i)^2
1047
+ w = M * (LL + QQ * diagrr) * rr
1048
+
1049
+ f = np.dot(w * H, w) / 2 + np.dot(Cw, w)
1050
+
1051
+ return f
1052
+
1053
+ def get_cost_params(self, name=None):
1054
+ """Returns the cost parameter struct for user-defined costs.
1055
+
1056
+ Requires calling L{build_cost_params} first to build the full set of
1057
+ parameters. Returns the full cost parameter struct for all user-defined
1058
+ costs that incorporates all of the named cost sets added via
1059
+ L{add_costs}, or, if a name is provided it returns the cost dict
1060
+ corresponding to the named set of cost rows (C{N} still has full number
1061
+ of columns).
1062
+
1063
+ The cost parameters are returned in a dict with the following fields::
1064
+ N - nw x nx sparse matrix
1065
+ Cw - nw x 1 vector
1066
+ H - nw x nw sparse matrix (optional, all zeros by default)
1067
+ dd, mm - nw x 1 vectors (optional, all ones by default)
1068
+ rh, kk - nw x 1 vectors (optional, all zeros by default)
1069
+ """
1070
+ if not 'params' in self.cost:
1071
+ logger.debug('opf_model.get_cost_params: must call build_cost_params first\n')
1072
+
1073
+ cp = self.cost["params"]
1074
+
1075
+ if name is not None:
1076
+ if self.getN('cost', name):
1077
+ idx = np.arange(self.cost["idx"]["i1"][name], self.cost["idx"]["iN"][name])
1078
+ nwa = self.cost["idx"]["i1"][name]
1079
+ nwb = self.cost["idx"]["iN"][name]
1080
+ cp["N"] = cp["N"][idx, :]
1081
+ cp["Cw"] = cp["Cw"][idx]
1082
+ cp["H"] = cp["H"][nwa:nwb, nwa:nwb]
1083
+ cp["dd"] = cp["dd"][idx]
1084
+ cp["rh"] = cp["rh"][idx]
1085
+ cp["kk"] = cp["kk"][idx]
1086
+ cp["mm"] = cp["mm"][idx]
1087
+
1088
+ return cp
1089
+
1090
+ def get_idx(self):
1091
+ """ Returns the idx struct for vars, lin/nln constraints, costs.
1092
+
1093
+ Returns a structure for each with the beginning and ending
1094
+ index value and the number of elements for each named block.
1095
+ The 'i1' field (that's a one) is a dict with all of the
1096
+ starting indices, 'iN' contains all the ending indices and
1097
+ 'N' contains all the sizes. Each is a dict whose keys are
1098
+ the named blocks.
1099
+
1100
+ Examples::
1101
+ [vv, ll, nn] = get_idx(om)
1102
+
1103
+ For a variable block named 'z' we have::
1104
+ vv['i1']['z'] - starting index for 'z' in optimization vector x
1105
+ vv['iN']['z'] - ending index for 'z' in optimization vector x
1106
+ vv["N"] - number of elements in 'z'
1107
+
1108
+ To extract a 'z' variable from x::
1109
+ z = x(vv['i1']['z']:vv['iN']['z'])
1110
+
1111
+ To extract the multipliers on a linear constraint set
1112
+ named 'foo', where mu_l and mu_u are the full set of
1113
+ linear constraint multipliers::
1114
+ mu_l_foo = mu_l(ll['i1']['foo']:ll['iN']['foo'])
1115
+ mu_u_foo = mu_u(ll['i1']['foo']:ll['iN']['foo'])
1116
+
1117
+ The number of nonlinear constraints in a set named 'bar'::
1118
+ nbar = nn["N"].bar
1119
+ (note: the following is preferable ::
1120
+ nbar = getN(om, 'nln', 'bar')
1121
+ ... if you haven't already called L{get_idx} to get C{nn}.)
1122
+ """
1123
+ vv = self.var["idx"]
1124
+ ll = self.lin["idx"]
1125
+ nn = self.nln["idx"]
1126
+ cc = self.cost["idx"]
1127
+
1128
+ return vv, ll, nn, cc
1129
+
1130
+ def get_ppc(self):
1131
+ """Returns the PYPOWER case dict.
1132
+ """
1133
+ return self.ppc
1134
+
1135
+ def getN(self, selector, name=None):
1136
+ """Returns the number of variables, constraints or cost rows.
1137
+
1138
+ Returns either the total number of variables/constraints/cost rows
1139
+ or the number corresponding to a specified named block.
1140
+
1141
+ Examples::
1142
+ N = getN(om, 'var') : total number of variables
1143
+ N = getN(om, 'lin') : total number of linear constraints
1144
+ N = getN(om, 'nln') : total number of nonlinear constraints
1145
+ N = getN(om, 'cost') : total number of cost rows (in N)
1146
+ N = getN(om, 'var', name) : number of variables in named set
1147
+ N = getN(om, 'lin', name) : number of linear constraints in named set
1148
+ N = getN(om, 'nln', name) : number of nonlinear cons. in named set
1149
+ N = getN(om, 'cost', name) : number of cost rows (in N) in named set
1150
+ """
1151
+ if name is None:
1152
+ N = getattr(self, selector)["N"]
1153
+ else:
1154
+ if name in getattr(self, selector)["idx"]["N"]:
1155
+ N = getattr(self, selector)["idx"]["N"][name]
1156
+ else:
1157
+ N = 0
1158
+ return N
1159
+
1160
+ def getv(self, name=None):
1161
+ """Returns initial value, lower bound and upper bound for opt variables.
1162
+
1163
+ Returns the initial value, lower bound and upper bound for the full
1164
+ optimization variable vector, or for a specific named variable set.
1165
+
1166
+ Examples::
1167
+ x, xmin, xmax = getv(om)
1168
+ Pg, Pmin, Pmax = getv(om, 'Pg')
1169
+ """
1170
+ if name is None:
1171
+ v0 = np.array([])
1172
+ vl = np.array([])
1173
+ vu = np.array([])
1174
+ for k in range(self.var["NS"]):
1175
+ name = self.var["order"][k]
1176
+ v0 = np.r_[v0, self.var["data"]["v0"][name]]
1177
+ vl = np.r_[vl, self.var["data"]["vl"][name]]
1178
+ vu = np.r_[vu, self.var["data"]["vu"][name]]
1179
+ else:
1180
+ if name in self.var["idx"]["N"]:
1181
+ v0 = self.var["data"]["v0"][name]
1182
+ vl = self.var["data"]["vl"][name]
1183
+ vu = self.var["data"]["vu"][name]
1184
+ else:
1185
+ v0 = np.array([])
1186
+ vl = np.array([])
1187
+ vu = np.array([])
1188
+
1189
+ return v0, vl, vu
1190
+
1191
+ def linear_constraints(self):
1192
+ """Builds and returns the full set of linear constraints.
1193
+
1194
+ Builds the full set of linear constraints based on those added by
1195
+ L{add_constraints}::
1196
+
1197
+ L <= A * x <= U
1198
+ """
1199
+
1200
+ # initialize A, l and u
1201
+ # nnzA = 0
1202
+ # for k in range(self.lin["NS"]):
1203
+ # nnzA = nnzA + nnz(self.lin["data"].A.(self.lin.order{k}))
1204
+
1205
+ if self.lin["N"]:
1206
+ A = sp.lil_matrix((self.lin["N"], self.var["N"]))
1207
+ u = inf * np.ones(self.lin["N"])
1208
+ l = -u
1209
+ else:
1210
+ A = None
1211
+ u = np.array([])
1212
+ l = np.array([])
1213
+
1214
+ return A, l, u
1215
+
1216
+ # fill in each piece
1217
+ for k in range(self.lin["NS"]):
1218
+ name = self.lin["order"][k]
1219
+ N = self.lin["idx"]["N"][name]
1220
+ if N: # non-zero number of rows to add
1221
+ Ak = self.lin["data"]["A"][name] # A for kth linear constrain set
1222
+ i1 = self.lin["idx"]["i1"][name] # starting row index
1223
+ iN = self.lin["idx"]["iN"][name] # ing row index
1224
+ vsl = self.lin["data"]["vs"][name] # var set list
1225
+ kN = 0 # initialize last col of Ak used
1226
+ # FIXME: Sparse matrix with fancy indexing
1227
+ Ai = np.zeros((N, self.var["N"]))
1228
+ for v in vsl:
1229
+ j1 = self.var["idx"]["i1"][v] # starting column in A
1230
+ jN = self.var["idx"]["iN"][v] # ing column in A
1231
+ k1 = kN # starting column in Ak
1232
+ kN = kN + self.var["idx"]["N"][v] # ing column in Ak
1233
+ Ai[:, j1:jN] = Ak[:, k1:kN].todense()
1234
+
1235
+ A[i1:iN, :] = Ai
1236
+
1237
+ l[i1:iN] = self.lin["data"]["l"][name]
1238
+ u[i1:iN] = self.lin["data"]["u"][name]
1239
+
1240
+ return A.tocsr(), l, u
1241
+
1242
+ def userdata(self, name, val=None):
1243
+ """Used to save or retrieve values of user data.
1244
+
1245
+ This function allows the user to save any arbitrary data in the object
1246
+ for later use. This can be useful when using a user function to add
1247
+ variables, constraints, costs, etc. For example, suppose some special
1248
+ indexing is constructed when adding some variables or constraints.
1249
+ This indexing data can be stored and used later to "unpack" the results
1250
+ of the solved case.
1251
+ """
1252
+ if val is not None:
1253
+ self.user_data[name] = val
1254
+ return self
1255
+ else:
1256
+ if name in self.user_data:
1257
+ return self.user_data[name]
1258
+ else:
1259
+ return np.array([])
1260
+
1261
+ def opf_execute(om, ppopt):
1262
+ """
1263
+ Executes the OPF specified by an OPF model object.
1264
+
1265
+ C{results} are returned with internal indexing, all equipment
1266
+ in-service, etc.
1267
+
1268
+ @see: L{opf}, L{opf_setup}
1269
+
1270
+ @author: Ray Zimmerman (PSERC Cornell)
1271
+ """
1272
+ # ----- setup -----
1273
+ # options
1274
+ alg = ppopt['OPF_ALG']
1275
+
1276
+ # build user-defined costs
1277
+ om.build_cost_params()
1278
+
1279
+ # get indexing
1280
+ vv, ll, nn, _ = om.get_idx()
1281
+
1282
+ # ----- run AC OPF solver -----
1283
+ # if OPF_ALG not set, choose best available option
1284
+ if alg == 0:
1285
+ alg = 560 # MIPS
1286
+
1287
+ # update deprecated algorithm codes to new, generalized formulation equivalents
1288
+ if alg == 100 | alg == 200: # CONSTR
1289
+ alg = 300
1290
+ elif alg == 120 | alg == 220: # dense LP
1291
+ alg = 320
1292
+ elif alg == 140 | alg == 240: # sparse (relaxed) LP
1293
+ alg = 340
1294
+ elif alg == 160 | alg == 260: # sparse (full) LP
1295
+ alg = 360
1296
+
1297
+ ppopt['OPF_ALG_POLY'] = alg
1298
+
1299
+ # run specific AC OPF solver
1300
+ if alg == 560 or alg == 565: # PIPS
1301
+ results, success, raw = pipsopf_solver(om, ppopt)
1302
+ elif alg == 580: # IPOPT
1303
+ try:
1304
+ __import__('pyipopt')
1305
+ results, success, raw = ipoptopf_solver(om, ppopt)
1306
+ except ImportError:
1307
+ raise ImportError('OPF_ALG %d requires IPOPT '
1308
+ '(see https://projects.coin-or.org/Ipopt/)' %
1309
+ alg)
1310
+ else:
1311
+ logger.debug('opf_execute: OPF_ALG %d is not a valid algorithm code\n' % alg)
1312
+
1313
+ if ('output' not in raw) or ('alg' not in raw['output']):
1314
+ raw['output']['alg'] = alg
1315
+
1316
+ if success:
1317
+ # copy bus voltages back to gen matrix
1318
+ results['gen'][:, IDX.gen.VG] = results['bus'][
1319
+ results['gen'][:, IDX.gen.GEN_BUS].astype(int),
1320
+ IDX.bus.VM]
1321
+
1322
+ # gen PQ capability curve multipliers
1323
+ if (ll['N']['PQh'] > 0) | (ll['N']['PQl'] > 0):
1324
+ mu_PQh = results['mu']['lin']['l'][ll['i1']['PQh']:ll['iN']['PQh']
1325
+ ] - results['mu']['lin']['u'][ll['i1']['PQh']:ll['iN']['PQh']]
1326
+ mu_PQl = results['mu']['lin']['l'][ll['i1']['PQl']:ll['iN']['PQl']
1327
+ ] - results['mu']['lin']['u'][ll['i1']['PQl']:ll['iN']['PQl']]
1328
+ Apqdata = om.userdata('Apqdata')
1329
+ results['gen'] = opfcn.update_mupq(results['baseMVA'], results['gen'], mu_PQh, mu_PQl, Apqdata)
1330
+
1331
+ # compute g, dg, f, df, d2f if requested by RETURN_RAW_DER = 1
1332
+ if ppopt['RETURN_RAW_DER']:
1333
+ # move from results to raw if using v4.0 of MINOPF or TSPOPF
1334
+ if 'dg' in results:
1335
+ raw = {}
1336
+ raw['dg'] = results['dg']
1337
+ raw['g'] = results['g']
1338
+
1339
+ # compute g, dg, unless already done by post-v4.0 MINOPF or TSPOPF
1340
+ if 'dg' not in raw:
1341
+ ppc = om.get_ppc()
1342
+ Ybus, Yf, Yt = makeYbus(ppc['baseMVA'], ppc['bus'], ppc['branch'])
1343
+ g, geq, dg, dgeq = opf_consfcn(results['x'], om, Ybus, Yf, Yt, ppopt)
1344
+ raw['g'] = np.r_[geq, g]
1345
+ raw['dg'] = np.r_[dgeq.T, dg.T] # true Jacobian organization
1346
+
1347
+ # compute df, d2f
1348
+ _, df, d2f = opf_costfcn(results['x'], om, True)
1349
+ raw['df'] = df
1350
+ raw['d2f'] = d2f
1351
+
1352
+ # delete g and dg fieldsfrom results if using v4.0 of MINOPF or TSPOPF
1353
+ if 'dg' in results:
1354
+ del results['dg']
1355
+ del results['g']
1356
+
1357
+ # angle limit constraint multipliers
1358
+ if ll['N']['ang'] > 0:
1359
+ iang = om.userdata('iang')
1360
+ results['branch'][
1361
+ iang, IDX.branch.MU_ANGMIN] = results['mu']['lin']['l'][
1362
+ ll['i1']['ang']: ll['iN']['ang']] * deg2rad
1363
+ results['branch'][
1364
+ iang, IDX.branch.MU_ANGMAX] = results['mu']['lin']['u'][
1365
+ ll['i1']['ang']: ll['iN']['ang']] * deg2rad
1366
+ else:
1367
+ # assign empty g, dg, f, df, d2f if requested by RETURN_RAW_DER = 1
1368
+ raw['dg'] = np.array([])
1369
+ raw['g'] = np.array([])
1370
+ raw['df'] = np.array([])
1371
+ raw['d2f'] = np.array([])
1372
+
1373
+ # assign values and limit shadow prices for variables
1374
+ if om.var['order']:
1375
+ results['var'] = {'val': {}, 'mu': {'l': {}, 'u': {}}}
1376
+ for name in om.var['order']:
1377
+ if om.getN('var', name):
1378
+ idx = np.arange(vv['i1'][name], vv['iN'][name])
1379
+ results['var']['val'][name] = results['x'][idx]
1380
+ results['var']['mu']['l'][name] = results['mu']['var']['l'][idx]
1381
+ results['var']['mu']['u'][name] = results['mu']['var']['u'][idx]
1382
+
1383
+ # assign shadow prices for linear constraints
1384
+ if om.lin['order']:
1385
+ results['lin'] = {'mu': {'l': {}, 'u': {}}}
1386
+ for name in om.lin['order']:
1387
+ if om.getN('lin', name):
1388
+ idx = np.arange(ll['i1'][name], ll['iN'][name])
1389
+ results['lin']['mu']['l'][name] = results['mu']['lin']['l'][idx]
1390
+ results['lin']['mu']['u'][name] = results['mu']['lin']['u'][idx]
1391
+
1392
+ # assign shadow prices for nonlinear constraints
1393
+ if om.nln['order']:
1394
+ results['nln'] = {'mu': {'l': {}, 'u': {}}}
1395
+ for name in om.nln['order']:
1396
+ if om.getN('nln', name):
1397
+ idx = np.arange(nn['i1'][name], nn['iN'][name])
1398
+ results['nln']['mu']['l'][name] = results['mu']['nln']['l'][idx]
1399
+ results['nln']['mu']['u'][name] = results['mu']['nln']['u'][idx]
1400
+
1401
+ # assign values for components of user cost
1402
+ if om.cost['order']:
1403
+ results['cost'] = {}
1404
+ for name in om.cost['order']:
1405
+ if om.getN('cost', name):
1406
+ results['cost'][name] = om.compute_cost(results['x'], name)
1407
+
1408
+ # if single-block PWL costs were converted to POLY, insert dummy y into x
1409
+ # Note: The "y" portion of x will be nonsense, but everything should at
1410
+ # least be in the expected locations.
1411
+ pwl1 = om.userdata('pwl1')
1412
+ if (len(pwl1) > 0) and (alg != 545) and (alg != 550):
1413
+ # get indexing
1414
+ vv, _, _, _ = om.get_idx()
1415
+ nx = vv['iN']['Qg']
1416
+
1417
+ y = np.zeros(len(pwl1))
1418
+ raw['xr'] = np.r_[raw['xr'][:nx], y, raw['xr'][nx:]]
1419
+ results['x'] = np.r_[results['x'][:nx], y, results['x'][nx:]]
1420
+
1421
+ return results, success, raw
1422
+
1423
+
1424
+ def opf_args(*args):
1425
+ """Parses and initializes OPF input arguments.
1426
+
1427
+ Returns the full set of initialized OPF input arguments, filling in
1428
+ default values for missing arguments. See Examples below for the
1429
+ possible calling syntax options.
1430
+
1431
+ Input arguments options::
1432
+
1433
+ opf_args(ppc)
1434
+ opf_args(ppc, ppopt)
1435
+ opf_args(ppc, userfcn, ppopt)
1436
+ opf_args(ppc, A, l, u)
1437
+ opf_args(ppc, A, l, u, ppopt)
1438
+ opf_args(ppc, A, l, u, ppopt, N, fparm, H, Cw)
1439
+ opf_args(ppc, A, l, u, ppopt, N, fparm, H, Cw, z0, zl, zu)
1440
+
1441
+ opf_args(baseMVA, bus, gen, branch, areas, gencost)
1442
+ opf_args(baseMVA, bus, gen, branch, areas, gencost, ppopt)
1443
+ opf_args(baseMVA, bus, gen, branch, areas, gencost, userfcn, ppopt)
1444
+ opf_args(baseMVA, bus, gen, branch, areas, gencost, A, l, u)
1445
+ opf_args(baseMVA, bus, gen, branch, areas, gencost, A, l, u, ppopt)
1446
+ opf_args(baseMVA, bus, gen, branch, areas, gencost, A, l, u, ...
1447
+ ppopt, N, fparm, H, Cw)
1448
+ opf_args(baseMVA, bus, gen, branch, areas, gencost, A, l, u, ...
1449
+ ppopt, N, fparm, H, Cw, z0, zl, zu)
1450
+
1451
+ The data for the problem can be specified in one of three ways:
1452
+ 1. a string (ppc) containing the file name of a PYPOWER case
1453
+ which defines the data matrices baseMVA, bus, gen, branch, and
1454
+ gencost (areas is not used at all, it is only included for
1455
+ backward compatibility of the API).
1456
+ 2. a dict (ppc) containing the data matrices as fields.
1457
+ 3. the individual data matrices themselves.
1458
+
1459
+ The optional user parameters for user constraints (C{A, l, u}), user costs
1460
+ (C{N, fparm, H, Cw}), user variable initializer (z0), and user variable
1461
+ limits (C{zl, zu}) can also be specified as fields in a case dict,
1462
+ either passed in directly or defined in a case file referenced by name.
1463
+
1464
+ When specified, C{A, l, u} represent additional linear constraints on the
1465
+ optimization variables, C{l <= A*[x z] <= u}. If the user specifies an C{A}
1466
+ matrix that has more columns than the number of "C{x}" (OPF) variables,
1467
+ then there are extra linearly constrained "C{z}" variables. For an
1468
+ explanation of the formulation used and instructions for forming the
1469
+ C{A} matrix, see the MATPOWER manual.
1470
+
1471
+ A generalized cost on all variables can be applied if input arguments
1472
+ C{N}, C{fparm}, C{H} and C{Cw} are specified. First, a linear
1473
+ transformation of the optimization variables is defined by means of
1474
+ C{r = N * [x z]}. Then, to each element of r a function is applied as
1475
+ encoded in the C{fparm} matrix (see Matpower manual). If the resulting
1476
+ vector is named C{w}, then C{H} and C{Cw} define a quadratic cost on
1477
+ C{w}: C{(1/2)*w'*H*w + Cw * w}.
1478
+ C{H} and C{N} should be sparse matrices and C{H} should also be symmetric.
1479
+
1480
+ The optional C{ppopt} vector specifies PYPOWER options. See L{ppoption}
1481
+ for details and default values.
1482
+
1483
+ @author: Ray Zimmerman (PSERC Cornell)
1484
+ @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad
1485
+ Autonoma de Manizales)
1486
+ """
1487
+ # nargin = len([arg for arg in [baseMVA, bus, gen, branch, areas, gencost,
1488
+ # Au, lbu, ubu, ppopt, N, fparm, H, Cw,
1489
+ # z0, zl, zu] if arg is not None])
1490
+ nargin = len(args)
1491
+ userfcn = np.array([])
1492
+
1493
+ # parsing filename or dict
1494
+ if isinstance(args[0], str) or isinstance(args[0], dict):
1495
+ if nargin in [1, 2, 3, 4, 5, 9, 12]:
1496
+ casefile = args[0]
1497
+ if nargin == 12:
1498
+ baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu, ppopt, N, fparm = args
1499
+ elif nargin == 9:
1500
+ baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu = args
1501
+ elif nargin == 5:
1502
+ baseMVA, bus, gen, branch, areas = args
1503
+ elif nargin == 4:
1504
+ baseMVA, bus, gen, branch = args
1505
+ elif nargin == 3:
1506
+ baseMVA, bus, gen = args
1507
+ userfcn = bus
1508
+ elif nargin == 2:
1509
+ baseMVA, bus = args
1510
+ elif nargin == 1:
1511
+ pass # Use default values for all variables
1512
+ else:
1513
+ logger.debug('opf_args: Incorrect input arg order, number or type\n')
1514
+
1515
+ # Set default values for variables if they are not provided
1516
+ zu = np.array([]) if nargin in [1, 2, 3, 4, 5, 9, 12] else zu
1517
+ zl = np.array([]) if nargin in [1, 2, 3, 4, 5, 9, 12] else zl
1518
+ z0 = np.array([]) if nargin in [1, 2, 3, 4, 5, 9, 12] else z0
1519
+ Cw = np.array([]) if nargin in [1, 2, 3, 4, 5, 9, 12] else Cw
1520
+ H = None if nargin in [1, 2, 3, 4, 5, 9, 12] else H
1521
+ fparm = np.array([]) if nargin in [1, 2, 3, 4, 5, 9, 12] else fparm
1522
+ N = None if nargin in [1, 2, 3, 4, 5, 9, 12] else N
1523
+ ppopt = ppoption() if nargin in [1, 2, 3, 4, 5, 9, 12] else ppopt
1524
+ ubu = np.array([]) if nargin in [1, 2, 3, 4, 5, 9, 12] else ubu
1525
+ lbu = np.array([]) if nargin in [1, 2, 3, 4, 5, 9, 12] else lbu
1526
+ Au = None if nargin in [1, 2, 3, 4, 5, 9, 12] else Au
1527
+ else:
1528
+ logger.debug('opf_args: Incorrect input arg order, number or type\n')
1529
+
1530
+ ppc = loadcase(casefile)
1531
+ baseMVA, bus, gen, branch, gencost = \
1532
+ ppc['baseMVA'], ppc['bus'], ppc['gen'], ppc['branch'], ppc['gencost']
1533
+ if 'areas' in ppc:
1534
+ areas = ppc['areas']
1535
+ else:
1536
+ areas = np.array([])
1537
+ if Au is None and 'A' in ppc:
1538
+ Au, lbu, ubu = ppc["A"], ppc["l"], ppc["u"]
1539
+ if N is None and 'N' in ppc: # these two must go together
1540
+ N, Cw = ppc["N"], ppc["Cw"]
1541
+ if H is None and 'H' in ppc: # will default to zeros
1542
+ H = ppc["H"]
1543
+ if (fparm is None or len(fparm) == 0) and 'fparm' in ppc: # will default to [1 0 0 1]
1544
+ fparm = ppc["fparm"]
1545
+ if (z0 is None or len(z0) == 0) and 'z0' in ppc:
1546
+ z0 = ppc["z0"]
1547
+ if (zl is None or len(zl) == 0) and 'zl' in ppc:
1548
+ zl = ppc["zl"]
1549
+ if (zu is None or len(zu) == 0) and 'zu' in ppc:
1550
+ zu = ppc["zu"]
1551
+ if (userfcn is None or len(userfcn) == 0) and 'userfcn' in ppc:
1552
+ userfcn = ppc['userfcn']
1553
+ else: # passing individual data matrices
1554
+ # ----opf(baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu, ppopt, N, fparm, H, Cw, z0, zl, zu)
1555
+ # 17 opf(baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu, ppopt, N, fparm, H, Cw, z0, zl, zu)
1556
+ # 14 opf(baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu, ppopt, N, fparm, H, Cw)
1557
+ # 10 opf(baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu, ppopt)
1558
+ # 9 opf(baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu)
1559
+ # 8 opf(baseMVA, bus, gen, branch, areas, gencost, userfcn, ppopt)
1560
+ # 7 opf(baseMVA, bus, gen, branch, areas, gencost, ppopt)
1561
+ # 6 opf(baseMVA, bus, gen, branch, areas, gencost)
1562
+ if nargin in [6, 7, 8, 9, 10, 14, 17]:
1563
+ if nargin == 17:
1564
+ baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu, ppopt, N, fparm, H, Cw, z0, zl, zu = args
1565
+ elif nargin == 14:
1566
+ baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu, ppopt, N, fparm, H, Cw = args
1567
+ zu = np.array([])
1568
+ zl = np.array([])
1569
+ z0 = np.array([])
1570
+ elif nargin == 10:
1571
+ baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu, ppopt = args
1572
+ zu = np.array([])
1573
+ zl = np.array([])
1574
+ z0 = np.array([])
1575
+ Cw = np.array([])
1576
+ H = None
1577
+ fparm = np.array([])
1578
+ N = None
1579
+ elif nargin == 9:
1580
+ baseMVA, bus, gen, branch, areas, gencost, Au, lbu, ubu = args
1581
+ zu = np.array([])
1582
+ zl = np.array([])
1583
+ z0 = np.array([])
1584
+ Cw = np.array([])
1585
+ H = None
1586
+ fparm = np.array([])
1587
+ N = None
1588
+ ppopt = ppoption()
1589
+ elif nargin == 8:
1590
+ baseMVA, bus, gen, branch, areas, gencost, userfcn, ppopt = args
1591
+ zu = np.array([])
1592
+ zl = np.array([])
1593
+ z0 = np.array([])
1594
+ Cw = np.array([])
1595
+ H = None
1596
+ fparm = np.array([])
1597
+ N = None
1598
+ ubu = np.array([])
1599
+ lbu = np.array([])
1600
+ Au = None
1601
+ elif nargin == 7:
1602
+ baseMVA, bus, gen, branch, areas, gencost, ppopt = args
1603
+ zu = np.array([])
1604
+ zl = np.array([])
1605
+ z0 = np.array([])
1606
+ Cw = np.array([])
1607
+ H = None
1608
+ fparm = np.array([])
1609
+ N = None
1610
+ ubu = np.array([])
1611
+ lbu = np.array([])
1612
+ Au = None
1613
+ elif nargin == 6:
1614
+ baseMVA, bus, gen, branch, areas, gencost = args
1615
+ zu = np.array([])
1616
+ zl = np.array([])
1617
+ z0 = np.array([])
1618
+ Cw = np.array([])
1619
+ H = None
1620
+ fparm = np.array([])
1621
+ N = None
1622
+ ppopt = ppoption()
1623
+ ubu = np.array([])
1624
+ lbu = np.array([])
1625
+ Au = None
1626
+ else:
1627
+ logger.debug('opf_args: Incorrect input arg order, number or type\n')
1628
+
1629
+ if N is not None:
1630
+ nw = N.shape[0]
1631
+ else:
1632
+ nw = 0
1633
+
1634
+ if nw:
1635
+ if Cw.shape[0] != nw:
1636
+ logger.debug('opf_args.m: dimension mismatch between N and Cw in '
1637
+ 'generalized cost parameters\n')
1638
+ if len(fparm) > 0 and fparm.shape[0] != nw:
1639
+ logger.debug('opf_args.m: dimension mismatch between N and fparm '
1640
+ 'in generalized cost parameters\n')
1641
+ if (H is not None) and (H.shape[0] != nw | H.shape[0] != nw):
1642
+ logger.debug('opf_args.m: dimension mismatch between N and H in '
1643
+ 'generalized cost parameters\n')
1644
+ if Au is not None:
1645
+ if Au.shape[0] > 0 and N.shape[1] != Au.shape[1]:
1646
+ logger.debug('opf_args.m: A and N must have the same number '
1647
+ 'of columns\n')
1648
+ # make sure N and H are sparse
1649
+ if not sp.issparse(N):
1650
+ logger.debug('opf_args.m: N must be sparse in generalized cost '
1651
+ 'parameters\n')
1652
+ if not sp.issparse(H):
1653
+ logger.debug('opf_args.m: H must be sparse in generalized cost parameters\n')
1654
+
1655
+ if Au is not None and not sp.issparse(Au):
1656
+ logger.debug('opf_args.m: Au must be sparse\n')
1657
+ if ppopt == None or len(ppopt) == 0:
1658
+ ppopt = ppoption()
1659
+
1660
+ return baseMVA, bus, gen, branch, gencost, Au, lbu, ubu, \
1661
+ ppopt, N, fparm, H, Cw, z0, zl, zu, userfcn, areas
1662
+
1663
+
1664
+ def opf_args2(*args):
1665
+ """
1666
+ Parses and initializes OPF input arguments.
1667
+ """
1668
+ baseMVA, bus, gen, branch, gencost, Au, lbu, ubu, \
1669
+ ppopt, N, fparm, H, Cw, z0, zl, zu, userfcn, areas = opf_args(*args)
1670
+
1671
+ ppc = args[0] if isinstance(args[0], dict) else {}
1672
+
1673
+ ppc['baseMVA'] = baseMVA
1674
+ ppc['bus'] = bus
1675
+ ppc['gen'] = gen
1676
+ ppc['branch'] = branch
1677
+ ppc['gencost'] = gencost
1678
+
1679
+ if areas is not None and len(areas) > 0:
1680
+ ppc["areas"] = areas
1681
+ if lbu is not None and len(lbu) > 0:
1682
+ ppc["A"], ppc["l"], ppc["u"] = Au, lbu, ubu
1683
+ if Cw is not None and len(Cw) > 0:
1684
+ ppc["N"], ppc["Cw"] = N, Cw
1685
+ if len(fparm) > 0:
1686
+ ppc["fparm"] = fparm
1687
+ # if len(H) > 0:
1688
+ ppc["H"] = H
1689
+ if z0 is not None and len(z0) > 0:
1690
+ ppc["z0"] = z0
1691
+ if zl is not None and len(zl) > 0:
1692
+ ppc["zl"] = zl
1693
+ if zu is not None and len(zu) > 0:
1694
+ ppc["zu"] = zu
1695
+ if userfcn is not None and len(userfcn) > 0:
1696
+ ppc["userfcn"] = userfcn
1697
+
1698
+ return ppc, ppopt
1699
+
1700
+
1701
+ def uopf(*args):
1702
+ """Solves combined unit decommitment / optimal power flow.
1703
+
1704
+ Solves a combined unit decommitment and optimal power flow for a single
1705
+ time period. Uses an algorithm similar to dynamic programming. It proceeds
1706
+ through a sequence of stages, where stage C{N} has C{N} generators shut
1707
+ down, starting with C{N=0}. In each stage, it forms a list of candidates
1708
+ (gens at their C{Pmin} limits) and computes the cost with each one of them
1709
+ shut down. It selects the least cost case as the starting point for the
1710
+ next stage, continuing until there are no more candidates to be shut down
1711
+ or no more improvement can be gained by shutting something down.
1712
+ If C{verbose} in ppopt (see L{ppoption} is C{true}, it prints progress
1713
+ info, if it is > 1 it prints the output of each individual opf.
1714
+
1715
+ @see: L{opf}, L{runuopf}
1716
+
1717
+ @author: Ray Zimmerman (PSERC Cornell)
1718
+ """
1719
+ # ----- initialization -----
1720
+ t0, _ = elapsed() # start timer
1721
+
1722
+ # process input arguments
1723
+ ppc, ppopt = opf_args2(*args)
1724
+
1725
+ # options
1726
+ verbose = ppopt["VERBOSE"]
1727
+ if verbose: # turn down verbosity one level for calls to opf
1728
+ ppopt = ppoption(ppopt, VERBOSE=verbose - 1)
1729
+
1730
+ # ----- do combined unit commitment/optimal power flow -----
1731
+
1732
+ # check for sum(Pmin) > total load, decommit as necessary
1733
+ on = find((ppc["gen"][:, IDX.gen.GEN_STATUS] > 0) & ~isload(ppc["gen"])) # gens in service
1734
+ onld = find((ppc["gen"][:, IDX.gen.GEN_STATUS] > 0) & isload(ppc["gen"])) # disp loads in serv
1735
+ load_capacity = sum(ppc["bus"][:, IDX.bus.PD]) - sum(ppc["gen"][onld, IDX.gen.PMIN]) # total load capacity
1736
+ Pmin = ppc["gen"][on, IDX.gen.PMIN]
1737
+ while sum(Pmin) > load_capacity:
1738
+ # shut down most expensive unit
1739
+ avgPmincost = opfcn.totcost(ppc["gencost"][on, :], Pmin) / Pmin
1740
+ _, i = fairmax(avgPmincost) # pick one with max avg cost at Pmin
1741
+ i = on[i] # convert to generator index
1742
+
1743
+ if verbose:
1744
+ print('Shutting down generator %d so all Pmin limits can be satisfied.\n' % i)
1745
+
1746
+ # set generation to zero
1747
+ ppc["gen"][i, [IDX.gen.PG, IDX.gen.QG, IDX.gen.GEN_STATUS]] = 0
1748
+
1749
+ # update minimum gen capacity
1750
+ on = find((ppc["gen"][:, IDX.gen.GEN_STATUS] > 0) & ~isload(ppc["gen"])) # gens in service
1751
+ Pmin = ppc["gen"][on, IDX.gen.PMIN]
1752
+
1753
+ # run initial opf
1754
+ results = fopf(ppc, ppopt)
1755
+
1756
+ # best case so far
1757
+ results1 = deepcopy(results)
1758
+
1759
+ # best case for this stage (ie. with n gens shut down, n=0,1,2 ...)
1760
+ results0 = deepcopy(results1)
1761
+ ppc["bus"] = results0["bus"].copy() # use these V as starting point for OPF
1762
+
1763
+ while True:
1764
+ # get candidates for shutdown
1765
+ candidates = find((results0["gen"][:, IDX.gen.MU_PMIN] > 0) & (results0["gen"][:, IDX.gen.PMIN] > 0))
1766
+ if len(candidates) == 0:
1767
+ break
1768
+
1769
+ # do not check for further decommitment unless we
1770
+ # see something better during this stage
1771
+ done = True
1772
+
1773
+ for k in candidates:
1774
+ # start with best for this stage
1775
+ ppc["gen"] = results0["gen"].copy()
1776
+
1777
+ # shut down gen k
1778
+ ppc["gen"][k, [IDX.gen.PG, IDX.gen.QG, IDX.gen.GEN_STATUS]] = 0
1779
+
1780
+ # run opf
1781
+ results = fopf(ppc, ppopt)
1782
+
1783
+ # something better?
1784
+ if results['success'] and (results["f"] < results1["f"]):
1785
+ results1 = deepcopy(results)
1786
+ k1 = k
1787
+ done = False # make sure we check for further decommitment
1788
+
1789
+ if done:
1790
+ # decommits at this stage did not help, so let's quit
1791
+ break
1792
+ else:
1793
+ # shutting something else down helps, so let's keep going
1794
+ if verbose:
1795
+ print('Shutting down generator %d.\n' % k1)
1796
+
1797
+ results0 = deepcopy(results1)
1798
+ ppc["bus"] = results0["bus"].copy() # use these V as starting point for OPF
1799
+
1800
+ # compute elapsed time
1801
+ _, results0['et'] = elapsed(t0)
1802
+
1803
+ return results0